From 1d7aadbad6e0709a0ba60f37cbfc8543f9cc70da Mon Sep 17 00:00:00 2001 From: yokobond Date: Tue, 13 Apr 2021 12:49:59 +0000 Subject: [PATCH] deploy: 65f39cea1dbff4e296c2df70ae54ad8734848147 --- dev/dist/microbitMore.mjs | 2 +- dev/lib.min.js | 2 +- dev/lib.min.js.map | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/dist/microbitMore.mjs b/dev/dist/microbitMore.mjs index 6592496..cba0fa4 100644 --- a/dev/dist/microbitMore.mjs +++ b/dev/dist/microbitMore.mjs @@ -16791,7 +16791,7 @@ var MbitMoreBlocks = /*#__PURE__*/function () { return this._peripheral.configMic(true, util).then(function (micState) { if (micState) { - return _this16._peripheral.readSoundLevel(); + return Math.round(_this16._peripheral.readSoundLevel() * 1000 / 255) / 10; } return 0; diff --git a/dev/lib.min.js b/dev/lib.min.js index fbfb427..3e03534 100644 --- a/dev/lib.min.js +++ b/dev/lib.min.js @@ -284857,7 +284857,7 @@ var MbitMoreBlocks = /*#__PURE__*/function () { return this._peripheral.configMic(true, util).then(function (micState) { if (micState) { - return _this16._peripheral.readSoundLevel(); + return Math.round(_this16._peripheral.readSoundLevel() * 1000 / 255) / 10; } return 0; diff --git a/dev/lib.min.js.map b/dev/lib.min.js.map index 390bf75..58b5a50 100644 --- a/dev/lib.min.js.map +++ b/dev/lib.min.js.map @@ -1 +1 @@ -{"version":3,"file":"lib.min.js","sources":["webpack://GUI/webpack/bootstrap","webpack://GUI/./node_modules/@babel/runtime/helpers/interopRequireDefault.js","webpack://GUI/./node_modules/@scratch/paper/dist/paper-full.js","webpack://GUI/./node_modules/@vernier/godirect/dist/godirect.min.umd.js","webpack://GUI/./node_modules/acorn/dist/acorn.mjs","webpack://GUI/./node_modules/scratch-storage/src/builtins/defaultBitmap.png","webpack://GUI/./node_modules/scratch-storage/src/builtins/defaultSound.wav","webpack://GUI/./node_modules/scratch-storage/src/builtins/defaultVector.svg","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/1-snare.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/12-triangle.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/13-bongo.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/14-conga.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/16-guiro.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/18-cuica.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/drums/9-claves.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3","webpack://GUI/./src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav","webpack://GUI/./src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav","webpack://GUI/./node_modules/arraybuffer-loader/lib/to-array-buffer.js","webpack://GUI/./node_modules/atob/browser-atob.js","webpack://GUI/./node_modules/audio-context/index.js","webpack://GUI/./node_modules/babel-runtime/core-js/get-iterator.js","webpack://GUI/./node_modules/babel-runtime/core-js/is-iterable.js","webpack://GUI/./node_modules/babel-runtime/core-js/object/assign.js","webpack://GUI/./node_modules/babel-runtime/core-js/object/create.js","webpack://GUI/./node_modules/babel-runtime/core-js/object/define-property.js","webpack://GUI/./node_modules/babel-runtime/core-js/object/get-own-property-descriptor.js","webpack://GUI/./node_modules/babel-runtime/core-js/object/get-prototype-of.js","webpack://GUI/./node_modules/babel-runtime/core-js/object/keys.js","webpack://GUI/./node_modules/babel-runtime/core-js/object/set-prototype-of.js","webpack://GUI/./node_modules/babel-runtime/core-js/promise.js","webpack://GUI/./node_modules/babel-runtime/core-js/symbol.js","webpack://GUI/./node_modules/babel-runtime/core-js/symbol/iterator.js","webpack://GUI/./node_modules/babel-runtime/helpers/classCallCheck.js","webpack://GUI/./node_modules/babel-runtime/helpers/createClass.js","webpack://GUI/./node_modules/babel-runtime/helpers/defineProperty.js","webpack://GUI/./node_modules/babel-runtime/helpers/extends.js","webpack://GUI/./node_modules/babel-runtime/helpers/inherits.js","webpack://GUI/./node_modules/babel-runtime/helpers/objectWithoutProperties.js","webpack://GUI/./node_modules/babel-runtime/helpers/possibleConstructorReturn.js","webpack://GUI/./node_modules/babel-runtime/helpers/slicedToArray.js","webpack://GUI/./node_modules/babel-runtime/helpers/typeof.js","webpack://GUI/./node_modules/base64-js/index.js","webpack://GUI/./node_modules/scratch-render-fonts/src/Grand9K-Pixel.ttf","webpack://GUI/./node_modules/scratch-render-fonts/src/Griffy-Regular.ttf","webpack://GUI/./node_modules/scratch-render-fonts/src/Knewave.ttf","webpack://GUI/./node_modules/scratch-render-fonts/src/NotoSans-Medium.ttf","webpack://GUI/./node_modules/scratch-render-fonts/src/Scratch.ttf","webpack://GUI/./node_modules/scratch-render-fonts/src/SourceSerifPro-Regular.otf","webpack://GUI/./node_modules/scratch-render-fonts/src/handlee-regular.ttf","webpack://GUI/./src/lib/backpack/sound-thumbnail.jpg","webpack://GUI/./node_modules/bowser/src/bowser.js","webpack://GUI/./node_modules/btoa/index.js","webpack://GUI/./node_modules/buffer/index.js","webpack://GUI/./node_modules/canvas-toBlob/canvas-toBlob.js","webpack://GUI/./node_modules/classnames/index.js","webpack://GUI/./node_modules/computed-style-to-inline-style/dist/index.js","webpack://GUI/./node_modules/core-js/fn/array/includes.js","webpack://GUI/./node_modules/core-js/fn/promise/finally.js","webpack://GUI/./node_modules/core-js/library/fn/get-iterator.js","webpack://GUI/./node_modules/core-js/library/fn/is-iterable.js","webpack://GUI/./node_modules/core-js/library/fn/object/assign.js","webpack://GUI/./node_modules/core-js/library/fn/object/create.js","webpack://GUI/./node_modules/core-js/library/fn/object/define-property.js","webpack://GUI/./node_modules/core-js/library/fn/object/get-own-property-descriptor.js","webpack://GUI/./node_modules/core-js/library/fn/object/get-prototype-of.js","webpack://GUI/./node_modules/core-js/library/fn/object/keys.js","webpack://GUI/./node_modules/core-js/library/fn/object/set-prototype-of.js","webpack://GUI/./node_modules/core-js/library/fn/promise.js","webpack://GUI/./node_modules/core-js/library/fn/symbol/index.js","webpack://GUI/./node_modules/core-js/library/fn/symbol/iterator.js","webpack://GUI/./node_modules/core-js/library/modules/_a-function.js","webpack://GUI/./node_modules/core-js/library/modules/_add-to-unscopables.js","webpack://GUI/./node_modules/core-js/library/modules/_an-instance.js","webpack://GUI/./node_modules/core-js/library/modules/_an-object.js","webpack://GUI/./node_modules/core-js/library/modules/_array-includes.js","webpack://GUI/./node_modules/core-js/library/modules/_classof.js","webpack://GUI/./node_modules/core-js/library/modules/_cof.js","webpack://GUI/./node_modules/core-js/library/modules/_core.js","webpack://GUI/./node_modules/core-js/library/modules/_ctx.js","webpack://GUI/./node_modules/core-js/library/modules/_defined.js","webpack://GUI/./node_modules/core-js/library/modules/_descriptors.js","webpack://GUI/./node_modules/core-js/library/modules/_dom-create.js","webpack://GUI/./node_modules/core-js/library/modules/_enum-bug-keys.js","webpack://GUI/./node_modules/core-js/library/modules/_enum-keys.js","webpack://GUI/./node_modules/core-js/library/modules/_export.js","webpack://GUI/./node_modules/core-js/library/modules/_fails.js","webpack://GUI/./node_modules/core-js/library/modules/_for-of.js","webpack://GUI/./node_modules/core-js/library/modules/_global.js","webpack://GUI/./node_modules/core-js/library/modules/_has.js","webpack://GUI/./node_modules/core-js/library/modules/_hide.js","webpack://GUI/./node_modules/core-js/library/modules/_html.js","webpack://GUI/./node_modules/core-js/library/modules/_ie8-dom-define.js","webpack://GUI/./node_modules/core-js/library/modules/_invoke.js","webpack://GUI/./node_modules/core-js/library/modules/_iobject.js","webpack://GUI/./node_modules/core-js/library/modules/_is-array-iter.js","webpack://GUI/./node_modules/core-js/library/modules/_is-array.js","webpack://GUI/./node_modules/core-js/library/modules/_is-object.js","webpack://GUI/./node_modules/core-js/library/modules/_iter-call.js","webpack://GUI/./node_modules/core-js/library/modules/_iter-create.js","webpack://GUI/./node_modules/core-js/library/modules/_iter-define.js","webpack://GUI/./node_modules/core-js/library/modules/_iter-detect.js","webpack://GUI/./node_modules/core-js/library/modules/_iter-step.js","webpack://GUI/./node_modules/core-js/library/modules/_iterators.js","webpack://GUI/./node_modules/core-js/library/modules/_library.js","webpack://GUI/./node_modules/core-js/library/modules/_meta.js","webpack://GUI/./node_modules/core-js/library/modules/_microtask.js","webpack://GUI/./node_modules/core-js/library/modules/_new-promise-capability.js","webpack://GUI/./node_modules/core-js/library/modules/_object-assign.js","webpack://GUI/./node_modules/core-js/library/modules/_object-create.js","webpack://GUI/./node_modules/core-js/library/modules/_object-dp.js","webpack://GUI/./node_modules/core-js/library/modules/_object-dps.js","webpack://GUI/./node_modules/core-js/library/modules/_object-gopd.js","webpack://GUI/./node_modules/core-js/library/modules/_object-gopn-ext.js","webpack://GUI/./node_modules/core-js/library/modules/_object-gopn.js","webpack://GUI/./node_modules/core-js/library/modules/_object-gops.js","webpack://GUI/./node_modules/core-js/library/modules/_object-gpo.js","webpack://GUI/./node_modules/core-js/library/modules/_object-keys-internal.js","webpack://GUI/./node_modules/core-js/library/modules/_object-keys.js","webpack://GUI/./node_modules/core-js/library/modules/_object-pie.js","webpack://GUI/./node_modules/core-js/library/modules/_object-sap.js","webpack://GUI/./node_modules/core-js/library/modules/_perform.js","webpack://GUI/./node_modules/core-js/library/modules/_promise-resolve.js","webpack://GUI/./node_modules/core-js/library/modules/_property-desc.js","webpack://GUI/./node_modules/core-js/library/modules/_redefine-all.js","webpack://GUI/./node_modules/core-js/library/modules/_redefine.js","webpack://GUI/./node_modules/core-js/library/modules/_set-proto.js","webpack://GUI/./node_modules/core-js/library/modules/_set-species.js","webpack://GUI/./node_modules/core-js/library/modules/_set-to-string-tag.js","webpack://GUI/./node_modules/core-js/library/modules/_shared-key.js","webpack://GUI/./node_modules/core-js/library/modules/_shared.js","webpack://GUI/./node_modules/core-js/library/modules/_species-constructor.js","webpack://GUI/./node_modules/core-js/library/modules/_string-at.js","webpack://GUI/./node_modules/core-js/library/modules/_task.js","webpack://GUI/./node_modules/core-js/library/modules/_to-absolute-index.js","webpack://GUI/./node_modules/core-js/library/modules/_to-integer.js","webpack://GUI/./node_modules/core-js/library/modules/_to-iobject.js","webpack://GUI/./node_modules/core-js/library/modules/_to-length.js","webpack://GUI/./node_modules/core-js/library/modules/_to-object.js","webpack://GUI/./node_modules/core-js/library/modules/_to-primitive.js","webpack://GUI/./node_modules/core-js/library/modules/_uid.js","webpack://GUI/./node_modules/core-js/library/modules/_user-agent.js","webpack://GUI/./node_modules/core-js/library/modules/_wks-define.js","webpack://GUI/./node_modules/core-js/library/modules/_wks-ext.js","webpack://GUI/./node_modules/core-js/library/modules/_wks.js","webpack://GUI/./node_modules/core-js/library/modules/core.get-iterator-method.js","webpack://GUI/./node_modules/core-js/library/modules/core.get-iterator.js","webpack://GUI/./node_modules/core-js/library/modules/core.is-iterable.js","webpack://GUI/./node_modules/core-js/library/modules/es6.array.iterator.js","webpack://GUI/./node_modules/core-js/library/modules/es6.object.assign.js","webpack://GUI/./node_modules/core-js/library/modules/es6.object.create.js","webpack://GUI/./node_modules/core-js/library/modules/es6.object.define-property.js","webpack://GUI/./node_modules/core-js/library/modules/es6.object.get-own-property-descriptor.js","webpack://GUI/./node_modules/core-js/library/modules/es6.object.get-prototype-of.js","webpack://GUI/./node_modules/core-js/library/modules/es6.object.keys.js","webpack://GUI/./node_modules/core-js/library/modules/es6.object.set-prototype-of.js","webpack://GUI/./node_modules/core-js/library/modules/es6.promise.js","webpack://GUI/./node_modules/core-js/library/modules/es6.string.iterator.js","webpack://GUI/./node_modules/core-js/library/modules/es6.symbol.js","webpack://GUI/./node_modules/core-js/library/modules/es7.promise.finally.js","webpack://GUI/./node_modules/core-js/library/modules/es7.promise.try.js","webpack://GUI/./node_modules/core-js/library/modules/es7.symbol.async-iterator.js","webpack://GUI/./node_modules/core-js/library/modules/es7.symbol.observable.js","webpack://GUI/./node_modules/core-js/library/modules/web.dom.iterable.js","webpack://GUI/./node_modules/core-js/modules/_a-function.js","webpack://GUI/./node_modules/core-js/modules/_add-to-unscopables.js","webpack://GUI/./node_modules/core-js/modules/_an-instance.js","webpack://GUI/./node_modules/core-js/modules/_an-object.js","webpack://GUI/./node_modules/core-js/modules/_array-includes.js","webpack://GUI/./node_modules/core-js/modules/_classof.js","webpack://GUI/./node_modules/core-js/modules/_cof.js","webpack://GUI/./node_modules/core-js/modules/_core.js","webpack://GUI/./node_modules/core-js/modules/_ctx.js","webpack://GUI/./node_modules/core-js/modules/_defined.js","webpack://GUI/./node_modules/core-js/modules/_descriptors.js","webpack://GUI/./node_modules/core-js/modules/_dom-create.js","webpack://GUI/./node_modules/core-js/modules/_export.js","webpack://GUI/./node_modules/core-js/modules/_fails.js","webpack://GUI/./node_modules/core-js/modules/_for-of.js","webpack://GUI/./node_modules/core-js/modules/_global.js","webpack://GUI/./node_modules/core-js/modules/_has.js","webpack://GUI/./node_modules/core-js/modules/_hide.js","webpack://GUI/./node_modules/core-js/modules/_html.js","webpack://GUI/./node_modules/core-js/modules/_ie8-dom-define.js","webpack://GUI/./node_modules/core-js/modules/_invoke.js","webpack://GUI/./node_modules/core-js/modules/_iobject.js","webpack://GUI/./node_modules/core-js/modules/_is-array-iter.js","webpack://GUI/./node_modules/core-js/modules/_is-object.js","webpack://GUI/./node_modules/core-js/modules/_iter-call.js","webpack://GUI/./node_modules/core-js/modules/_iter-detect.js","webpack://GUI/./node_modules/core-js/modules/_iterators.js","webpack://GUI/./node_modules/core-js/modules/_library.js","webpack://GUI/./node_modules/core-js/modules/_microtask.js","webpack://GUI/./node_modules/core-js/modules/_new-promise-capability.js","webpack://GUI/./node_modules/core-js/modules/_object-dp.js","webpack://GUI/./node_modules/core-js/modules/_perform.js","webpack://GUI/./node_modules/core-js/modules/_promise-resolve.js","webpack://GUI/./node_modules/core-js/modules/_property-desc.js","webpack://GUI/./node_modules/core-js/modules/_redefine-all.js","webpack://GUI/./node_modules/core-js/modules/_redefine.js","webpack://GUI/./node_modules/core-js/modules/_set-species.js","webpack://GUI/./node_modules/core-js/modules/_set-to-string-tag.js","webpack://GUI/./node_modules/core-js/modules/_shared.js","webpack://GUI/./node_modules/core-js/modules/_species-constructor.js","webpack://GUI/./node_modules/core-js/modules/_task.js","webpack://GUI/./node_modules/core-js/modules/_to-absolute-index.js","webpack://GUI/./node_modules/core-js/modules/_to-integer.js","webpack://GUI/./node_modules/core-js/modules/_to-iobject.js","webpack://GUI/./node_modules/core-js/modules/_to-length.js","webpack://GUI/./node_modules/core-js/modules/_to-primitive.js","webpack://GUI/./node_modules/core-js/modules/_uid.js","webpack://GUI/./node_modules/core-js/modules/_user-agent.js","webpack://GUI/./node_modules/core-js/modules/_wks.js","webpack://GUI/./node_modules/core-js/modules/core.get-iterator-method.js","webpack://GUI/./node_modules/core-js/modules/es6.promise.js","webpack://GUI/./node_modules/core-js/modules/es7.array.includes.js","webpack://GUI/./node_modules/core-js/modules/es7.promise.finally.js","webpack://GUI/./node_modules/core-util-is/lib/util.js","webpack://GUI/./node_modules/react-tabs/style/react-tabs.css","webpack://GUI/./node_modules/scratch-paint/src/components/button-group/button-group.css","webpack://GUI/./node_modules/scratch-paint/src/components/button/button.css","webpack://GUI/./node_modules/scratch-paint/src/components/color-button/color-button.css","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/color-picker.css","webpack://GUI/./node_modules/scratch-paint/src/components/dropdown/dropdown.css","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/fixed-tools.css","webpack://GUI/./node_modules/scratch-paint/src/components/font-dropdown/font-dropdown.css","webpack://GUI/./node_modules/scratch-paint/src/components/forms/input.css","webpack://GUI/./node_modules/scratch-paint/src/components/forms/label.css","webpack://GUI/./node_modules/scratch-paint/src/components/forms/slider.css","webpack://GUI/./node_modules/scratch-paint/src/components/input-group/input-group.css","webpack://GUI/./node_modules/scratch-paint/src/components/labeled-icon-button/labeled-icon-button.css","webpack://GUI/./node_modules/scratch-paint/src/components/loupe/loupe.css","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/mode-tools.css","webpack://GUI/./node_modules/scratch-paint/src/components/paint-editor/paint-editor.css","webpack://GUI/./node_modules/scratch-paint/src/components/scrollable-canvas/scrollable-canvas.css","webpack://GUI/./node_modules/scratch-paint/src/components/tool-select-base/tool-select-base.css","webpack://GUI/./node_modules/scratch-paint/src/containers/paper-canvas.css","webpack://GUI/./src/components/action-menu/action-menu.css","webpack://GUI/./src/components/alerts/alert.css","webpack://GUI/./src/components/alerts/alerts.css","webpack://GUI/./src/components/alerts/inline-message.css","webpack://GUI/./src/components/asset-panel/asset-panel.css","webpack://GUI/./src/components/asset-panel/selector.css","webpack://GUI/./src/components/audio-trimmer/audio-trimmer.css","webpack://GUI/./src/components/backpack/backpack.css","webpack://GUI/./src/components/blocks/blocks.css","webpack://GUI/./src/components/box/box.css","webpack://GUI/./src/components/browser-modal/browser-modal.css","webpack://GUI/./src/components/button/button.css","webpack://GUI/./src/components/camera-modal/camera-modal.css","webpack://GUI/./src/components/cards/card.css","webpack://GUI/./src/components/close-button/close-button.css","webpack://GUI/./src/components/coming-soon/coming-soon.css","webpack://GUI/./src/components/connection-modal/connection-modal.css","webpack://GUI/./src/components/context-menu/context-menu.css","webpack://GUI/./src/components/controls/controls.css","webpack://GUI/./src/components/crash-message/crash-message.css","webpack://GUI/./src/components/custom-procedures/custom-procedures.css","webpack://GUI/./src/components/delete-button/delete-button.css","webpack://GUI/./src/components/direction-picker/dial.css","webpack://GUI/./src/components/direction-picker/direction-picker.css","webpack://GUI/./src/components/divider/divider.css","webpack://GUI/./src/components/drag-layer/drag-layer.css","webpack://GUI/./src/components/filter/filter.css","webpack://GUI/./src/components/forms/input.css","webpack://GUI/./src/components/forms/label.css","webpack://GUI/./src/components/green-flag/green-flag.css","webpack://GUI/./src/components/gui/gui.css","webpack://GUI/./src/components/icon-button/icon-button.css","webpack://GUI/./src/components/language-selector/language-selector.css","webpack://GUI/./src/components/library-item/library-item.css","webpack://GUI/./src/components/library/library.css","webpack://GUI/./src/components/loader/loader.css","webpack://GUI/./src/components/loupe/loupe.css","webpack://GUI/./src/components/menu-bar/account-nav.css","webpack://GUI/./src/components/menu-bar/author-info.css","webpack://GUI/./src/components/menu-bar/community-button.css","webpack://GUI/./src/components/menu-bar/login-dropdown.css","webpack://GUI/./src/components/menu-bar/menu-bar.css","webpack://GUI/./src/components/menu-bar/project-title-input.css","webpack://GUI/./src/components/menu-bar/save-status.css","webpack://GUI/./src/components/menu-bar/share-button.css","webpack://GUI/./src/components/menu-bar/user-avatar.css","webpack://GUI/./src/components/menu/menu.css","webpack://GUI/./src/components/meter/meter.css","webpack://GUI/./src/components/mic-indicator/mic-indicator.css","webpack://GUI/./src/components/modal/modal.css","webpack://GUI/./src/components/monitor-list/monitor-list.css","webpack://GUI/./src/components/monitor/monitor.css","webpack://GUI/./src/components/play-button/play-button.css","webpack://GUI/./src/components/prompt/prompt.css","webpack://GUI/./src/components/question/question.css","webpack://GUI/./src/components/record-modal/record-modal.css","webpack://GUI/./src/components/slider-prompt/slider-prompt.css","webpack://GUI/./src/components/sound-editor/sound-editor.css","webpack://GUI/./src/components/spinner/spinner.css","webpack://GUI/./src/components/sprite-info/sprite-info.css","webpack://GUI/./src/components/sprite-selector-item/sprite-selector-item.css","webpack://GUI/./src/components/sprite-selector/sprite-selector.css","webpack://GUI/./src/components/stage-header/stage-header.css","webpack://GUI/./src/components/stage-selector/stage-selector.css","webpack://GUI/./src/components/stage-wrapper/stage-wrapper.css","webpack://GUI/./src/components/stage/stage.css","webpack://GUI/./src/components/stop-all/stop-all.css","webpack://GUI/./src/components/tag-button/tag-button.css","webpack://GUI/./src/components/target-pane/target-pane.css","webpack://GUI/./src/components/telemetry-modal/telemetry-modal.css","webpack://GUI/./src/components/turbo-mode/turbo-mode.css","webpack://GUI/./src/components/watermark/watermark.css","webpack://GUI/./src/components/waveform/waveform.css","webpack://GUI/./src/components/webgl-modal/webgl-modal.css","webpack://GUI/./node_modules/css-loader/lib/css-base.js","webpack://GUI/./node_modules/css-loader/lib/url/escape.js","webpack://GUI/./node_modules/css-vendor/lib/camelize.js","webpack://GUI/./node_modules/css-vendor/lib/index.js","webpack://GUI/./node_modules/css-vendor/lib/prefix.js","webpack://GUI/./node_modules/css-vendor/lib/supported-property.js","webpack://GUI/./node_modules/css-vendor/lib/supported-value.js","webpack://GUI/./node_modules/debug/src/browser.js","webpack://GUI/./node_modules/debug/src/debug.js","webpack://GUI/./node_modules/decode-html/index.js","webpack://GUI/./node_modules/decode-uri-component/index.js","webpack://GUI/./node_modules/define-properties/index.js","webpack://GUI/./node_modules/dom-helpers/util/inDOM.js","webpack://GUI/./node_modules/dom-helpers/util/scrollbarSize.js","webpack://GUI/./node_modules/dom-serializer/index.js","webpack://GUI/./node_modules/domelementtype/index.js","webpack://GUI/./node_modules/domhandler/index.js","webpack://GUI/./node_modules/domhandler/lib/element.js","webpack://GUI/./node_modules/domhandler/lib/node.js","webpack://GUI/./node_modules/dompurify/dist/purify.js","webpack://GUI/./node_modules/domutils/index.js","webpack://GUI/./node_modules/domutils/lib/helpers.js","webpack://GUI/./node_modules/domutils/lib/legacy.js","webpack://GUI/./node_modules/domutils/lib/manipulation.js","webpack://GUI/./node_modules/domutils/lib/querying.js","webpack://GUI/./node_modules/domutils/lib/stringify.js","webpack://GUI/./node_modules/domutils/lib/traversal.js","webpack://GUI/./node_modules/entities/index.js","webpack://GUI/./node_modules/entities/lib/decode.js","webpack://GUI/./node_modules/entities/lib/decode_codepoint.js","webpack://GUI/./node_modules/entities/lib/encode.js","webpack://GUI/./node_modules/es-abstract/GetIntrinsic.js","webpack://GUI/./node_modules/es-abstract/es5.js","webpack://GUI/./node_modules/es-abstract/helpers/assertRecord.js","webpack://GUI/./node_modules/es-abstract/helpers/callBind.js","webpack://GUI/./node_modules/es-abstract/helpers/callBound.js","webpack://GUI/./node_modules/es-abstract/helpers/isFinite.js","webpack://GUI/./node_modules/es-abstract/helpers/isNaN.js","webpack://GUI/./node_modules/es-abstract/helpers/isPrefixOf.js","webpack://GUI/./node_modules/es-abstract/helpers/isPropertyDescriptor.js","webpack://GUI/./node_modules/es-abstract/helpers/mod.js","webpack://GUI/./node_modules/es-abstract/helpers/sign.js","webpack://GUI/./node_modules/es-to-primitive/es5.js","webpack://GUI/./node_modules/es-to-primitive/helpers/isPrimitive.js","webpack://GUI/./node_modules/es6-object-assign/auto.js","webpack://GUI/./node_modules/es6-object-assign/index.js","webpack://GUI/./node_modules/events/events.js","webpack://GUI/./node_modules/exenv/index.js","webpack://GUI/./node_modules/fast-json-stable-stringify/index.js","webpack://GUI/./node_modules/fbjs/lib/EventListener.js","webpack://GUI/./node_modules/fbjs/lib/ExecutionEnvironment.js","webpack://GUI/./node_modules/fbjs/lib/camelize.js","webpack://GUI/./node_modules/fbjs/lib/camelizeStyleName.js","webpack://GUI/./node_modules/fbjs/lib/containsNode.js","webpack://GUI/./node_modules/fbjs/lib/emptyFunction.js","webpack://GUI/./node_modules/fbjs/lib/emptyObject.js","webpack://GUI/./node_modules/fbjs/lib/focusNode.js","webpack://GUI/./node_modules/fbjs/lib/getActiveElement.js","webpack://GUI/./node_modules/fbjs/lib/hyphenate.js","webpack://GUI/./node_modules/fbjs/lib/hyphenateStyleName.js","webpack://GUI/./node_modules/fbjs/lib/invariant.js","webpack://GUI/./node_modules/fbjs/lib/isNode.js","webpack://GUI/./node_modules/fbjs/lib/isTextNode.js","webpack://GUI/./node_modules/fbjs/lib/shallowEqual.js","webpack://GUI/./node_modules/fbjs/lib/warning.js","webpack://GUI/./node_modules/for-each/index.js","webpack://GUI/./node_modules/format-message-formats/index.js","webpack://GUI/./node_modules/format-message-interpret/index.js","webpack://GUI/./node_modules/format-message-interpret/plurals.js","webpack://GUI/./node_modules/format-message-parse/index.js","webpack://GUI/./node_modules/format-message/index.js","webpack://GUI/./node_modules/function-bind/implementation.js","webpack://GUI/./node_modules/function-bind/index.js","webpack://GUI/./node_modules/get-float-time-domain-data/lib/get-float-time-domain-data.js","webpack://GUI/./node_modules/get-user-media-promise/lib/get-user-media-promise.js","webpack://GUI/./node_modules/global/window.js","webpack://GUI/./node_modules/has-symbols/index.js","webpack://GUI/./node_modules/has-symbols/shams.js","webpack://GUI/./node_modules/has/src/index.js","webpack://GUI/./node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js","webpack://GUI/./node_modules/hull.js/src/convex.js","webpack://GUI/./node_modules/hull.js/src/format.js","webpack://GUI/./node_modules/hull.js/src/grid.js","webpack://GUI/./node_modules/hull.js/src/hull.js","webpack://GUI/./node_modules/hull.js/src/intersect.js","webpack://GUI/./node_modules/ieee754/index.js","webpack://GUI/./node_modules/grapheme-breaker/src/GraphemeBreaker.js","webpack://GUI/./node_modules/linebreak/src/linebreaker.js","webpack://GUI/./node_modules/immediate/lib/browser.js","webpack://GUI/./node_modules/immutable/dist/immutable.js","webpack://GUI/./node_modules/scratch-blocks/msg/messages.js","webpack://GUI/./node_modules/scratch-blocks/msg/scratch_msgs.js","webpack://GUI/./node_modules/scratch-blocks/blocks_compressed.js","webpack://GUI/./node_modules/scratch-blocks/blocks_compressed_vertical.js","webpack://GUI/./node_modules/scratch-blocks/blockly_compressed_vertical.js","webpack://GUI/./node_modules/inherits/inherits_browser.js","webpack://GUI/./node_modules/intl-format-cache/lib/index.js","webpack://GUI/./node_modules/intl-messageformat-parser/index.js","webpack://GUI/./node_modules/intl-messageformat-parser/lib/parser.js","webpack://GUI/./node_modules/intl-messageformat/index.js","webpack://GUI/./node_modules/intl-messageformat/lib/compiler.js","webpack://GUI/./node_modules/intl-messageformat/lib/core.js","webpack://GUI/./node_modules/intl-messageformat/lib/en.js","webpack://GUI/./node_modules/intl-messageformat/lib/es5.js","webpack://GUI/./node_modules/intl-messageformat/lib/main.js","webpack://GUI/./node_modules/intl-messageformat/lib/utils.js","webpack://GUI/./node_modules/intl-relativeformat/index.js","webpack://GUI/./node_modules/intl-relativeformat/lib/core.js","webpack://GUI/./node_modules/intl-relativeformat/lib/diff.js","webpack://GUI/./node_modules/intl-relativeformat/lib/en.js","webpack://GUI/./node_modules/intl-relativeformat/lib/es5.js","webpack://GUI/./node_modules/intl-relativeformat/lib/main.js","webpack://GUI/./node_modules/intl/index.js","webpack://GUI/./node_modules/intl/lib/core.js","webpack://GUI/./node_modules/invariant/browser.js","webpack://GUI/./node_modules/is-callable/index.js","webpack://GUI/./node_modules/is-function/index.js","webpack://GUI/./node_modules/is-in-browser/dist/module.js","webpack://GUI/./node_modules/isarray/index.js","webpack://GUI/./node_modules/js-base64/base64.js","webpack://GUI/./node_modules/js-md5/src/md5.js","webpack://GUI/./node_modules/jszip/lib/base64.js","webpack://GUI/./node_modules/jszip/lib/compressedObject.js","webpack://GUI/./node_modules/jszip/lib/compressions.js","webpack://GUI/./node_modules/jszip/lib/crc32.js","webpack://GUI/./node_modules/jszip/lib/defaults.js","webpack://GUI/./node_modules/jszip/lib/external.js","webpack://GUI/./node_modules/jszip/lib/flate.js","webpack://GUI/./node_modules/jszip/lib/generate/ZipFileWorker.js","webpack://GUI/./node_modules/jszip/lib/generate/index.js","webpack://GUI/./node_modules/jszip/lib/index.js","webpack://GUI/./node_modules/jszip/lib/load.js","webpack://GUI/./node_modules/jszip/lib/nodejs/NodejsStreamInputAdapter.js","webpack://GUI/./node_modules/jszip/lib/nodejs/NodejsStreamOutputAdapter.js","webpack://GUI/./node_modules/jszip/lib/nodejsUtils.js","webpack://GUI/./node_modules/jszip/lib/object.js","webpack://GUI/./node_modules/jszip/lib/readable-stream-browser.js","webpack://GUI/./node_modules/jszip/lib/reader/ArrayReader.js","webpack://GUI/./node_modules/jszip/lib/reader/DataReader.js","webpack://GUI/./node_modules/jszip/lib/reader/NodeBufferReader.js","webpack://GUI/./node_modules/jszip/lib/reader/StringReader.js","webpack://GUI/./node_modules/jszip/lib/reader/Uint8ArrayReader.js","webpack://GUI/./node_modules/jszip/lib/reader/readerFor.js","webpack://GUI/./node_modules/jszip/lib/signature.js","webpack://GUI/./node_modules/jszip/lib/stream/ConvertWorker.js","webpack://GUI/./node_modules/jszip/lib/stream/Crc32Probe.js","webpack://GUI/./node_modules/jszip/lib/stream/DataLengthProbe.js","webpack://GUI/./node_modules/jszip/lib/stream/DataWorker.js","webpack://GUI/./node_modules/jszip/lib/stream/GenericWorker.js","webpack://GUI/./node_modules/jszip/lib/stream/StreamHelper.js","webpack://GUI/./node_modules/jszip/lib/support.js","webpack://GUI/./node_modules/jszip/lib/utf8.js","webpack://GUI/./node_modules/jszip/lib/utils.js","webpack://GUI/./node_modules/jszip/lib/zipEntries.js","webpack://GUI/./node_modules/jszip/lib/zipEntry.js","webpack://GUI/./node_modules/jszip/lib/zipObject.js","webpack://GUI/./node_modules/keymirror/index.js","webpack://GUI/./node_modules/lie/lib/browser.js","webpack://GUI/./node_modules/linebreak/node_modules/base64-js/lib/b64.js","webpack://GUI/./node_modules/linebreak/src/classes.js","webpack://GUI/./node_modules/linebreak/src/pairs.js","webpack://GUI/./node_modules/lodash-es/_Symbol.js","webpack://GUI/./node_modules/lodash-es/_baseGetTag.js","webpack://GUI/./node_modules/lodash-es/_freeGlobal.js","webpack://GUI/./node_modules/lodash-es/_getPrototype.js","webpack://GUI/./node_modules/lodash-es/_getRawTag.js","webpack://GUI/./node_modules/lodash-es/_objectToString.js","webpack://GUI/./node_modules/lodash-es/_overArg.js","webpack://GUI/./node_modules/lodash-es/_root.js","webpack://GUI/./node_modules/lodash-es/isObjectLike.js","webpack://GUI/./node_modules/lodash-es/isPlainObject.js","webpack://GUI/./node_modules/lodash._getnative/index.js","webpack://GUI/./node_modules/lodash.bindall/index.js","webpack://GUI/./node_modules/lodash.debounce/index.js","webpack://GUI/./node_modules/lodash.defaultsdeep/index.js","webpack://GUI/./node_modules/lodash.omit/index.js","webpack://GUI/./node_modules/lodash.throttle/index.js","webpack://GUI/./node_modules/lookup-closest-locale/index.js","webpack://GUI/./node_modules/microee/index.js","webpack://GUI/./node_modules/minilog/lib/common/filter.js","webpack://GUI/./node_modules/minilog/lib/common/minilog.js","webpack://GUI/./node_modules/minilog/lib/common/transform.js","webpack://GUI/./node_modules/minilog/lib/web/array.js","webpack://GUI/./node_modules/minilog/lib/web/console.js","webpack://GUI/./node_modules/minilog/lib/web/formatters/color.js","webpack://GUI/./node_modules/minilog/lib/web/formatters/minilog.js","webpack://GUI/./node_modules/minilog/lib/web/formatters/util.js","webpack://GUI/./node_modules/minilog/lib/web/index.js","webpack://GUI/./node_modules/minilog/lib/web/jquery_simple.js","webpack://GUI/./node_modules/minilog/lib/web/localstorage.js","webpack://GUI/./node_modules/ms/index.js","webpack://GUI/./node_modules/nets/index.js","webpack://GUI/./node_modules/node-libs-browser/node_modules/punycode/punycode.js","webpack://GUI/./node_modules/object-assign/index.js","webpack://GUI/./node_modules/object-keys/implementation.js","webpack://GUI/./node_modules/object-keys/index.js","webpack://GUI/./node_modules/object-keys/isArguments.js","webpack://GUI/./node_modules/omggif/omggif.js","webpack://GUI/./node_modules/pako/index.js","webpack://GUI/./node_modules/pako/lib/deflate.js","webpack://GUI/./node_modules/pako/lib/inflate.js","webpack://GUI/./node_modules/pako/lib/utils/common.js","webpack://GUI/./node_modules/pako/lib/utils/strings.js","webpack://GUI/./node_modules/pako/lib/zlib/adler32.js","webpack://GUI/./node_modules/pako/lib/zlib/constants.js","webpack://GUI/./node_modules/pako/lib/zlib/crc32.js","webpack://GUI/./node_modules/pako/lib/zlib/deflate.js","webpack://GUI/./node_modules/pako/lib/zlib/gzheader.js","webpack://GUI/./node_modules/pako/lib/zlib/inffast.js","webpack://GUI/./node_modules/pako/lib/zlib/inflate.js","webpack://GUI/./node_modules/pako/lib/zlib/inftrees.js","webpack://GUI/./node_modules/pako/lib/zlib/messages.js","webpack://GUI/./node_modules/pako/lib/zlib/trees.js","webpack://GUI/./node_modules/pako/lib/zlib/zstream.js","webpack://GUI/./node_modules/papaparse/papaparse.min.js","webpack://GUI/./node_modules/parse-color/index.js","webpack://GUI/./node_modules/parse-color/node_modules/color-convert/conversions.js","webpack://GUI/./node_modules/parse-color/node_modules/color-convert/index.js","webpack://GUI/./node_modules/parse-headers/parse-headers.js","webpack://GUI/./node_modules/pify/index.js","webpack://GUI/./node_modules/process-nextick-args/index.js","webpack://GUI/./node_modules/process/browser.js","webpack://GUI/./node_modules/prop-types/checkPropTypes.js","webpack://GUI/./node_modules/prop-types/factoryWithTypeCheckers.js","webpack://GUI/./node_modules/prop-types/index.js","webpack://GUI/./node_modules/prop-types/lib/ReactPropTypesSecret.js","webpack://GUI/./node_modules/query-string/index.js","webpack://GUI/./node_modules/querystring-es3/decode.js","webpack://GUI/./node_modules/querystring-es3/encode.js","webpack://GUI/./node_modules/querystring-es3/index.js","webpack://GUI/./node_modules/scratch-render/src/shaders/sprite.frag","webpack://GUI/./node_modules/scratch-render/src/shaders/sprite.vert","webpack://GUI/./src/lib/default-project/0fb9be3e8397c983338cb71dc84d0b25.svg","webpack://GUI/./src/lib/default-project/bcf454acf82e4504149f7ffe07081dbc.svg","webpack://GUI/./src/lib/default-project/cd21514d0531fdffb22204e0ec5ed84a.svg","webpack://GUI/./node_modules/react-contextmenu/es6/AbstractMenu.js","webpack://GUI/./node_modules/react-contextmenu/es6/ContextMenu.js","webpack://GUI/./node_modules/react-contextmenu/es6/ContextMenuTrigger.js","webpack://GUI/./node_modules/react-contextmenu/es6/MenuItem.js","webpack://GUI/./node_modules/react-contextmenu/es6/SubMenu.js","webpack://GUI/./node_modules/react-contextmenu/es6/actions.js","webpack://GUI/./node_modules/react-contextmenu/es6/connectMenu.js","webpack://GUI/./node_modules/react-contextmenu/es6/globalEventListener.js","webpack://GUI/./node_modules/react-contextmenu/es6/helpers.js","webpack://GUI/./node_modules/react-contextmenu/es6/index.js","webpack://GUI/./node_modules/react-dom/cjs/react-dom.development.js","webpack://GUI/./node_modules/react-dom/index.js","webpack://GUI/./node_modules/react-draggable/dist/react-draggable.js","webpack://GUI/./node_modules/react-ga/dist/react-ga.js","webpack://GUI/./node_modules/react-intl/lib/index.es.js","webpack://GUI/./node_modules/react-intl/locale-data/af.js","webpack://GUI/./node_modules/react-intl/locale-data/am.js","webpack://GUI/./node_modules/react-intl/locale-data/ar.js","webpack://GUI/./node_modules/react-intl/locale-data/az.js","webpack://GUI/./node_modules/react-intl/locale-data/be.js","webpack://GUI/./node_modules/react-intl/locale-data/bg.js","webpack://GUI/./node_modules/react-intl/locale-data/ca.js","webpack://GUI/./node_modules/react-intl/locale-data/ckb.js","webpack://GUI/./node_modules/react-intl/locale-data/cs.js","webpack://GUI/./node_modules/react-intl/locale-data/cy.js","webpack://GUI/./node_modules/react-intl/locale-data/da.js","webpack://GUI/./node_modules/react-intl/locale-data/de.js","webpack://GUI/./node_modules/react-intl/locale-data/el.js","webpack://GUI/./node_modules/react-intl/locale-data/en.js","webpack://GUI/./node_modules/react-intl/locale-data/es.js","webpack://GUI/./node_modules/react-intl/locale-data/et.js","webpack://GUI/./node_modules/react-intl/locale-data/eu.js","webpack://GUI/./node_modules/react-intl/locale-data/fa.js","webpack://GUI/./node_modules/react-intl/locale-data/fi.js","webpack://GUI/./node_modules/react-intl/locale-data/fr.js","webpack://GUI/./node_modules/react-intl/locale-data/ga.js","webpack://GUI/./node_modules/react-intl/locale-data/gd.js","webpack://GUI/./node_modules/react-intl/locale-data/gl.js","webpack://GUI/./node_modules/react-intl/locale-data/he.js","webpack://GUI/./node_modules/react-intl/locale-data/hr.js","webpack://GUI/./node_modules/react-intl/locale-data/hu.js","webpack://GUI/./node_modules/react-intl/locale-data/hy.js","webpack://GUI/./node_modules/react-intl/locale-data/id.js","webpack://GUI/./node_modules/react-intl/locale-data/is.js","webpack://GUI/./node_modules/react-intl/locale-data/it.js","webpack://GUI/./node_modules/react-intl/locale-data/ja.js","webpack://GUI/./node_modules/react-intl/locale-data/ka.js","webpack://GUI/./node_modules/react-intl/locale-data/km.js","webpack://GUI/./node_modules/react-intl/locale-data/ko.js","webpack://GUI/./node_modules/react-intl/locale-data/ku.js","webpack://GUI/./node_modules/react-intl/locale-data/lt.js","webpack://GUI/./node_modules/react-intl/locale-data/lv.js","webpack://GUI/./node_modules/react-intl/locale-data/mi.js","webpack://GUI/./node_modules/react-intl/locale-data/mn.js","webpack://GUI/./node_modules/react-intl/locale-data/nb.js","webpack://GUI/./node_modules/react-intl/locale-data/nl.js","webpack://GUI/./node_modules/react-intl/locale-data/nn.js","webpack://GUI/./node_modules/react-intl/locale-data/nso.js","webpack://GUI/./node_modules/react-intl/locale-data/or.js","webpack://GUI/./node_modules/react-intl/locale-data/pl.js","webpack://GUI/./node_modules/react-intl/locale-data/pt.js","webpack://GUI/./node_modules/react-intl/locale-data/qu.js","webpack://GUI/./node_modules/react-intl/locale-data/ro.js","webpack://GUI/./node_modules/react-intl/locale-data/ru.js","webpack://GUI/./node_modules/react-intl/locale-data/sk.js","webpack://GUI/./node_modules/react-intl/locale-data/sl.js","webpack://GUI/./node_modules/react-intl/locale-data/sr.js","webpack://GUI/./node_modules/react-intl/locale-data/sv.js","webpack://GUI/./node_modules/react-intl/locale-data/sw.js","webpack://GUI/./node_modules/react-intl/locale-data/th.js","webpack://GUI/./node_modules/react-intl/locale-data/tn.js","webpack://GUI/./node_modules/react-intl/locale-data/tr.js","webpack://GUI/./node_modules/react-intl/locale-data/uk.js","webpack://GUI/./node_modules/react-intl/locale-data/uz.js","webpack://GUI/./node_modules/react-intl/locale-data/vi.js","webpack://GUI/./node_modules/react-intl/locale-data/xh.js","webpack://GUI/./node_modules/react-intl/locale-data/zh.js","webpack://GUI/./node_modules/react-intl/locale-data/zu.js","webpack://GUI/./node_modules/react-is/cjs/react-is.development.js","webpack://GUI/./node_modules/react-is/index.js","webpack://GUI/./node_modules/react-lifecycles-compat/react-lifecycles-compat.es.js","webpack://GUI/./node_modules/react-modal/lib/components/Modal.js","webpack://GUI/./node_modules/react-modal/lib/components/ModalPortal.js","webpack://GUI/./node_modules/react-modal/lib/helpers/ariaAppHider.js","webpack://GUI/./node_modules/react-modal/lib/helpers/classList.js","webpack://GUI/./node_modules/react-modal/lib/helpers/focusManager.js","webpack://GUI/./node_modules/react-modal/lib/helpers/safeHTMLElement.js","webpack://GUI/./node_modules/react-modal/lib/helpers/scopeTab.js","webpack://GUI/./node_modules/react-modal/lib/helpers/tabbable.js","webpack://GUI/./node_modules/react-modal/lib/index.js","webpack://GUI/./node_modules/react-popover/build/index.js","webpack://GUI/./node_modules/react-popover/build/layout.js","webpack://GUI/./node_modules/react-popover/build/on-resize.js","webpack://GUI/./node_modules/react-popover/build/platform.js","webpack://GUI/./node_modules/react-popover/build/tip.js","webpack://GUI/./node_modules/react-popover/build/utils.js","webpack://GUI/./node_modules/react-popover/index.js","webpack://GUI/./node_modules/react-popover/node_modules/lodash.debounce/index.js","webpack://GUI/./node_modules/react-popover/node_modules/lodash.throttle/index.js","webpack://GUI/./node_modules/react-redux/es/components/Provider.js","webpack://GUI/./node_modules/react-redux/es/components/connectAdvanced.js","webpack://GUI/./node_modules/react-redux/es/connect/connect.js","webpack://GUI/./node_modules/react-redux/es/connect/mapDispatchToProps.js","webpack://GUI/./node_modules/react-redux/es/connect/mapStateToProps.js","webpack://GUI/./node_modules/react-redux/es/connect/mergeProps.js","webpack://GUI/./node_modules/react-redux/es/connect/selectorFactory.js","webpack://GUI/./node_modules/react-redux/es/connect/verifySubselectors.js","webpack://GUI/./node_modules/react-redux/es/connect/wrapMapToProps.js","webpack://GUI/./node_modules/react-redux/es/index.js","webpack://GUI/./node_modules/react-redux/es/utils/PropTypes.js","webpack://GUI/./node_modules/react-redux/es/utils/Subscription.js","webpack://GUI/./node_modules/react-redux/es/utils/shallowEqual.js","webpack://GUI/./node_modules/react-redux/es/utils/verifyPlainObject.js","webpack://GUI/./node_modules/react-redux/es/utils/warning.js","webpack://GUI/./node_modules/react-redux/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js","webpack://GUI/./node_modules/react-responsive/dist/react-responsive.js","webpack://GUI/./node_modules/react-style-proptype/src/css-properties.js","webpack://GUI/./node_modules/react-style-proptype/src/index.js","webpack://GUI/./node_modules/react-tabs/esm/components/Tab.js","webpack://GUI/./node_modules/react-tabs/esm/components/TabList.js","webpack://GUI/./node_modules/react-tabs/esm/components/TabPanel.js","webpack://GUI/./node_modules/react-tabs/esm/components/Tabs.js","webpack://GUI/./node_modules/react-tabs/esm/components/UncontrolledTabs.js","webpack://GUI/./node_modules/react-tabs/esm/helpers/childrenDeepMap.js","webpack://GUI/./node_modules/react-tabs/esm/helpers/count.js","webpack://GUI/./node_modules/react-tabs/esm/helpers/elementTypes.js","webpack://GUI/./node_modules/react-tabs/esm/helpers/propTypes.js","webpack://GUI/./node_modules/react-tabs/esm/helpers/uuid.js","webpack://GUI/./node_modules/react-tabs/esm/index.js","webpack://GUI/./node_modules/react-tabs/style/react-tabs.css?f800","webpack://GUI/./node_modules/react-tooltip/dist/constant.js","webpack://GUI/./node_modules/react-tooltip/dist/decorators/customEvent.js","webpack://GUI/./node_modules/react-tooltip/dist/decorators/getEffect.js","webpack://GUI/./node_modules/react-tooltip/dist/decorators/isCapture.js","webpack://GUI/./node_modules/react-tooltip/dist/decorators/staticMethods.js","webpack://GUI/./node_modules/react-tooltip/dist/decorators/trackRemoval.js","webpack://GUI/./node_modules/react-tooltip/dist/decorators/windowListener.js","webpack://GUI/./node_modules/react-tooltip/dist/index.js","webpack://GUI/./node_modules/react-tooltip/dist/style.js","webpack://GUI/./node_modules/react-tooltip/dist/utils/aria.js","webpack://GUI/./node_modules/react-tooltip/dist/utils/getPosition.js","webpack://GUI/./node_modules/react-tooltip/dist/utils/getTipContent.js","webpack://GUI/./node_modules/react-tooltip/dist/utils/nodeListToArray.js","webpack://GUI/./node_modules/react-virtualized/dist/es/ArrowKeyStepper/ArrowKeyStepper.js","webpack://GUI/./node_modules/react-virtualized/dist/es/ArrowKeyStepper/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/ArrowKeyStepper/types.js","webpack://GUI/./node_modules/react-virtualized/dist/es/AutoSizer/AutoSizer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/AutoSizer/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/CellMeasurer/CellMeasurer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/CellMeasurer/CellMeasurerCache.js","webpack://GUI/./node_modules/react-virtualized/dist/es/CellMeasurer/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/CellMeasurer/types.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Collection/Collection.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Collection/CollectionView.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Collection/Section.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Collection/SectionManager.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Collection/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Collection/types.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Collection/utils/calculateSizeAndPositionData.js","webpack://GUI/./node_modules/react-virtualized/dist/es/ColumnSizer/ColumnSizer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/ColumnSizer/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/Grid.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/accessibilityOverscanIndicesGetter.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/defaultCellRangeRenderer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/defaultOverscanIndicesGetter.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/types.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/utils/CellSizeAndPositionManager.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/utils/ScalingCellSizeAndPositionManager.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/utils/maxElementSize.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Grid/utils/updateScrollIndexHelper.js","webpack://GUI/./node_modules/react-virtualized/dist/es/InfiniteLoader/InfiniteLoader.js","webpack://GUI/./node_modules/react-virtualized/dist/es/InfiniteLoader/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/List/List.js","webpack://GUI/./node_modules/react-virtualized/dist/es/List/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/List/types.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Masonry/Masonry.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Masonry/PositionCache.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Masonry/createCellPositioner.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Masonry/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/MultiGrid/CellMeasurerCacheDecorator.js","webpack://GUI/./node_modules/react-virtualized/dist/es/MultiGrid/MultiGrid.js","webpack://GUI/./node_modules/react-virtualized/dist/es/MultiGrid/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/ScrollSync/ScrollSync.js","webpack://GUI/./node_modules/react-virtualized/dist/es/ScrollSync/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/Column.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/SortDirection.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/SortIndicator.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/Table.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/createMultiSort.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/defaultCellDataGetter.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/defaultCellRenderer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/defaultHeaderRenderer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/defaultHeaderRowRenderer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/defaultRowRenderer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/Table/types.js","webpack://GUI/./node_modules/react-virtualized/dist/es/WindowScroller/WindowScroller.js","webpack://GUI/./node_modules/react-virtualized/dist/es/WindowScroller/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/WindowScroller/utils/dimensions.js","webpack://GUI/./node_modules/react-virtualized/dist/es/WindowScroller/utils/onScroll.js","webpack://GUI/./node_modules/react-virtualized/dist/es/index.js","webpack://GUI/./node_modules/react-virtualized/dist/es/utils/animationFrame.js","webpack://GUI/./node_modules/react-virtualized/dist/es/utils/createCallbackMemoizer.js","webpack://GUI/./node_modules/react-virtualized/dist/es/utils/getUpdatedOffsetForIndex.js","webpack://GUI/./node_modules/react-virtualized/dist/es/utils/requestAnimationTimeout.js","webpack://GUI/./node_modules/react-virtualized/dist/es/vendor/binarySearchBounds.js","webpack://GUI/./node_modules/react-virtualized/dist/es/vendor/detectElementResize.js","webpack://GUI/./node_modules/react-virtualized/dist/es/vendor/intervalTree.js","webpack://GUI/./node_modules/react/cjs/react.development.js","webpack://GUI/./node_modules/react/index.js","webpack://GUI/./node_modules/readable-stream/duplex-browser.js","webpack://GUI/./node_modules/readable-stream/lib/_stream_duplex.js","webpack://GUI/./node_modules/readable-stream/lib/_stream_passthrough.js","webpack://GUI/./node_modules/readable-stream/lib/_stream_readable.js","webpack://GUI/./node_modules/readable-stream/lib/_stream_transform.js","webpack://GUI/./node_modules/readable-stream/lib/_stream_writable.js","webpack://GUI/./node_modules/readable-stream/lib/internal/streams/BufferList.js","webpack://GUI/./node_modules/readable-stream/lib/internal/streams/destroy.js","webpack://GUI/./node_modules/readable-stream/lib/internal/streams/stream-browser.js","webpack://GUI/./node_modules/readable-stream/passthrough.js","webpack://GUI/./node_modules/readable-stream/readable-browser.js","webpack://GUI/./node_modules/readable-stream/transform.js","webpack://GUI/./node_modules/readable-stream/writable-browser.js","webpack://GUI/./node_modules/redux-throttle/build/index.js","webpack://GUI/./node_modules/redux/es/applyMiddleware.js","webpack://GUI/./node_modules/redux/es/bindActionCreators.js","webpack://GUI/./node_modules/redux/es/combineReducers.js","webpack://GUI/./node_modules/redux/es/compose.js","webpack://GUI/./node_modules/redux/es/createStore.js","webpack://GUI/./node_modules/redux/es/index.js","webpack://GUI/./node_modules/redux/es/utils/warning.js","webpack://GUI/./node_modules/safe-buffer/index.js","webpack://GUI/./node_modules/scratch-audio/src/ADPCMSoundDecoder.js","webpack://GUI/./node_modules/scratch-audio/src/ArrayBufferStream.js","webpack://GUI/./node_modules/scratch-audio/src/AudioEngine.js","webpack://GUI/./node_modules/scratch-audio/src/Loudness.js","webpack://GUI/./node_modules/scratch-audio/src/SoundBank.js","webpack://GUI/./node_modules/scratch-audio/src/SoundPlayer.js","webpack://GUI/./node_modules/scratch-audio/src/StartAudioContext.js","webpack://GUI/./node_modules/scratch-audio/src/effects/Effect.js","webpack://GUI/./node_modules/scratch-audio/src/effects/EffectChain.js","webpack://GUI/./node_modules/scratch-audio/src/effects/PanEffect.js","webpack://GUI/./node_modules/scratch-audio/src/effects/PitchEffect.js","webpack://GUI/./node_modules/scratch-audio/src/effects/VolumeEffect.js","webpack://GUI/./node_modules/scratch-audio/src/index.js","webpack://GUI/./node_modules/scratch-audio/src/log.js","webpack://GUI/./node_modules/scratch-audio/src/uid.js","webpack://GUI/./node_modules/scratch-blocks/shim/blockly_compressed_vertical-blocks_compressed.js","webpack://GUI/./node_modules/scratch-blocks/shim/blockly_compressed_vertical.Blockly.js","webpack://GUI/./node_modules/scratch-blocks/shim/blockly_compressed_vertical.goog.js","webpack://GUI/./node_modules/scratch-blocks/shim/blockly_compressed_vertical.js","webpack://GUI/./node_modules/scratch-blocks/shim/blocks_compressed_vertical-blockly_compressed_vertical-messages.js","webpack://GUI/./node_modules/scratch-blocks/shim/blocks_compressed_vertical.js","webpack://GUI/./node_modules/scratch-blocks/shim/vertical.js","webpack://GUI/./node_modules/scratch-l10n/locales/editor-msgs.js","webpack://GUI/./node_modules/scratch-l10n/src/index.js","webpack://GUI/./node_modules/scratch-l10n/src/supported-locales.js","webpack://GUI/./node_modules/scratch-paint/node_modules/classnames/index.js","webpack://GUI/./node_modules/scratch-paint/src/components/bit-brush-mode/bit-brush-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-brush-mode/brush.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-eraser-mode/bit-eraser-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-eraser-mode/eraser.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-fill-mode/bit-fill-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-fill-mode/fill.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-line-mode/bit-line-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-line-mode/line.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-oval-mode/bit-oval-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-oval-mode/oval-outlined.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-oval-mode/oval.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-rect-mode/bit-rect-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-rect-mode/rectangle-outlined.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-rect-mode/rectangle.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-select-mode/bit-select-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-select-mode/marquee.svg","webpack://GUI/./node_modules/scratch-paint/src/components/bit-text-mode/bit-text-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/bit-text-mode/text.svg","webpack://GUI/./node_modules/scratch-paint/src/components/box/box.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/brush-mode/brush-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/brush-mode/brush.svg","webpack://GUI/./node_modules/scratch-paint/src/components/button-group/button-group.css?9d8f","webpack://GUI/./node_modules/scratch-paint/src/components/button-group/button-group.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/button/button.css?2f4b","webpack://GUI/./node_modules/scratch-paint/src/components/button/button.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/color-button/color-button.css?2df0","webpack://GUI/./node_modules/scratch-paint/src/components/color-button/color-button.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/color-button/mixed-fill.svg","webpack://GUI/./node_modules/scratch-paint/src/components/color-button/no-fill.svg","webpack://GUI/./node_modules/scratch-paint/src/components/color-indicator.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/color-picker.css?b338","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/color-picker.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/icons/eye-dropper.svg","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/icons/fill-horz-gradient-enabled.svg","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/icons/fill-radial-enabled.svg","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/icons/fill-solid-enabled.svg","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/icons/fill-vert-gradient-enabled.svg","webpack://GUI/./node_modules/scratch-paint/src/components/color-picker/icons/swap.svg","webpack://GUI/./node_modules/scratch-paint/src/components/dropdown/dropdown-caret.svg","webpack://GUI/./node_modules/scratch-paint/src/components/dropdown/dropdown.css?b75a","webpack://GUI/./node_modules/scratch-paint/src/components/dropdown/dropdown.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/eraser-mode/eraser-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/eraser-mode/eraser.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fill-mode/fill-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/fill-mode/fill.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/fixed-tools.css?3f5a","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/fixed-tools.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/group.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/redo.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/send-back.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/send-backward.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/send-forward.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/send-front.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/undo.svg","webpack://GUI/./node_modules/scratch-paint/src/components/fixed-tools/icons/ungroup.svg","webpack://GUI/./node_modules/scratch-paint/src/components/font-dropdown/font-dropdown.css?9579","webpack://GUI/./node_modules/scratch-paint/src/components/font-dropdown/font-dropdown.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/forms/buffered-input-hoc.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/forms/input.css?abf3","webpack://GUI/./node_modules/scratch-paint/src/components/forms/input.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/forms/label.css?9f1f","webpack://GUI/./node_modules/scratch-paint/src/components/forms/label.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/forms/live-input-hoc.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/forms/slider.css?1de8","webpack://GUI/./node_modules/scratch-paint/src/components/forms/slider.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/input-group/input-group.css?34c7","webpack://GUI/./node_modules/scratch-paint/src/components/input-group/input-group.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/labeled-icon-button/labeled-icon-button.css?6391","webpack://GUI/./node_modules/scratch-paint/src/components/labeled-icon-button/labeled-icon-button.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/line-mode/line-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/line-mode/line.svg","webpack://GUI/./node_modules/scratch-paint/src/components/loupe/loupe.css?409e","webpack://GUI/./node_modules/scratch-paint/src/components/loupe/loupe.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/icons/copy.svg","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/icons/curved-point.svg","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/icons/delete.svg","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/icons/flip-horizontal.svg","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/icons/flip-vertical.svg","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/icons/paste.svg","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/icons/straight-point.svg","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/mode-tools.css?5266","webpack://GUI/./node_modules/scratch-paint/src/components/mode-tools/mode-tools.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/oval-mode/oval-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/oval-mode/oval.svg","webpack://GUI/./node_modules/scratch-paint/src/components/paint-editor/icons/bitmap.svg","webpack://GUI/./node_modules/scratch-paint/src/components/paint-editor/icons/zoom-in.svg","webpack://GUI/./node_modules/scratch-paint/src/components/paint-editor/icons/zoom-out.svg","webpack://GUI/./node_modules/scratch-paint/src/components/paint-editor/icons/zoom-reset.svg","webpack://GUI/./node_modules/scratch-paint/src/components/paint-editor/paint-editor.css?56ce","webpack://GUI/./node_modules/scratch-paint/src/components/paint-editor/paint-editor.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/rect-mode/rect-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/rect-mode/rectangle.svg","webpack://GUI/./node_modules/scratch-paint/src/components/reshape-mode/reshape-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/reshape-mode/reshape.svg","webpack://GUI/./node_modules/scratch-paint/src/components/scrollable-canvas/scrollable-canvas.css?3434","webpack://GUI/./node_modules/scratch-paint/src/components/scrollable-canvas/scrollable-canvas.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/select-mode/select-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/select-mode/select.svg","webpack://GUI/./node_modules/scratch-paint/src/components/stroke-width-indicator.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/text-mode/text-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/components/text-mode/text.svg","webpack://GUI/./node_modules/scratch-paint/src/components/tool-select-base/tool-select-base.css?a3f5","webpack://GUI/./node_modules/scratch-paint/src/components/tool-select-base/tool-select-base.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/bit-brush-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/bit-eraser-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/bit-fill-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/bit-line-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/bit-oval-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/bit-rect-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/bit-select-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/brush-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/color-indicator.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/color-picker.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/eraser-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/fill-color-indicator.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/fill-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/fixed-tools.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/font-dropdown.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/line-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/mode-tools.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/oval-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/paint-editor.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/paper-canvas.css?42e7","webpack://GUI/./node_modules/scratch-paint/src/containers/paper-canvas.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/rect-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/reshape-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/scrollable-canvas.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/select-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/stroke-color-indicator.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/stroke-width-indicator.jsx","webpack://GUI/./node_modules/scratch-paint/src/containers/text-mode.jsx","webpack://GUI/./node_modules/scratch-paint/src/helper/bit-tools/brush-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/bit-tools/fill-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/bit-tools/line-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/bit-tools/oval-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/bit-tools/rect-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/bit-tools/select-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/bitmap.js","webpack://GUI/./node_modules/scratch-paint/src/helper/blob-tools/blob.js","webpack://GUI/./node_modules/scratch-paint/src/helper/blob-tools/broad-brush-helper.js","webpack://GUI/./node_modules/scratch-paint/src/helper/blob-tools/segment-brush-helper.js","webpack://GUI/./node_modules/scratch-paint/src/helper/compound-path.js","webpack://GUI/./node_modules/scratch-paint/src/helper/group.js","webpack://GUI/./node_modules/scratch-paint/src/helper/guides.js","webpack://GUI/./node_modules/scratch-paint/src/helper/hover.js","webpack://GUI/./node_modules/scratch-paint/src/helper/item.js","webpack://GUI/./node_modules/scratch-paint/src/helper/layer.js","webpack://GUI/./node_modules/scratch-paint/src/helper/math.js","webpack://GUI/./node_modules/scratch-paint/src/helper/order.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/bounding-box-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/handle-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/move-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/nudge-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/point-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/reshape-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/rotate-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/scale-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/select-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection-tools/selection-box-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/selection.js","webpack://GUI/./node_modules/scratch-paint/src/helper/snapping.js","webpack://GUI/./node_modules/scratch-paint/src/helper/style-path.js","webpack://GUI/./node_modules/scratch-paint/src/helper/tools/eye-dropper.js","webpack://GUI/./node_modules/scratch-paint/src/helper/tools/fill-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/tools/oval-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/tools/rect-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/tools/text-tool.js","webpack://GUI/./node_modules/scratch-paint/src/helper/undo.js","webpack://GUI/./node_modules/scratch-paint/src/helper/view.js","webpack://GUI/./node_modules/scratch-paint/src/hocs/copy-paste-hoc.jsx","webpack://GUI/./node_modules/scratch-paint/src/hocs/keyboard-shortcuts-hoc.jsx","webpack://GUI/./node_modules/scratch-paint/src/hocs/selection-hoc.jsx","webpack://GUI/./node_modules/scratch-paint/src/hocs/undo-hoc.jsx","webpack://GUI/./node_modules/scratch-paint/src/hocs/update-image-hoc.jsx","webpack://GUI/./node_modules/scratch-paint/src/index.js","webpack://GUI/./node_modules/scratch-paint/src/lib/color-style-proptype.js","webpack://GUI/./node_modules/scratch-paint/src/lib/cursors.js","webpack://GUI/./node_modules/scratch-paint/src/lib/fonts.js","webpack://GUI/./node_modules/scratch-paint/src/lib/format.js","webpack://GUI/./node_modules/scratch-paint/src/lib/gradient-types.js","webpack://GUI/./node_modules/scratch-paint/src/lib/hide-label.js","webpack://GUI/./node_modules/scratch-paint/src/lib/layout-constants.js","webpack://GUI/./node_modules/scratch-paint/src/lib/make-color-style-reducer.js","webpack://GUI/./node_modules/scratch-paint/src/lib/messages.js","webpack://GUI/./node_modules/scratch-paint/src/lib/modes.js","webpack://GUI/./node_modules/scratch-paint/src/lib/touch-utils.js","webpack://GUI/./node_modules/scratch-paint/src/log/log.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/bit-brush-size.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/bit-eraser-size.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/brush-mode.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/clipboard.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/color-index.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/color.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/cursor.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/eraser-mode.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/eye-dropper.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/fill-bitmap-shapes.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/fill-mode-gradient-type.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/fill-mode.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/fill-style.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/font.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/format.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/hover.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/layout.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/modals.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/modes.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/scratch-paint-reducer.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/selected-items.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/stroke-style.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/stroke-width.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/text-edit-target.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/undo.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/view-bounds.js","webpack://GUI/./node_modules/scratch-paint/src/reducers/zoom-levels.js","webpack://GUI/./node_modules/scratch-parser/index.js","webpack://GUI/./node_modules/scratch-parser/lib/parse.js","webpack://GUI/./node_modules/scratch-parser/lib/unpack.js","webpack://GUI/./node_modules/scratch-parser/lib/unzip.js","webpack://GUI/./node_modules/scratch-parser/lib/validate.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/ajv.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/cache.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/async.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/error_classes.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/formats.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/index.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/resolve.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/rules.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/schema_obj.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/ucs2length.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/compile/util.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/data.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/_limit.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/_limitItems.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/_limitLength.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/_limitProperties.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/allOf.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/anyOf.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/comment.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/const.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/contains.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/custom.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/dependencies.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/enum.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/format.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/if.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/index.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/items.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/multipleOf.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/not.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/oneOf.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/pattern.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/properties.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/propertyNames.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/ref.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/required.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/uniqueItems.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/dotjs/validate.js","webpack://GUI/./node_modules/scratch-parser/node_modules/ajv/lib/keyword.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/fn/set-immediate.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_a-function.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_an-object.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_cof.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_core.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_ctx.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_descriptors.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_dom-create.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_export.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_fails.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_global.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_hide.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_html.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_ie8-dom-define.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_invoke.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_is-object.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_object-dp.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_property-desc.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_task.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/_to-primitive.js","webpack://GUI/./node_modules/scratch-parser/node_modules/core-js/library/modules/web.immediate.js","webpack://GUI/./node_modules/scratch-parser/node_modules/fast-deep-equal/index.js","webpack://GUI/./node_modules/scratch-parser/node_modules/json-schema-traverse/index.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/base64.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/compressedObject.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/compressions.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/crc32.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/defaults.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/external.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/flate.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/generate/ZipFileWorker.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/generate/index.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/index.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/load.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/nodejs/NodejsStreamInputAdapter.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/nodejs/NodejsStreamOutputAdapter.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/nodejsUtils.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/object.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/readable-stream-browser.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/reader/ArrayReader.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/reader/DataReader.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/reader/NodeBufferReader.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/reader/StringReader.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/reader/Uint8ArrayReader.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/reader/readerFor.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/signature.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/stream/ConvertWorker.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/stream/Crc32Probe.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/stream/DataLengthProbe.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/stream/DataWorker.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/stream/GenericWorker.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/stream/StreamHelper.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/support.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/utf8.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/utils.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/zipEntries.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/zipEntry.js","webpack://GUI/./node_modules/scratch-parser/node_modules/jszip/lib/zipObject.js","webpack://GUI/./node_modules/scratch-parser/node_modules/lie/lib/browser.js","webpack://GUI/./node_modules/scratch-render-fonts/src/index.js","webpack://GUI/./node_modules/scratch-render/src/BitmapSkin.js","webpack://GUI/./node_modules/scratch-render/src/Drawable.js","webpack://GUI/./node_modules/scratch-render/src/EffectTransform.js","webpack://GUI/./node_modules/scratch-render/src/PenSkin.js","webpack://GUI/./node_modules/scratch-render/src/Rectangle.js","webpack://GUI/./node_modules/scratch-render/src/RenderConstants.js","webpack://GUI/./node_modules/scratch-render/src/RenderWebGL.js","webpack://GUI/./node_modules/scratch-render/src/SVGSkin.js","webpack://GUI/./node_modules/scratch-render/src/ShaderManager.js","webpack://GUI/./node_modules/scratch-render/src/Silhouette.js","webpack://GUI/./node_modules/scratch-render/src/Skin.js","webpack://GUI/./node_modules/scratch-render/src/TextBubbleSkin.js","webpack://GUI/./node_modules/scratch-render/src/index.js","webpack://GUI/./node_modules/scratch-render/src/util/canvas-measurement-provider.js","webpack://GUI/./node_modules/scratch-render/src/util/color-conversions.js","webpack://GUI/./node_modules/scratch-render/src/util/log.js","webpack://GUI/./node_modules/scratch-render/src/util/text-wrapper.js","webpack://GUI/./node_modules/scratch-sb1-converter/index.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/adler32.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/byte-packets.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/byte-primitives.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/byte-stream.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/crc32.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/deflate-packets.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/deflate-stream.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/png-chunk-stream.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/png-file.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/png-packets.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/proxy-stream.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/squeak-image.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/squeak-sound.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/wav-file.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/coders/wav-packets.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/sb1-file-packets.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/sb1-file.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/byte-primitives.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/byte-take-iterator.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/field-iterator.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/field-object.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/fields.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/ids.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/reference-fixer.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/type-iterator.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/squeak/types.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/to-sb2/fake-zip.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/to-sb2/json-generator.js","webpack://GUI/./node_modules/scratch-sb1-converter/src/util/assert.js","webpack://GUI/./node_modules/scratch-storage/node_modules/base64-js/index.js","webpack://GUI/./node_modules/scratch-storage/src/Asset.js","webpack://GUI/./node_modules/scratch-storage/src/AssetType.js","webpack://GUI/./node_modules/scratch-storage/src/BuiltinHelper.js","webpack://GUI/./node_modules/scratch-storage/src/DataFormat.js","webpack://GUI/./node_modules/scratch-storage/src/FetchTool.js","webpack://GUI/./node_modules/scratch-storage/src/FetchWorkerTool.js","webpack://GUI/./node_modules/scratch-storage/src/Helper.js","webpack://GUI/./node_modules/scratch-storage/src/NetsTool.js","webpack://GUI/./node_modules/scratch-storage/src/ProxyTool.js","webpack://GUI/./node_modules/scratch-storage/src/ScratchStorage.js","webpack://GUI/./node_modules/scratch-storage/src/WebHelper.js","webpack://GUI/./node_modules/scratch-storage/src/index.js","webpack://GUI/./node_modules/scratch-storage/src/log.js","webpack://GUI/./node_modules/scratch-svg-renderer/node_modules/base64-js/index.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/bitmap-adapter.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/fixup-svg-string.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/font-converter.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/font-inliner.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/index.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/load-svg-string.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/serialize-svg-to-string.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/svg-element.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/svg-renderer.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/transform-applier.js","webpack://GUI/./node_modules/scratch-svg-renderer/src/util/log.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/CollectingHandler.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/FeedHandler.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/Parser.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/ProxyHandler.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/Stream.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/Tokenizer.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/WritableStream.js","webpack://GUI/./node_modules/scratch-vm/node_modules/htmlparser2/lib/index.js","webpack://GUI/./node_modules/scratch-vm/node_modules/immutable/dist/immutable.js","webpack://GUI/./node_modules/scratch-vm/src/extension-support/extension-worker.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_control.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_core_example.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_data.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_event.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_looks.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_motion.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_operators.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_procedures.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_sensing.js","webpack://GUI/./node_modules/scratch-vm/src/blocks/scratch3_sound.js","webpack://GUI/./node_modules/scratch-vm/src/dispatch/central-dispatch.js","webpack://GUI/./node_modules/scratch-vm/src/dispatch/shared-dispatch.js","webpack://GUI/./node_modules/scratch-vm/src/engine/adapter.js","webpack://GUI/./node_modules/scratch-vm/src/engine/block-utility.js","webpack://GUI/./node_modules/scratch-vm/src/engine/blocks-execute-cache.js","webpack://GUI/./node_modules/scratch-vm/src/engine/blocks-runtime-cache.js","webpack://GUI/./node_modules/scratch-vm/src/engine/blocks.js","webpack://GUI/./node_modules/scratch-vm/src/engine/comment.js","webpack://GUI/./node_modules/scratch-vm/src/engine/execute.js","webpack://GUI/./node_modules/scratch-vm/src/engine/monitor-record.js","webpack://GUI/./node_modules/scratch-vm/src/engine/mutation-adapter.js","webpack://GUI/./node_modules/scratch-vm/src/engine/profiler.js","webpack://GUI/./node_modules/scratch-vm/src/engine/runtime.js","webpack://GUI/./node_modules/scratch-vm/src/engine/scratch-blocks-constants.js","webpack://GUI/./node_modules/scratch-vm/src/engine/sequencer.js","webpack://GUI/./node_modules/scratch-vm/src/engine/stage-layering.js","webpack://GUI/./node_modules/scratch-vm/src/engine/target.js","webpack://GUI/./node_modules/scratch-vm/src/engine/thread.js","webpack://GUI/./node_modules/scratch-vm/src/engine/variable.js","webpack://GUI/./node_modules/scratch-vm/src/extension-support/argument-type.js","webpack://GUI/./node_modules/scratch-vm/src/extension-support/block-type.js","webpack://GUI/./node_modules/scratch-vm/src/extension-support/extension-manager.js","webpack://GUI/./node_modules/scratch-vm/src/extension-support/target-type.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/microbitMore/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_boost/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_ev3/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_gdx_for/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_makeymakey/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_microbit/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_music/manifest.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_pen/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_text2speech/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_translate/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_video_sensing/index.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_video_sensing/library.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_video_sensing/math.js","webpack://GUI/./node_modules/scratch-vm/src/extensions/scratch3_wedo2/index.js","webpack://GUI/./node_modules/scratch-vm/src/import/load-costume.js","webpack://GUI/./node_modules/scratch-vm/src/import/load-sound.js","webpack://GUI/./node_modules/scratch-vm/src/index.js","webpack://GUI/./node_modules/scratch-vm/src/io/ble.js","webpack://GUI/./node_modules/scratch-vm/src/io/bt.js","webpack://GUI/./node_modules/scratch-vm/src/io/clock.js","webpack://GUI/./node_modules/scratch-vm/src/io/cloud.js","webpack://GUI/./node_modules/scratch-vm/src/io/keyboard.js","webpack://GUI/./node_modules/scratch-vm/src/io/mouse.js","webpack://GUI/./node_modules/scratch-vm/src/io/mouseWheel.js","webpack://GUI/./node_modules/scratch-vm/src/io/userData.js","webpack://GUI/./node_modules/scratch-vm/src/io/video.js","webpack://GUI/./node_modules/scratch-vm/src/serialization/deserialize-assets.js","webpack://GUI/./node_modules/scratch-vm/src/serialization/sb2.js","webpack://GUI/./node_modules/scratch-vm/src/serialization/sb2_specmap.js","webpack://GUI/./node_modules/scratch-vm/src/serialization/sb3.js","webpack://GUI/./node_modules/scratch-vm/src/serialization/serialize-assets.js","webpack://GUI/./node_modules/scratch-vm/src/sprites/rendered-target.js","webpack://GUI/./node_modules/scratch-vm/src/sprites/sprite.js","webpack://GUI/./node_modules/scratch-vm/src/util/base64-util.js","webpack://GUI/./node_modules/scratch-vm/src/util/cast.js","webpack://GUI/./node_modules/scratch-vm/src/util/clone.js","webpack://GUI/./node_modules/scratch-vm/src/util/color.js","webpack://GUI/./node_modules/scratch-vm/src/util/fetch-with-timeout.js","webpack://GUI/./node_modules/scratch-vm/src/util/get-monitor-id.js","webpack://GUI/./node_modules/scratch-vm/src/util/jsonrpc.js","webpack://GUI/./node_modules/scratch-vm/src/util/log.js","webpack://GUI/./node_modules/scratch-vm/src/util/math-util.js","webpack://GUI/./node_modules/scratch-vm/src/util/maybe-format-message.js","webpack://GUI/./node_modules/scratch-vm/src/util/new-block-ids.js","webpack://GUI/./node_modules/scratch-vm/src/util/rateLimiter.js","webpack://GUI/./node_modules/scratch-vm/src/util/scratch-link-websocket.js","webpack://GUI/./node_modules/scratch-vm/src/util/string-util.js","webpack://GUI/./node_modules/scratch-vm/src/util/timer.js","webpack://GUI/./node_modules/scratch-vm/src/util/uid.js","webpack://GUI/./node_modules/scratch-vm/src/util/variable-util.js","webpack://GUI/./node_modules/scratch-vm/src/util/xml-escape.js","webpack://GUI/./node_modules/scratch-vm/src/virtual-machine.js","webpack://GUI/./node_modules/set-immediate-shim/index.js","webpack://GUI/./node_modules/setimmediate/setImmediate.js","webpack://GUI/./node_modules/startaudiocontext/StartAudioContext.js","webpack://GUI/./node_modules/stream-browserify/index.js","webpack://GUI/./node_modules/strict-uri-encode/index.js","webpack://GUI/./node_modules/string.prototype.trim/implementation.js","webpack://GUI/./node_modules/string.prototype.trim/index.js","webpack://GUI/./node_modules/string.prototype.trim/polyfill.js","webpack://GUI/./node_modules/string.prototype.trim/shim.js","webpack://GUI/./node_modules/string_decoder/lib/string_decoder.js","webpack://GUI/./node_modules/style-loader/lib/addStyles.js","webpack://GUI/./node_modules/style-loader/lib/urls.js","webpack://GUI/./node_modules/symbol-observable/es/index.js","webpack://GUI/./node_modules/symbol-observable/es/ponyfill.js","webpack://GUI/./node_modules/text-encoding/index.js","webpack://GUI/./node_modules/text-encoding/lib/encoding-indexes.js","webpack://GUI/./node_modules/text-encoding/lib/encoding.js","webpack://GUI/./node_modules/timers-browserify/main.js","webpack://GUI/./node_modules/tiny-inflate/index.js","webpack://GUI/./node_modules/to-style/index.js","webpack://GUI/./node_modules/to-style/src/cssPrefix.js","webpack://GUI/./node_modules/to-style/src/cssUnitless.js","webpack://GUI/./node_modules/to-style/src/hasOwn.js","webpack://GUI/./node_modules/to-style/src/isFunction.js","webpack://GUI/./node_modules/to-style/src/isObject.js","webpack://GUI/./node_modules/to-style/src/prefixInfo.js","webpack://GUI/./node_modules/to-style/src/prefixProperties.js","webpack://GUI/./node_modules/to-style/src/prefixer.js","webpack://GUI/./node_modules/to-style/src/stringUtils/camelize.js","webpack://GUI/./node_modules/to-style/src/stringUtils/hyphenRe.js","webpack://GUI/./node_modules/to-style/src/stringUtils/hyphenate.js","webpack://GUI/./node_modules/to-style/src/stringUtils/separate.js","webpack://GUI/./node_modules/to-style/src/stringUtils/toLowerFirst.js","webpack://GUI/./node_modules/to-style/src/stringUtils/toUpperFirst.js","webpack://GUI/./node_modules/to-style/src/toStyleObject.js","webpack://GUI/./node_modules/to-style/src/toStyleString.js","webpack://GUI/./node_modules/transformation-matrix/build-umd/transformation-matrix.min.js","webpack://GUI/./node_modules/twgl.js/dist/4.x/twgl-full.js","webpack://GUI/./node_modules/unicode-trie/index.js","webpack://GUI/./node_modules/url/url.js","webpack://GUI/./node_modules/url/util.js","webpack://GUI/./node_modules/util-deprecate/browser.js","webpack://GUI/./node_modules/warning/warning.js","webpack://GUI/./node_modules/wav-encoder/index.js","webpack://GUI/(webpack)/buildin/amd-define.js","webpack://GUI/(webpack)/buildin/amd-options.js","webpack://GUI/(webpack)/buildin/global.js","webpack://GUI/(webpack)/buildin/harmony-module.js","webpack://GUI/(webpack)/buildin/module.js","webpack://GUI/./node_modules/scratch-storage/src/FetchWorkerTool.worker.js","webpack://GUI/./node_modules/worker-loader/dist/workers/InlineWorker.js","webpack://GUI/./node_modules/xhr/index.js","webpack://GUI/./node_modules/xhr/node_modules/global/window.js","webpack://GUI/./node_modules/xtend/immutable.js","webpack://GUI/./src/components/action-menu/action-menu.css?bdb9","webpack://GUI/./src/components/action-menu/action-menu.jsx","webpack://GUI/./src/components/action-menu/icon--backdrop.svg","webpack://GUI/./src/components/action-menu/icon--camera.svg","webpack://GUI/./src/components/action-menu/icon--file-upload.svg","webpack://GUI/./src/components/action-menu/icon--paint.svg","webpack://GUI/./src/components/action-menu/icon--search.svg","webpack://GUI/./src/components/action-menu/icon--sprite.svg","webpack://GUI/./src/components/action-menu/icon--surprise.svg","webpack://GUI/./src/components/alerts/alert.css?d79a","webpack://GUI/./src/components/alerts/alert.jsx","webpack://GUI/./src/components/alerts/alerts.css?d97e","webpack://GUI/./src/components/alerts/alerts.jsx","webpack://GUI/./src/components/alerts/inline-message.css?7847","webpack://GUI/./src/components/alerts/inline-message.jsx","webpack://GUI/./src/components/asset-panel/asset-panel.css?b9e6","webpack://GUI/./src/components/asset-panel/asset-panel.jsx","webpack://GUI/./src/components/asset-panel/icon--add-backdrop-lib.svg","webpack://GUI/./src/components/asset-panel/icon--add-costume-lib.svg","webpack://GUI/./src/components/asset-panel/icon--add-sound-lib.svg","webpack://GUI/./src/components/asset-panel/icon--add-sound-record.svg","webpack://GUI/./src/components/asset-panel/icon--sound-rtl.svg","webpack://GUI/./src/components/asset-panel/icon--sound.svg","webpack://GUI/./src/components/asset-panel/selector.css?7bea","webpack://GUI/./src/components/asset-panel/selector.jsx","webpack://GUI/./src/components/asset-panel/sortable-asset.jsx","webpack://GUI/./src/components/audio-trimmer/audio-selector.jsx","webpack://GUI/./src/components/audio-trimmer/audio-trimmer.css?ece8","webpack://GUI/./src/components/audio-trimmer/audio-trimmer.jsx","webpack://GUI/./src/components/audio-trimmer/icon--handle.svg","webpack://GUI/./src/components/audio-trimmer/playhead.jsx","webpack://GUI/./src/components/audio-trimmer/selection-handle.jsx","webpack://GUI/./src/components/backpack/backpack.css?c811","webpack://GUI/./src/components/backpack/backpack.jsx","webpack://GUI/./src/components/blocks/blocks.css?cc3d","webpack://GUI/./src/components/blocks/blocks.jsx","webpack://GUI/./src/components/box/box.css?476f","webpack://GUI/./src/components/box/box.jsx","webpack://GUI/./src/components/browser-modal/browser-modal.css?e794","webpack://GUI/./src/components/browser-modal/browser-modal.jsx","webpack://GUI/./src/components/browser-modal/unsupported-browser.svg","webpack://GUI/./src/components/button/button.css?9cd9","webpack://GUI/./src/components/button/button.jsx","webpack://GUI/./src/components/camera-modal/camera-modal.css?2837","webpack://GUI/./src/components/camera-modal/camera-modal.jsx","webpack://GUI/./src/components/camera-modal/icon--back.svg","webpack://GUI/./src/components/cards/card.css?1463","webpack://GUI/./src/components/cards/cards.jsx","webpack://GUI/./src/components/cards/icon--close.svg","webpack://GUI/./src/components/cards/icon--expand.svg","webpack://GUI/./src/components/cards/icon--next.svg","webpack://GUI/./src/components/cards/icon--prev.svg","webpack://GUI/./src/components/cards/icon--shrink.svg","webpack://GUI/./src/components/close-button/close-button.css?ca11","webpack://GUI/./src/components/close-button/close-button.jsx","webpack://GUI/./src/components/close-button/icon--close-orange.svg","webpack://GUI/./src/components/close-button/icon--close.svg","webpack://GUI/./src/components/coming-soon/aww-cat.png","webpack://GUI/./src/components/coming-soon/coming-soon.css?6f03","webpack://GUI/./src/components/coming-soon/coming-soon.jsx","webpack://GUI/./src/components/coming-soon/cool-cat.png","webpack://GUI/./src/components/connection-modal/auto-scanning-step.jsx","webpack://GUI/./src/components/connection-modal/connected-step.jsx","webpack://GUI/./src/components/connection-modal/connecting-step.jsx","webpack://GUI/./src/components/connection-modal/connection-modal.css?b43b","webpack://GUI/./src/components/connection-modal/connection-modal.jsx","webpack://GUI/./src/components/connection-modal/dots.jsx","webpack://GUI/./src/components/connection-modal/error-step.jsx","webpack://GUI/./src/components/connection-modal/icons/back.svg","webpack://GUI/./src/components/connection-modal/icons/bluetooth-white.svg","webpack://GUI/./src/components/connection-modal/icons/bluetooth.svg","webpack://GUI/./src/components/connection-modal/icons/help.svg","webpack://GUI/./src/components/connection-modal/icons/refresh.svg","webpack://GUI/./src/components/connection-modal/icons/scratchlink.svg","webpack://GUI/./src/components/connection-modal/icons/searching.png","webpack://GUI/./src/components/connection-modal/peripheral-tile.jsx","webpack://GUI/./src/components/connection-modal/scanning-step.jsx","webpack://GUI/./src/components/connection-modal/unavailable-step.jsx","webpack://GUI/./src/components/context-menu/context-menu.css?e836","webpack://GUI/./src/components/context-menu/context-menu.jsx","webpack://GUI/./src/components/controls/controls.css?e8e1","webpack://GUI/./src/components/controls/controls.jsx","webpack://GUI/./src/components/crash-message/crash-message.css?bf1f","webpack://GUI/./src/components/crash-message/crash-message.jsx","webpack://GUI/./src/components/crash-message/reload.svg","webpack://GUI/./src/components/custom-procedures/custom-procedures.css?7a6f","webpack://GUI/./src/components/custom-procedures/custom-procedures.jsx","webpack://GUI/./src/components/custom-procedures/icon--boolean-input.svg","webpack://GUI/./src/components/custom-procedures/icon--label.svg","webpack://GUI/./src/components/custom-procedures/icon--text-input.svg","webpack://GUI/./src/components/delete-button/delete-button.css?d6f1","webpack://GUI/./src/components/delete-button/delete-button.jsx","webpack://GUI/./src/components/delete-button/icon--delete.svg","webpack://GUI/./src/components/direction-picker/dial.css?1d90","webpack://GUI/./src/components/direction-picker/dial.jsx","webpack://GUI/./src/components/direction-picker/direction-picker.css?36be","webpack://GUI/./src/components/direction-picker/direction-picker.jsx","webpack://GUI/./src/components/direction-picker/icon--all-around.svg","webpack://GUI/./src/components/direction-picker/icon--dial.svg","webpack://GUI/./src/components/direction-picker/icon--dont-rotate.svg","webpack://GUI/./src/components/direction-picker/icon--handle.svg","webpack://GUI/./src/components/direction-picker/icon--left-right.svg","webpack://GUI/./src/components/divider/divider.css?a745","webpack://GUI/./src/components/divider/divider.jsx","webpack://GUI/./src/components/drag-layer/drag-layer.css?3de6","webpack://GUI/./src/components/drag-layer/drag-layer.jsx","webpack://GUI/./src/components/filter/filter.css?78e6","webpack://GUI/./src/components/filter/filter.jsx","webpack://GUI/./src/components/filter/icon--filter.svg","webpack://GUI/./src/components/filter/icon--x.svg","webpack://GUI/./src/components/forms/buffered-input-hoc.jsx","webpack://GUI/./src/components/forms/input.css?97cf","webpack://GUI/./src/components/forms/input.jsx","webpack://GUI/./src/components/forms/label.css?d07d","webpack://GUI/./src/components/forms/label.jsx","webpack://GUI/./src/components/green-flag/green-flag.css?edb5","webpack://GUI/./src/components/green-flag/green-flag.jsx","webpack://GUI/./src/components/green-flag/icon--green-flag.svg","webpack://GUI/./src/components/gui/gui.css?7e88","webpack://GUI/./src/components/gui/gui.jsx","webpack://GUI/./src/components/gui/icon--code.svg","webpack://GUI/./src/components/gui/icon--costumes.svg","webpack://GUI/./src/components/gui/icon--extensions.svg","webpack://GUI/./src/components/gui/icon--sounds.svg","webpack://GUI/./src/components/icon-button/icon-button.css?df91","webpack://GUI/./src/components/icon-button/icon-button.jsx","webpack://GUI/./src/components/language-selector/language-icon.svg","webpack://GUI/./src/components/language-selector/language-selector.css?a1e1","webpack://GUI/./src/components/language-selector/language-selector.jsx","webpack://GUI/./src/components/library-item/bluetooth.svg","webpack://GUI/./src/components/library-item/internet-connection.svg","webpack://GUI/./src/components/library-item/lib-icon--sound-rtl.svg","webpack://GUI/./src/components/library-item/lib-icon--sound.svg","webpack://GUI/./src/components/library-item/library-item.css?f982","webpack://GUI/./src/components/library-item/library-item.jsx","webpack://GUI/./src/components/library/library.css?a5aa","webpack://GUI/./src/components/library/library.jsx","webpack://GUI/./src/components/loader/bottom-block.svg","webpack://GUI/./src/components/loader/loader.css?b840","webpack://GUI/./src/components/loader/loader.jsx","webpack://GUI/./src/components/loader/middle-block.svg","webpack://GUI/./src/components/loader/top-block.svg","webpack://GUI/./src/components/loupe/loupe.css?75a2","webpack://GUI/./src/components/loupe/loupe.jsx","webpack://GUI/./src/components/menu-bar/account-nav.css?d5e9","webpack://GUI/./src/components/menu-bar/account-nav.jsx","webpack://GUI/./src/components/menu-bar/author-info.css?d741","webpack://GUI/./src/components/menu-bar/author-info.jsx","webpack://GUI/./src/components/menu-bar/community-button.css?1901","webpack://GUI/./src/components/menu-bar/community-button.jsx","webpack://GUI/./src/components/menu-bar/dropdown-caret.svg","webpack://GUI/./src/components/menu-bar/icon--about.svg","webpack://GUI/./src/components/menu-bar/icon--mystuff.png","webpack://GUI/./src/components/menu-bar/icon--profile.png","webpack://GUI/./src/components/menu-bar/icon--remix.svg","webpack://GUI/./src/components/menu-bar/icon--see-community.svg","webpack://GUI/./src/components/menu-bar/login-dropdown.css?63f7","webpack://GUI/./src/components/menu-bar/login-dropdown.jsx","webpack://GUI/./src/components/menu-bar/menu-bar-menu.jsx","webpack://GUI/./src/components/menu-bar/menu-bar.css?6d88","webpack://GUI/./src/components/menu-bar/menu-bar.jsx","webpack://GUI/./src/components/menu-bar/project-title-input.css?deeb","webpack://GUI/./src/components/menu-bar/project-title-input.jsx","webpack://GUI/./src/components/menu-bar/save-status.css?2a69","webpack://GUI/./src/components/menu-bar/save-status.jsx","webpack://GUI/./src/components/menu-bar/scratch-logo.svg","webpack://GUI/./src/components/menu-bar/share-button.css?024b","webpack://GUI/./src/components/menu-bar/share-button.jsx","webpack://GUI/./src/components/menu-bar/user-avatar.css?daa7","webpack://GUI/./src/components/menu-bar/user-avatar.jsx","webpack://GUI/./src/components/menu/menu.css?fded","webpack://GUI/./src/components/menu/menu.jsx","webpack://GUI/./src/components/meter/meter.css?8842","webpack://GUI/./src/components/meter/meter.jsx","webpack://GUI/./src/components/mic-indicator/mic-indicator.css?601e","webpack://GUI/./src/components/mic-indicator/mic-indicator.jsx","webpack://GUI/./src/components/mic-indicator/mic-indicator.svg","webpack://GUI/./src/components/modal/modal.css?6c48","webpack://GUI/./src/components/modal/modal.jsx","webpack://GUI/./src/components/monitor-list/monitor-list.css?51ed","webpack://GUI/./src/components/monitor-list/monitor-list.jsx","webpack://GUI/./src/components/monitor/default-monitor.jsx","webpack://GUI/./src/components/monitor/large-monitor.jsx","webpack://GUI/./src/components/monitor/list-monitor-scroller.jsx","webpack://GUI/./src/components/monitor/list-monitor.jsx","webpack://GUI/./src/components/monitor/monitor.css?e42e","webpack://GUI/./src/components/monitor/monitor.jsx","webpack://GUI/./src/components/monitor/slider-monitor.jsx","webpack://GUI/./src/components/play-button/icon--play.svg","webpack://GUI/./src/components/play-button/icon--stop.svg","webpack://GUI/./src/components/play-button/play-button.css?3af6","webpack://GUI/./src/components/play-button/play-button.jsx","webpack://GUI/./src/components/prompt/prompt.css?f64c","webpack://GUI/./src/components/prompt/prompt.jsx","webpack://GUI/./src/components/question/icon--enter.svg","webpack://GUI/./src/components/question/question.css?6320","webpack://GUI/./src/components/question/question.jsx","webpack://GUI/./src/components/record-modal/icon--back.svg","webpack://GUI/./src/components/record-modal/icon--play.svg","webpack://GUI/./src/components/record-modal/icon--stop-playback.svg","webpack://GUI/./src/components/record-modal/icon--stop-recording.svg","webpack://GUI/./src/components/record-modal/playback-step.jsx","webpack://GUI/./src/components/record-modal/record-modal.css?ac67","webpack://GUI/./src/components/record-modal/record-modal.jsx","webpack://GUI/./src/components/record-modal/recording-step.jsx","webpack://GUI/./src/components/slider-prompt/slider-prompt.css?1e1e","webpack://GUI/./src/components/slider-prompt/slider-prompt.jsx","webpack://GUI/./src/components/sound-editor/icon--copy-to-new.svg","webpack://GUI/./src/components/sound-editor/icon--copy.svg","webpack://GUI/./src/components/sound-editor/icon--delete.svg","webpack://GUI/./src/components/sound-editor/icon--fade-in.svg","webpack://GUI/./src/components/sound-editor/icon--fade-out.svg","webpack://GUI/./src/components/sound-editor/icon--faster.svg","webpack://GUI/./src/components/sound-editor/icon--louder.svg","webpack://GUI/./src/components/sound-editor/icon--mute.svg","webpack://GUI/./src/components/sound-editor/icon--paste.svg","webpack://GUI/./src/components/sound-editor/icon--play.svg","webpack://GUI/./src/components/sound-editor/icon--redo.svg","webpack://GUI/./src/components/sound-editor/icon--reverse.svg","webpack://GUI/./src/components/sound-editor/icon--robot.svg","webpack://GUI/./src/components/sound-editor/icon--slower.svg","webpack://GUI/./src/components/sound-editor/icon--softer.svg","webpack://GUI/./src/components/sound-editor/icon--stop.svg","webpack://GUI/./src/components/sound-editor/icon--undo.svg","webpack://GUI/./src/components/sound-editor/sound-editor.css?5a86","webpack://GUI/./src/components/sound-editor/sound-editor.jsx","webpack://GUI/./src/components/spinner/spinner.css?1a2a","webpack://GUI/./src/components/spinner/spinner.jsx","webpack://GUI/./src/components/sprite-info/icon--hide.svg","webpack://GUI/./src/components/sprite-info/icon--show.svg","webpack://GUI/./src/components/sprite-info/icon--x.svg","webpack://GUI/./src/components/sprite-info/icon--y.svg","webpack://GUI/./src/components/sprite-info/sprite-info.css?cb1e","webpack://GUI/./src/components/sprite-info/sprite-info.jsx","webpack://GUI/./src/components/sprite-selector-item/sprite-selector-item.css?eba5","webpack://GUI/./src/components/sprite-selector-item/sprite-selector-item.jsx","webpack://GUI/./src/components/sprite-selector/sprite-list.jsx","webpack://GUI/./src/components/sprite-selector/sprite-selector.css?2d9e","webpack://GUI/./src/components/sprite-selector/sprite-selector.jsx","webpack://GUI/./src/components/stage-header/icon--fullscreen.svg","webpack://GUI/./src/components/stage-header/icon--large-stage.svg","webpack://GUI/./src/components/stage-header/icon--small-stage.svg","webpack://GUI/./src/components/stage-header/icon--unfullscreen.svg","webpack://GUI/./src/components/stage-header/stage-header.css?0f2f","webpack://GUI/./src/components/stage-header/stage-header.jsx","webpack://GUI/./src/components/stage-selector/stage-selector.css?eaeb","webpack://GUI/./src/components/stage-selector/stage-selector.jsx","webpack://GUI/./src/components/stage-wrapper/stage-wrapper.css?c1cb","webpack://GUI/./src/components/stage-wrapper/stage-wrapper.jsx","webpack://GUI/./src/components/stage/stage.css?84f0","webpack://GUI/./src/components/stage/stage.jsx","webpack://GUI/./src/components/stop-all/icon--stop-all.svg","webpack://GUI/./src/components/stop-all/stop-all.css?457f","webpack://GUI/./src/components/stop-all/stop-all.jsx","webpack://GUI/./src/components/tag-button/tag-button.css?c590","webpack://GUI/./src/components/tag-button/tag-button.jsx","webpack://GUI/./src/components/target-pane/target-pane.css?1197","webpack://GUI/./src/components/target-pane/target-pane.jsx","webpack://GUI/./src/components/telemetry-modal/telemetry-modal-header.png","webpack://GUI/./src/components/telemetry-modal/telemetry-modal.css?efde","webpack://GUI/./src/components/telemetry-modal/telemetry-modal.jsx","webpack://GUI/./src/components/turbo-mode/icon--turbo.svg","webpack://GUI/./src/components/turbo-mode/turbo-mode.css?82a6","webpack://GUI/./src/components/turbo-mode/turbo-mode.jsx","webpack://GUI/./src/components/watermark/watermark.css?5339","webpack://GUI/./src/components/watermark/watermark.jsx","webpack://GUI/./src/components/waveform/waveform.css?c57a","webpack://GUI/./src/components/waveform/waveform.jsx","webpack://GUI/./src/components/webgl-modal/unsupported.png","webpack://GUI/./src/components/webgl-modal/webgl-modal.css?2fac","webpack://GUI/./src/components/webgl-modal/webgl-modal.jsx","webpack://GUI/./src/containers/account-nav.jsx","webpack://GUI/./src/containers/alert.jsx","webpack://GUI/./src/containers/alerts.jsx","webpack://GUI/./src/containers/audio-selector.jsx","webpack://GUI/./src/containers/audio-trimmer.jsx","webpack://GUI/./src/containers/auto-scanning-step.jsx","webpack://GUI/./src/containers/backdrop-library.jsx","webpack://GUI/./src/containers/backpack.jsx","webpack://GUI/./src/containers/blocks.jsx","webpack://GUI/./src/containers/camera-modal.jsx","webpack://GUI/./src/containers/cards.jsx","webpack://GUI/./src/containers/connection-modal.jsx","webpack://GUI/./src/containers/controls.jsx","webpack://GUI/./src/containers/costume-library.jsx","webpack://GUI/./src/containers/costume-tab.jsx","webpack://GUI/./src/containers/custom-procedures.jsx","webpack://GUI/./src/containers/deletion-restorer.jsx","webpack://GUI/./src/containers/direction-picker.jsx","webpack://GUI/./src/containers/dom-element-renderer.jsx","webpack://GUI/./src/containers/drag-layer.jsx","webpack://GUI/./src/containers/error-boundary.jsx","webpack://GUI/./src/containers/extension-library.jsx","webpack://GUI/./src/containers/green-flag-overlay.jsx","webpack://GUI/./src/containers/gui.jsx","webpack://GUI/./src/containers/inline-messages.jsx","webpack://GUI/./src/containers/language-selector.jsx","webpack://GUI/./src/containers/library-item.jsx","webpack://GUI/./src/containers/list-monitor.jsx","webpack://GUI/./src/containers/menu-bar-hoc.jsx","webpack://GUI/./src/containers/menu-item.jsx","webpack://GUI/./src/containers/menu.jsx","webpack://GUI/./src/containers/modal.jsx","webpack://GUI/./src/containers/monitor-list.jsx","webpack://GUI/./src/containers/monitor.jsx","webpack://GUI/./src/containers/paint-editor-wrapper.jsx","webpack://GUI/./src/containers/play-button.jsx","webpack://GUI/./src/containers/playback-step.jsx","webpack://GUI/./src/containers/project-watcher.jsx","webpack://GUI/./src/containers/prompt.jsx","webpack://GUI/./src/containers/question.jsx","webpack://GUI/./src/containers/record-modal.jsx","webpack://GUI/./src/containers/recording-step.jsx","webpack://GUI/./src/containers/sb3-downloader.jsx","webpack://GUI/./src/containers/scanning-step.jsx","webpack://GUI/./src/containers/slider-monitor.jsx","webpack://GUI/./src/containers/slider-prompt.jsx","webpack://GUI/./src/containers/sound-editor.jsx","webpack://GUI/./src/containers/sound-library.jsx","webpack://GUI/./src/containers/sound-tab.jsx","webpack://GUI/./src/containers/sprite-info.jsx","webpack://GUI/./src/containers/sprite-library.jsx","webpack://GUI/./src/containers/sprite-selector-item.jsx","webpack://GUI/./src/containers/stage-header.jsx","webpack://GUI/./src/containers/stage-selector.jsx","webpack://GUI/./src/containers/stage-wrapper.jsx","webpack://GUI/./src/containers/stage.jsx","webpack://GUI/./src/containers/tag-button.jsx","webpack://GUI/./src/containers/target-highlight.jsx","webpack://GUI/./src/containers/target-pane.jsx","webpack://GUI/./src/containers/tips-library.jsx","webpack://GUI/./src/containers/turbo-mode.jsx","webpack://GUI/./src/containers/watermark.jsx","webpack://GUI/./src/containers/webgl-modal.jsx","webpack://GUI/./src/lib/alerts/index.jsx","webpack://GUI/./src/lib/analytics.js","webpack://GUI/./src/lib/app-state-hoc.jsx","webpack://GUI/./src/lib/assets/icon--back.svg","webpack://GUI/./src/lib/assets/icon--help.svg","webpack://GUI/./src/lib/assets/icon--success.svg","webpack://GUI/./src/lib/assets/icon--tutorials.svg","webpack://GUI/./src/lib/async-modal.jsx","webpack://GUI/./src/lib/audio/audio-buffer-player.js","webpack://GUI/./src/lib/audio/audio-effects.js","webpack://GUI/./src/lib/audio/audio-recorder.js","webpack://GUI/./src/lib/audio/audio-util.js","webpack://GUI/./src/lib/audio/effects/echo-effect.js","webpack://GUI/./src/lib/audio/effects/fade-effect.js","webpack://GUI/./src/lib/audio/effects/mute-effect.js","webpack://GUI/./src/lib/audio/effects/robot-effect.js","webpack://GUI/./src/lib/audio/effects/volume-effect.js","webpack://GUI/./src/lib/audio/shared-audio-context.js","webpack://GUI/./src/lib/backpack-api.js","webpack://GUI/./src/lib/backpack/block-to-image.js","webpack://GUI/./src/lib/backpack/code-payload.js","webpack://GUI/./src/lib/backpack/costume-payload.js","webpack://GUI/./src/lib/backpack/jpeg-thumbnail.js","webpack://GUI/./src/lib/backpack/sound-payload.js","webpack://GUI/./src/lib/backpack/sprite-payload.js","webpack://GUI/./src/lib/blocks.js","webpack://GUI/./src/lib/bmp-converter.js","webpack://GUI/./src/lib/cloud-manager-hoc.jsx","webpack://GUI/./src/lib/cloud-provider.js","webpack://GUI/./src/lib/collect-metadata.js","webpack://GUI/./src/lib/connected-intl-provider.jsx","webpack://GUI/./src/lib/data-uri-to-blob.js","webpack://GUI/./src/lib/default-project/index.js","webpack://GUI/./src/lib/default-project/project-data.js","webpack://GUI/./src/lib/define-dynamic-block.js","webpack://GUI/./src/lib/detect-locale.js","webpack://GUI/./src/lib/download-blob.js","webpack://GUI/./src/lib/drag-constants.js","webpack://GUI/./src/lib/drag-recognizer.js","webpack://GUI/./src/lib/drag-utils.js","webpack://GUI/./src/lib/drop-area-hoc.jsx","webpack://GUI/./src/lib/empty-assets.js","webpack://GUI/./src/lib/error-boundary-hoc.jsx","webpack://GUI/./src/lib/file-uploader.js","webpack://GUI/./src/lib/font-loader-hoc.jsx","webpack://GUI/./src/lib/get-costume-url.js","webpack://GUI/./src/lib/gif-decoder.js","webpack://GUI/./src/lib/hash-parser-hoc.jsx","webpack://GUI/./src/lib/import-csv.js","webpack://GUI/./src/lib/isScratchDesktop.js","webpack://GUI/./src/lib/layout-constants.js","webpack://GUI/./src/lib/libraries/backdrop-tags.js","webpack://GUI/./src/lib/libraries/decks/en-steps.js","webpack://GUI/./src/lib/libraries/decks/index.jsx","webpack://GUI/./src/lib/libraries/decks/steps/add-backdrop.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/add-effects.en.png","webpack://GUI/./src/lib/libraries/decks/steps/add-sprite.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/add-variable.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/animate-char-add-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/animate-char-change-color.en.png","webpack://GUI/./src/lib/libraries/decks/steps/animate-char-jump.en.png","webpack://GUI/./src/lib/libraries/decks/steps/animate-char-move.en.png","webpack://GUI/./src/lib/libraries/decks/steps/animate-char-pick-sprite.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/animate-char-say-something.en.png","webpack://GUI/./src/lib/libraries/decks/steps/animate-char-talk.en.png","webpack://GUI/./src/lib/libraries/decks/steps/change-size.en.png","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-add-backdrop.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-add-sprite1.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-add-sprite2.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-change-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-move-randomly.en.png","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-play-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-right-left.en.png","webpack://GUI/./src/lib/libraries/decks/steps/chase-game-up-down.en.png","webpack://GUI/./src/lib/libraries/decks/steps/cn-backdrop.en.png","webpack://GUI/./src/lib/libraries/decks/steps/cn-collect.en.png","webpack://GUI/./src/lib/libraries/decks/steps/cn-glide.en.png","webpack://GUI/./src/lib/libraries/decks/steps/cn-pick-sprite.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/cn-say.en.png","webpack://GUI/./src/lib/libraries/decks/steps/cn-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/cn-show-character.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-01-say-something.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-02-animate.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-03-select-different-character.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-04-use-minus-sign.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-05-grow-shrink.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-06-select-another-different-character.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-07-jump.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-08-change-scenes.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-09-glide-around.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-10-change-costumes.en.png","webpack://GUI/./src/lib/libraries/decks/steps/code-cartoon-11-choose-more-characters.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-choose-backdrop.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/fly-choose-character.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-choose-scenery.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/fly-flying-heart.en.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-keep-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-make-interactive.en.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-move-scenery.en.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-object-to-collect.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-say-something.en.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-select-flyer.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/fly-switch-costume.en.png","webpack://GUI/./src/lib/libraries/decks/steps/glide-around-back-and-forth.en.png","webpack://GUI/./src/lib/libraries/decks/steps/glide-around-point.en.png","webpack://GUI/./src/lib/libraries/decks/steps/hide-show.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-change-costumes.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-choose-another-backdrop.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-choose-another-sprite.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-choose-any-sprite.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-choose-backdrop.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-choose-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-click-green-flag.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-fly-around.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-glide-to-point.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-grow-shrink.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-left-right.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-record-a-sound.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/imagine-switch-backdrops.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-type-what-you-want.en.png","webpack://GUI/./src/lib/libraries/decks/steps/imagine-up-down.en.png","webpack://GUI/./src/lib/libraries/decks/steps/intro-1-move.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/intro-2-say.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/intro-3-green-flag.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/move-arrow-keys-left-right.en.png","webpack://GUI/./src/lib/libraries/decks/steps/move-arrow-keys-up-down.en.png","webpack://GUI/./src/lib/libraries/decks/steps/music-make-beat.en.png","webpack://GUI/./src/lib/libraries/decks/steps/music-make-beatbox.en.png","webpack://GUI/./src/lib/libraries/decks/steps/music-make-song.en.png","webpack://GUI/./src/lib/libraries/decks/steps/music-pick-instrument.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/music-play-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/name-change-color.en.png","webpack://GUI/./src/lib/libraries/decks/steps/name-grow.en.png","webpack://GUI/./src/lib/libraries/decks/steps/name-pick-letter.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/name-pick-letter2.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/name-play-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/name-spin.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pick-backdrop.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/pong-add-a-paddle.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/pong-add-backdrop.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-add-ball-sprite.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-add-code-to-ball.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-add-line.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/pong-bounce-around.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-choose-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-game-over.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-insert-change-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-move-the-paddle.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-reset-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pong-select-ball.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/pop-game-change-color.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pop-game-change-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pop-game-pick-sprite.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/pop-game-play-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pop-game-random-position.en.png","webpack://GUI/./src/lib/libraries/decks/steps/pop-game-reset-score.en.png","webpack://GUI/./src/lib/libraries/decks/steps/record-a-sound-choose-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/record-a-sound-click-record.en.png","webpack://GUI/./src/lib/libraries/decks/steps/record-a-sound-play-your-sound.en.png","webpack://GUI/./src/lib/libraries/decks/steps/record-a-sound-press-record-button.en.png","webpack://GUI/./src/lib/libraries/decks/steps/record-a-sound-sounds-tab.en.png","webpack://GUI/./src/lib/libraries/decks/steps/speech-add-extension.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/speech-add-sprite.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/speech-change-color.en.png","webpack://GUI/./src/lib/libraries/decks/steps/speech-grow-shrink.en.png","webpack://GUI/./src/lib/libraries/decks/steps/speech-move-around.en.png","webpack://GUI/./src/lib/libraries/decks/steps/speech-say-something.en.png","webpack://GUI/./src/lib/libraries/decks/steps/speech-set-voice.en.png","webpack://GUI/./src/lib/libraries/decks/steps/speech-song.en.png","webpack://GUI/./src/lib/libraries/decks/steps/speech-spin.en.png","webpack://GUI/./src/lib/libraries/decks/steps/spin-point-in-direction.en.png","webpack://GUI/./src/lib/libraries/decks/steps/spin-turn.en.png","webpack://GUI/./src/lib/libraries/decks/steps/story-conversation.en.png","webpack://GUI/./src/lib/libraries/decks/steps/story-flip.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/story-hide-character.en.png","webpack://GUI/./src/lib/libraries/decks/steps/story-pick-backdrop.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/story-pick-backdrop2.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/story-pick-sprite.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/story-pick-sprite2.LTR.gif","webpack://GUI/./src/lib/libraries/decks/steps/story-say-something.en.png","webpack://GUI/./src/lib/libraries/decks/steps/story-show-character.en.png","webpack://GUI/./src/lib/libraries/decks/steps/story-switch-backdrop.en.png","webpack://GUI/./src/lib/libraries/decks/steps/switch-costumes.en.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-10-choose-third-backdrop.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-11-choose-sound.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/talking-12-dance-moves.en.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-13-ask-and-answer.en.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-2-choose-sprite.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-3-say-something.en.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-4-choose-backdrop.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-5-switch-backdrop.en.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-6-choose-another-sprite.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-7-move-around.en.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-8-choose-another-backdrop.LTR.png","webpack://GUI/./src/lib/libraries/decks/steps/talking-9-animate.en.png","webpack://GUI/./src/lib/libraries/decks/steps/video-add-extension.en.gif","webpack://GUI/./src/lib/libraries/decks/steps/video-animate.en.png","webpack://GUI/./src/lib/libraries/decks/steps/video-pet.en.png","webpack://GUI/./src/lib/libraries/decks/steps/video-pop.en.png","webpack://GUI/./src/lib/libraries/decks/thumbnails/add-backdrop.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/add-effects.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/add-sprite.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/animate-a-character.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/animate-a-name.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/animate-sprite.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/cartoon-network.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/change-size.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/chase-game.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/code-a-cartoon.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/getting-started.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/glide-around.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/hide-and-show.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/imagine.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/make-it-fly.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/make-music.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/move-arrow-keys.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/pong.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/pop-game.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/record-a-sound.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/spin.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/talking.png","webpack://GUI/./src/lib/libraries/decks/thumbnails/tell-a-story.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/text-to-speech.jpg","webpack://GUI/./src/lib/libraries/decks/thumbnails/video-sensing.jpg","webpack://GUI/./src/lib/libraries/decks/translate-image.js","webpack://GUI/./src/lib/libraries/decks/translate-video.js","webpack://GUI/./src/lib/libraries/extensions/boost/boost-button-illustration.svg","webpack://GUI/./src/lib/libraries/extensions/boost/boost-illustration.svg","webpack://GUI/./src/lib/libraries/extensions/boost/boost-small.svg","webpack://GUI/./src/lib/libraries/extensions/boost/boost.png","webpack://GUI/./src/lib/libraries/extensions/ev3/ev3-hub-illustration.svg","webpack://GUI/./src/lib/libraries/extensions/ev3/ev3-small.svg","webpack://GUI/./src/lib/libraries/extensions/ev3/ev3.png","webpack://GUI/./src/lib/libraries/extensions/extensionLoader/extensionLoader-small.svg","webpack://GUI/./src/lib/libraries/extensions/extensionLoader/extensionLoader.png","webpack://GUI/./src/lib/libraries/extensions/extensionLoader/index.jsx","webpack://GUI/./src/lib/libraries/extensions/gdxfor/gdxfor-illustration.svg","webpack://GUI/./src/lib/libraries/extensions/gdxfor/gdxfor-small.svg","webpack://GUI/./src/lib/libraries/extensions/gdxfor/gdxfor.png","webpack://GUI/./src/lib/libraries/extensions/index.jsx","webpack://GUI/./src/lib/libraries/extensions/makeymakey/makeymakey-small.svg","webpack://GUI/./src/lib/libraries/extensions/makeymakey/makeymakey.png","webpack://GUI/./src/lib/libraries/extensions/microbit/microbit-illustration.svg","webpack://GUI/./src/lib/libraries/extensions/microbit/microbit-small.svg","webpack://GUI/./src/lib/libraries/extensions/microbit/microbit.png","webpack://GUI/./src/lib/libraries/extensions/microbitMore/connection-icon.svg","webpack://GUI/./src/lib/libraries/extensions/microbitMore/connection-small-icon.svg","webpack://GUI/./src/lib/libraries/extensions/microbitMore/entry-icon.png","webpack://GUI/./src/lib/libraries/extensions/microbitMore/index.jsx","webpack://GUI/./src/lib/libraries/extensions/microbitMore/inset-icon.svg","webpack://GUI/./src/lib/libraries/extensions/music/music-small.svg","webpack://GUI/./src/lib/libraries/extensions/music/music.png","webpack://GUI/./src/lib/libraries/extensions/pen/pen-small.svg","webpack://GUI/./src/lib/libraries/extensions/pen/pen.png","webpack://GUI/./src/lib/libraries/extensions/preInstall/index.js","webpack://GUI/./src/lib/libraries/extensions/text2speech/text2speech-small.svg","webpack://GUI/./src/lib/libraries/extensions/text2speech/text2speech.png","webpack://GUI/./src/lib/libraries/extensions/translate/translate-small.png","webpack://GUI/./src/lib/libraries/extensions/translate/translate.png","webpack://GUI/./src/lib/libraries/extensions/videoSensing/video-sensing-small.svg","webpack://GUI/./src/lib/libraries/extensions/videoSensing/video-sensing.png","webpack://GUI/./src/lib/libraries/extensions/wedo2/wedo-button-illustration.svg","webpack://GUI/./src/lib/libraries/extensions/wedo2/wedo-illustration.svg","webpack://GUI/./src/lib/libraries/extensions/wedo2/wedo-small.svg","webpack://GUI/./src/lib/libraries/extensions/wedo2/wedo.png","webpack://GUI/./src/lib/libraries/sound-tags.js","webpack://GUI/./src/lib/libraries/sprite-tags.js","webpack://GUI/./src/lib/libraries/tag-messages.js","webpack://GUI/./src/lib/libraries/tutorial-tags.js","webpack://GUI/./src/lib/locale-utils.js","webpack://GUI/./src/lib/localization-hoc.jsx","webpack://GUI/./src/lib/log.js","webpack://GUI/./src/lib/make-toolbox-xml.js","webpack://GUI/./src/lib/monitor-adapter.js","webpack://GUI/./src/lib/opcode-labels.js","webpack://GUI/./src/lib/project-fetcher-hoc.jsx","webpack://GUI/./src/lib/project-saver-hoc.jsx","webpack://GUI/./src/lib/query-parser-hoc.jsx","webpack://GUI/./src/lib/randomize-sprite-position.js","webpack://GUI/./src/lib/save-project-to-server.js","webpack://GUI/./src/lib/sb-file-uploader-hoc.jsx","webpack://GUI/./src/lib/screen-utils.js","webpack://GUI/./src/lib/shared-messages.js","webpack://GUI/./src/lib/sortable-hoc.jsx","webpack://GUI/./src/lib/storage.js","webpack://GUI/./src/lib/supported-browser.js","webpack://GUI/./src/lib/throttled-property-hoc.jsx","webpack://GUI/./src/lib/titled-hoc.jsx","webpack://GUI/./src/lib/touch-utils.js","webpack://GUI/./src/lib/tutorial-from-url.js","webpack://GUI/./src/lib/variable-utils.js","webpack://GUI/./src/lib/video/camera.js","webpack://GUI/./src/lib/video/modal-video-manager.js","webpack://GUI/./src/lib/video/video-provider.js","webpack://GUI/./src/lib/vm-listener-hoc.jsx","webpack://GUI/./src/lib/vm-manager-hoc.jsx","webpack://GUI/./src/reducers/alerts.js","webpack://GUI/./src/reducers/asset-drag.js","webpack://GUI/./src/reducers/block-drag.js","webpack://GUI/./src/reducers/cards.js","webpack://GUI/./src/reducers/color-picker.js","webpack://GUI/./src/reducers/connection-modal.js","webpack://GUI/./src/reducers/custom-procedures.js","webpack://GUI/./src/reducers/editor-tab.js","webpack://GUI/./src/reducers/fonts-loaded.js","webpack://GUI/./src/reducers/gui.js","webpack://GUI/./src/reducers/hovered-target.js","webpack://GUI/./src/reducers/locales.js","webpack://GUI/./src/reducers/menus.js","webpack://GUI/./src/reducers/mic-indicator.js","webpack://GUI/./src/reducers/modals.js","webpack://GUI/./src/reducers/mode.js","webpack://GUI/./src/reducers/monitor-layout.js","webpack://GUI/./src/reducers/monitors.js","webpack://GUI/./src/reducers/project-changed.js","webpack://GUI/./src/reducers/project-state.js","webpack://GUI/./src/reducers/project-title.js","webpack://GUI/./src/reducers/restore-deletion.js","webpack://GUI/./src/reducers/stage-size.js","webpack://GUI/./src/reducers/targets.js","webpack://GUI/./src/reducers/timeout.js","webpack://GUI/./src/reducers/toolbox.js","webpack://GUI/./src/reducers/vm-status.js","webpack://GUI/./src/reducers/vm.js","webpack://GUI/./src/reducers/workspace-metrics.js","webpack://GUI/../locale-data/index.js (ignored)","webpack://GUI/./lib/locales (ignored)","webpack://GUI/./lib/locales (ignored)?c8bc","webpack://GUI/util (ignored)","webpack://GUI/util (ignored)?03c5","webpack://GUI/readable-stream (ignored)","webpack://GUI/./node/self.js (ignored)","webpack://GUI/./node/extend.js (ignored)"],"sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t\"lib.min\": 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// script path function\n \tfunction jsonpScriptSrc(chunkId) {\n \t\treturn __webpack_require__.p + \"chunks/\" + ({\"am-steps\":\"am-steps\",\"ar-steps\":\"ar-steps\",\"es-steps\":\"es-steps\",\"fr-steps\":\"fr-steps\",\"ja-steps\":\"ja-steps\",\"pt_BR-steps\":\"pt_BR-steps\",\"sw-steps\":\"sw-steps\",\"tr-steps\":\"tr-steps\",\"uk-steps\":\"uk-steps\",\"zh_CN-steps\":\"zh_CN-steps\",\"zh_TW-steps\":\"zh_TW-steps\",\"zu-steps\":\"zu-steps\"}[chunkId]||chunkId) + \".js\"\n \t}\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId) {\n \t\tvar promises = [];\n\n\n \t\t// JSONP chunk loading for javascript\n\n \t\tvar installedChunkData = installedChunks[chunkId];\n \t\tif(installedChunkData !== 0) { // 0 means \"already installed\".\n\n \t\t\t// a Promise means \"currently loading\".\n \t\t\tif(installedChunkData) {\n \t\t\t\tpromises.push(installedChunkData[2]);\n \t\t\t} else {\n \t\t\t\t// setup Promise in chunk cache\n \t\t\t\tvar promise = new Promise(function(resolve, reject) {\n \t\t\t\t\tinstalledChunkData = installedChunks[chunkId] = [resolve, reject];\n \t\t\t\t});\n \t\t\t\tpromises.push(installedChunkData[2] = promise);\n\n \t\t\t\t// start chunk loading\n \t\t\t\tvar script = document.createElement('script');\n \t\t\t\tvar onScriptComplete;\n\n \t\t\t\tscript.charset = 'utf-8';\n \t\t\t\tscript.timeout = 120;\n \t\t\t\tif (__webpack_require__.nc) {\n \t\t\t\t\tscript.setAttribute(\"nonce\", __webpack_require__.nc);\n \t\t\t\t}\n \t\t\t\tscript.src = jsonpScriptSrc(chunkId);\n\n \t\t\t\t// create error before stack unwound to get useful stacktrace later\n \t\t\t\tvar error = new Error();\n \t\t\t\tonScriptComplete = function (event) {\n \t\t\t\t\t// avoid mem leaks in IE.\n \t\t\t\t\tscript.onerror = script.onload = null;\n \t\t\t\t\tclearTimeout(timeout);\n \t\t\t\t\tvar chunk = installedChunks[chunkId];\n \t\t\t\t\tif(chunk !== 0) {\n \t\t\t\t\t\tif(chunk) {\n \t\t\t\t\t\t\tvar errorType = event && (event.type === 'load' ? 'missing' : event.type);\n \t\t\t\t\t\t\tvar realSrc = event && event.target && event.target.src;\n \t\t\t\t\t\t\terror.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';\n \t\t\t\t\t\t\terror.name = 'ChunkLoadError';\n \t\t\t\t\t\t\terror.type = errorType;\n \t\t\t\t\t\t\terror.request = realSrc;\n \t\t\t\t\t\t\tchunk[1](error);\n \t\t\t\t\t\t}\n \t\t\t\t\t\tinstalledChunks[chunkId] = undefined;\n \t\t\t\t\t}\n \t\t\t\t};\n \t\t\t\tvar timeout = setTimeout(function(){\n \t\t\t\t\tonScriptComplete({ type: 'timeout', target: script });\n \t\t\t\t}, 120000);\n \t\t\t\tscript.onerror = script.onload = onScriptComplete;\n \t\t\t\tdocument.head.appendChild(script);\n \t\t\t}\n \t\t}\n \t\treturn Promise.all(promises);\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n \tvar jsonpArray = window[\"webpackJsonpGUI\"] = window[\"webpackJsonpGUI\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n","function _interopRequireDefault(obj) {\n return obj && obj.__esModule ? obj : {\n \"default\": obj\n };\n}\n\nmodule.exports = _interopRequireDefault;","/*!\n * Paper.js v0.12.7 - The Swiss Army Knife of Vector Graphics Scripting.\n * http://paperjs.org/\n *\n * Copyright (c) 2011 - 2020, Jürg Lehni & Jonathan Puckey\n * http://juerglehni.com/ & https://puckey.studio/\n *\n * Distributed under the MIT license. See LICENSE file for details.\n *\n * All rights reserved.\n *\n * Date: Tue Jul 28 15:52:11 2020 -0400\n *\n ***\n *\n * Straps.js - Class inheritance library with support for bean-style accessors\n *\n * Copyright (c) 2006 - 2020 Jürg Lehni\n * http://juerglehni.com/\n *\n * Distributed under the MIT license.\n *\n ***\n *\n * Acorn.js\n * https://marijnhaverbeke.nl/acorn/\n *\n * Acorn is a tiny, fast JavaScript parser written in JavaScript,\n * created by Marijn Haverbeke and released under an MIT license.\n *\n */\n\nvar paper = function(self, undefined) {\n\nself = self || require('./node/self.js');\nvar window = self.window ? self.window : self,\n\tdocument = self.document;\n\nvar Base = new function() {\n\tvar hidden = /^(statics|enumerable|beans|preserve)$/,\n\t\tarray = [],\n\t\tslice = array.slice,\n\t\tcreate = Object.create,\n\t\tdescribe = Object.getOwnPropertyDescriptor,\n\t\tdefine = Object.defineProperty,\n\n\t\tforEach = array.forEach || function(iter, bind) {\n\t\t\tfor (var i = 0, l = this.length; i < l; i++) {\n\t\t\t\titer.call(bind, this[i], i, this);\n\t\t\t}\n\t\t},\n\n\t\tforIn = function(iter, bind) {\n\t\t\tfor (var i in this) {\n\t\t\t\tif (this.hasOwnProperty(i))\n\t\t\t\t\titer.call(bind, this[i], i, this);\n\t\t\t}\n\t\t},\n\n\t\tset = Object.assign || function(dst) {\n\t\t\tfor (var i = 1, l = arguments.length; i < l; i++) {\n\t\t\t\tvar src = arguments[i];\n\t\t\t\tfor (var key in src) {\n\t\t\t\t\tif (src.hasOwnProperty(key))\n\t\t\t\t\t\tdst[key] = src[key];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn dst;\n\t\t},\n\n\t\teach = function(obj, iter, bind) {\n\t\t\tif (obj) {\n\t\t\t\tvar desc = describe(obj, 'length');\n\t\t\t\t(desc && typeof desc.value === 'number' ? forEach : forIn)\n\t\t\t\t\t.call(obj, iter, bind = bind || obj);\n\t\t\t}\n\t\t\treturn bind;\n\t\t};\n\n\tfunction inject(dest, src, enumerable, beans, preserve) {\n\t\tvar beansNames = {};\n\n\t\tfunction field(name, val) {\n\t\t\tval = val || (val = describe(src, name))\n\t\t\t\t\t&& (val.get ? val : val.value);\n\t\t\tif (typeof val === 'string' && val[0] === '#')\n\t\t\t\tval = dest[val.substring(1)] || val;\n\t\t\tvar isFunc = typeof val === 'function',\n\t\t\t\tres = val,\n\t\t\t\tprev = preserve || isFunc && !val.base\n\t\t\t\t\t\t? (val && val.get ? name in dest : dest[name])\n\t\t\t\t\t\t: null,\n\t\t\t\tbean;\n\t\t\tif (!preserve || !prev) {\n\t\t\t\tif (isFunc && prev)\n\t\t\t\t\tval.base = prev;\n\t\t\t\tif (isFunc && beans !== false\n\t\t\t\t\t\t&& (bean = name.match(/^([gs]et|is)(([A-Z])(.*))$/)))\n\t\t\t\t\tbeansNames[bean[3].toLowerCase() + bean[4]] = bean[2];\n\t\t\t\tif (!res || isFunc || !res.get || typeof res.get !== 'function'\n\t\t\t\t\t\t|| !Base.isPlainObject(res)) {\n\t\t\t\t\tres = { value: res, writable: true };\n\t\t\t\t}\n\t\t\t\tif ((describe(dest, name)\n\t\t\t\t\t\t|| { configurable: true }).configurable) {\n\t\t\t\t\tres.configurable = true;\n\t\t\t\t\tres.enumerable = enumerable != null ? enumerable : !bean;\n\t\t\t\t}\n\t\t\t\tdefine(dest, name, res);\n\t\t\t}\n\t\t}\n\t\tif (src) {\n\t\t\tfor (var name in src) {\n\t\t\t\tif (src.hasOwnProperty(name) && !hidden.test(name))\n\t\t\t\t\tfield(name);\n\t\t\t}\n\t\t\tfor (var name in beansNames) {\n\t\t\t\tvar part = beansNames[name],\n\t\t\t\t\tset = dest['set' + part],\n\t\t\t\t\tget = dest['get' + part] || set && dest['is' + part];\n\t\t\t\tif (get && (beans === true || get.length === 0))\n\t\t\t\t\tfield(name, { get: get, set: set });\n\t\t\t}\n\t\t}\n\t\treturn dest;\n\t}\n\n\tfunction Base() {\n\t\tfor (var i = 0, l = arguments.length; i < l; i++) {\n\t\t\tvar src = arguments[i];\n\t\t\tif (src)\n\t\t\t\tset(this, src);\n\t\t}\n\t\treturn this;\n\t}\n\n\treturn inject(Base, {\n\t\tinject: function(src) {\n\t\t\tif (src) {\n\t\t\t\tvar statics = src.statics === true ? src : src.statics,\n\t\t\t\t\tbeans = src.beans,\n\t\t\t\t\tpreserve = src.preserve;\n\t\t\t\tif (statics !== src)\n\t\t\t\t\tinject(this.prototype, src, src.enumerable, beans, preserve);\n\t\t\t\tinject(this, statics, null, beans, preserve);\n\t\t\t}\n\t\t\tfor (var i = 1, l = arguments.length; i < l; i++)\n\t\t\t\tthis.inject(arguments[i]);\n\t\t\treturn this;\n\t\t},\n\n\t\textend: function() {\n\t\t\tvar base = this,\n\t\t\t\tctor,\n\t\t\t\tproto;\n\t\t\tfor (var i = 0, obj, l = arguments.length;\n\t\t\t\t\ti < l && !(ctor && proto); i++) {\n\t\t\t\tobj = arguments[i];\n\t\t\t\tctor = ctor || obj.initialize;\n\t\t\t\tproto = proto || obj.prototype;\n\t\t\t}\n\t\t\tctor = ctor || function() {\n\t\t\t\tbase.apply(this, arguments);\n\t\t\t};\n\t\t\tproto = ctor.prototype = proto || create(this.prototype);\n\t\t\tdefine(proto, 'constructor',\n\t\t\t\t\t{ value: ctor, writable: true, configurable: true });\n\t\t\tinject(ctor, this);\n\t\t\tif (arguments.length)\n\t\t\t\tthis.inject.apply(ctor, arguments);\n\t\t\tctor.base = base;\n\t\t\treturn ctor;\n\t\t}\n\t}).inject({\n\t\tenumerable: false,\n\n\t\tinitialize: Base,\n\n\t\tset: Base,\n\n\t\tinject: function() {\n\t\t\tfor (var i = 0, l = arguments.length; i < l; i++) {\n\t\t\t\tvar src = arguments[i];\n\t\t\t\tif (src) {\n\t\t\t\t\tinject(this, src, src.enumerable, src.beans, src.preserve);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn this;\n\t\t},\n\n\t\textend: function() {\n\t\t\tvar res = create(this);\n\t\t\treturn res.inject.apply(res, arguments);\n\t\t},\n\n\t\teach: function(iter, bind) {\n\t\t\treturn each(this, iter, bind);\n\t\t},\n\n\t\tclone: function() {\n\t\t\treturn new this.constructor(this);\n\t\t},\n\n\t\tstatics: {\n\t\t\tset: set,\n\t\t\teach: each,\n\t\t\tcreate: create,\n\t\t\tdefine: define,\n\t\t\tdescribe: describe,\n\n\t\t\tclone: function(obj) {\n\t\t\t\treturn set(new obj.constructor(), obj);\n\t\t\t},\n\n\t\t\tisPlainObject: function(obj) {\n\t\t\t\tvar ctor = obj != null && obj.constructor;\n\t\t\t\treturn ctor && (ctor === Object || ctor === Base\n\t\t\t\t\t\t|| ctor.name === 'Object');\n\t\t\t},\n\n\t\t\tpick: function(a, b) {\n\t\t\t\treturn a !== undefined ? a : b;\n\t\t\t},\n\n\t\t\tslice: function(list, begin, end) {\n\t\t\t\treturn slice.call(list, begin, end);\n\t\t\t}\n\t\t}\n\t});\n};\n\nif (typeof module !== 'undefined')\n\tmodule.exports = Base;\n\nBase.inject({\n\tenumerable: false,\n\n\ttoString: function() {\n\t\treturn this._id != null\n\t\t\t? (this._class || 'Object') + (this._name\n\t\t\t\t? \" '\" + this._name + \"'\"\n\t\t\t\t: ' @' + this._id)\n\t\t\t: '{ ' + Base.each(this, function(value, key) {\n\t\t\t\tif (!/^_/.test(key)) {\n\t\t\t\t\tvar type = typeof value;\n\t\t\t\t\tthis.push(key + ': ' + (type === 'number'\n\t\t\t\t\t\t\t? Formatter.instance.number(value)\n\t\t\t\t\t\t\t: type === 'string' ? \"'\" + value + \"'\" : value));\n\t\t\t\t}\n\t\t\t}, []).join(', ') + ' }';\n\t},\n\n\tgetClassName: function() {\n\t\treturn this._class || '';\n\t},\n\n\timportJSON: function(json) {\n\t\treturn Base.importJSON(json, this);\n\t},\n\n\texportJSON: function(options) {\n\t\treturn Base.exportJSON(this, options);\n\t},\n\n\ttoJSON: function() {\n\t\treturn Base.serialize(this);\n\t},\n\n\tset: function(props, exclude) {\n\t\tif (props)\n\t\t\tBase.filter(this, props, exclude, this._prioritize);\n\t\treturn this;\n\t}\n}, {\n\nbeans: false,\nstatics: {\n\texports: {},\n\n\textend: function extend() {\n\t\tvar res = extend.base.apply(this, arguments),\n\t\t\tname = res.prototype._class;\n\t\tif (name && !Base.exports[name])\n\t\t\tBase.exports[name] = res;\n\t\treturn res;\n\t},\n\n\tequals: function(obj1, obj2) {\n\t\tif (obj1 === obj2)\n\t\t\treturn true;\n\t\tif (obj1 && obj1.equals)\n\t\t\treturn obj1.equals(obj2);\n\t\tif (obj2 && obj2.equals)\n\t\t\treturn obj2.equals(obj1);\n\t\tif (obj1 && obj2\n\t\t\t\t&& typeof obj1 === 'object' && typeof obj2 === 'object') {\n\t\t\tif (Array.isArray(obj1) && Array.isArray(obj2)) {\n\t\t\t\tvar length = obj1.length;\n\t\t\t\tif (length !== obj2.length)\n\t\t\t\t\treturn false;\n\t\t\t\twhile (length--) {\n\t\t\t\t\tif (!Base.equals(obj1[length], obj2[length]))\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar keys = Object.keys(obj1),\n\t\t\t\t\tlength = keys.length;\n\t\t\t\tif (length !== Object.keys(obj2).length)\n\t\t\t\t\treturn false;\n\t\t\t\twhile (length--) {\n\t\t\t\t\tvar key = keys[length];\n\t\t\t\t\tif (!(obj2.hasOwnProperty(key)\n\t\t\t\t\t\t\t&& Base.equals(obj1[key], obj2[key])))\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n\n\tread: function(list, start, options, amount) {\n\t\tif (this === Base) {\n\t\t\tvar value = this.peek(list, start);\n\t\t\tlist.__index++;\n\t\t\treturn value;\n\t\t}\n\t\tvar proto = this.prototype,\n\t\t\treadIndex = proto._readIndex,\n\t\t\tbegin = start || readIndex && list.__index || 0,\n\t\t\tlength = list.length,\n\t\t\tobj = list[begin];\n\t\tamount = amount || length - begin;\n\t\tif (obj instanceof this\n\t\t\t|| options && options.readNull && obj == null && amount <= 1) {\n\t\t\tif (readIndex)\n\t\t\t\tlist.__index = begin + 1;\n\t\t\treturn obj && options && options.clone ? obj.clone() : obj;\n\t\t}\n\t\tobj = Base.create(proto);\n\t\tif (readIndex)\n\t\t\tobj.__read = true;\n\t\tobj = obj.initialize.apply(obj, begin > 0 || begin + amount < length\n\t\t\t\t? Base.slice(list, begin, begin + amount)\n\t\t\t\t: list) || obj;\n\t\tif (readIndex) {\n\t\t\tlist.__index = begin + obj.__read;\n\t\t\tvar filtered = obj.__filtered;\n\t\t\tif (filtered) {\n\t\t\t\tlist.__filtered = filtered;\n\t\t\t\tobj.__filtered = undefined;\n\t\t\t}\n\t\t\tobj.__read = undefined;\n\t\t}\n\t\treturn obj;\n\t},\n\n\tpeek: function(list, start) {\n\t\treturn list[list.__index = start || list.__index || 0];\n\t},\n\n\tremain: function(list) {\n\t\treturn list.length - (list.__index || 0);\n\t},\n\n\treadList: function(list, start, options, amount) {\n\t\tvar res = [],\n\t\t\tentry,\n\t\t\tbegin = start || 0,\n\t\t\tend = amount ? begin + amount : list.length;\n\t\tfor (var i = begin; i < end; i++) {\n\t\t\tres.push(Array.isArray(entry = list[i])\n\t\t\t\t\t? this.read(entry, 0, options)\n\t\t\t\t\t: this.read(list, i, options, 1));\n\t\t}\n\t\treturn res;\n\t},\n\n\treadNamed: function(list, name, start, options, amount) {\n\t\tvar value = this.getNamed(list, name),\n\t\t\thasValue = value !== undefined;\n\t\tif (hasValue) {\n\t\t\tvar filtered = list.__filtered;\n\t\t\tif (!filtered) {\n\t\t\t\tvar source = this.getSource(list);\n\t\t\t\tfiltered = list.__filtered = Base.create(source);\n\t\t\t\tfiltered.__unfiltered = source;\n\t\t\t}\n\t\t\tfiltered[name] = undefined;\n\t\t}\n\t\treturn this.read(hasValue ? [value] : list, start, options, amount);\n\t},\n\n\treadSupported: function(list, dest) {\n\t\tvar source = this.getSource(list),\n\t\t\tthat = this,\n\t\t\tread = false;\n\t\tif (source) {\n\t\t\tObject.keys(source).forEach(function(key) {\n\t\t\t\tif (key in dest) {\n\t\t\t\t\tvar value = that.readNamed(list, key);\n\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\tdest[key] = value;\n\t\t\t\t\t}\n\t\t\t\t\tread = true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\treturn read;\n\t},\n\n\tgetSource: function(list) {\n\t\tvar source = list.__source;\n\t\tif (source === undefined) {\n\t\t\tvar arg = list.length === 1 && list[0];\n\t\t\tsource = list.__source = arg && Base.isPlainObject(arg)\n\t\t\t\t? arg : null;\n\t\t}\n\t\treturn source;\n\t},\n\n\tgetNamed: function(list, name) {\n\t\tvar source = this.getSource(list);\n\t\tif (source) {\n\t\t\treturn name ? source[name] : list.__filtered || source;\n\t\t}\n\t},\n\n\thasNamed: function(list, name) {\n\t\treturn !!this.getNamed(list, name);\n\t},\n\n\tfilter: function(dest, source, exclude, prioritize) {\n\t\tvar processed;\n\n\t\tfunction handleKey(key) {\n\t\t\tif (!(exclude && key in exclude) &&\n\t\t\t\t!(processed && key in processed)) {\n\t\t\t\tvar value = source[key];\n\t\t\t\tif (value !== undefined)\n\t\t\t\t\tdest[key] = value;\n\t\t\t}\n\t\t}\n\n\t\tif (prioritize) {\n\t\t\tvar keys = {};\n\t\t\tfor (var i = 0, key, l = prioritize.length; i < l; i++) {\n\t\t\t\tif ((key = prioritize[i]) in source) {\n\t\t\t\t\thandleKey(key);\n\t\t\t\t\tkeys[key] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tprocessed = keys;\n\t\t}\n\n\t\tObject.keys(source.__unfiltered || source).forEach(handleKey);\n\t\treturn dest;\n\t},\n\n\tisPlainValue: function(obj, asString) {\n\t\treturn Base.isPlainObject(obj) || Array.isArray(obj)\n\t\t\t\t|| asString && typeof obj === 'string';\n\t},\n\n\tserialize: function(obj, options, compact, dictionary) {\n\t\toptions = options || {};\n\n\t\tvar isRoot = !dictionary,\n\t\t\tres;\n\t\tif (isRoot) {\n\t\t\toptions.formatter = new Formatter(options.precision);\n\t\t\tdictionary = {\n\t\t\t\tlength: 0,\n\t\t\t\tdefinitions: {},\n\t\t\t\treferences: {},\n\t\t\t\tadd: function(item, create) {\n\t\t\t\t\tvar id = '#' + item._id,\n\t\t\t\t\t\tref = this.references[id];\n\t\t\t\t\tif (!ref) {\n\t\t\t\t\t\tthis.length++;\n\t\t\t\t\t\tvar res = create.call(item),\n\t\t\t\t\t\t\tname = item._class;\n\t\t\t\t\t\tif (name && res[0] !== name)\n\t\t\t\t\t\t\tres.unshift(name);\n\t\t\t\t\t\tthis.definitions[id] = res;\n\t\t\t\t\t\tref = this.references[id] = [id];\n\t\t\t\t\t}\n\t\t\t\t\treturn ref;\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\tif (obj && obj._serialize) {\n\t\t\tres = obj._serialize(options, dictionary);\n\t\t\tvar name = obj._class;\n\t\t\tif (name && !obj._compactSerialize && (isRoot || !compact)\n\t\t\t\t\t&& res[0] !== name) {\n\t\t\t\tres.unshift(name);\n\t\t\t}\n\t\t} else if (Array.isArray(obj)) {\n\t\t\tres = [];\n\t\t\tfor (var i = 0, l = obj.length; i < l; i++)\n\t\t\t\tres[i] = Base.serialize(obj[i], options, compact, dictionary);\n\t\t} else if (Base.isPlainObject(obj)) {\n\t\t\tres = {};\n\t\t\tvar keys = Object.keys(obj);\n\t\t\tfor (var i = 0, l = keys.length; i < l; i++) {\n\t\t\t\tvar key = keys[i];\n\t\t\t\tres[key] = Base.serialize(obj[key], options, compact,\n\t\t\t\t\t\tdictionary);\n\t\t\t}\n\t\t} else if (typeof obj === 'number') {\n\t\t\tres = options.formatter.number(obj, options.precision);\n\t\t} else {\n\t\t\tres = obj;\n\t\t}\n\t\treturn isRoot && dictionary.length > 0\n\t\t\t\t? [['dictionary', dictionary.definitions], res]\n\t\t\t\t: res;\n\t},\n\n\tdeserialize: function(json, create, _data, _setDictionary, _isRoot) {\n\t\tvar res = json,\n\t\t\tisFirst = !_data,\n\t\t\thasDictionary = isFirst && json && json.length\n\t\t\t\t&& json[0][0] === 'dictionary';\n\t\t_data = _data || {};\n\t\tif (Array.isArray(json)) {\n\t\t\tvar type = json[0],\n\t\t\t\tisDictionary = type === 'dictionary';\n\t\t\tif (json.length == 1 && /^#/.test(type)) {\n\t\t\t\treturn _data.dictionary[type];\n\t\t\t}\n\t\t\ttype = Base.exports[type];\n\t\t\tres = [];\n\t\t\tfor (var i = type ? 1 : 0, l = json.length; i < l; i++) {\n\t\t\t\tres.push(Base.deserialize(json[i], create, _data,\n\t\t\t\t\t\tisDictionary, hasDictionary));\n\t\t\t}\n\t\t\tif (type) {\n\t\t\t\tvar args = res;\n\t\t\t\tif (create) {\n\t\t\t\t\tres = create(type, args, isFirst || _isRoot);\n\t\t\t\t} else {\n\t\t\t\t\tres = new type(args);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (Base.isPlainObject(json)) {\n\t\t\tres = {};\n\t\t\tif (_setDictionary)\n\t\t\t\t_data.dictionary = res;\n\t\t\tfor (var key in json)\n\t\t\t\tres[key] = Base.deserialize(json[key], create, _data);\n\t\t}\n\t\treturn hasDictionary ? res[1] : res;\n\t},\n\n\texportJSON: function(obj, options) {\n\t\tvar json = Base.serialize(obj, options);\n\t\treturn options && options.asString == false\n\t\t\t\t? json\n\t\t\t\t: JSON.stringify(json);\n\t},\n\n\timportJSON: function(json, target) {\n\t\treturn Base.deserialize(\n\t\t\t\ttypeof json === 'string' ? JSON.parse(json) : json,\n\t\t\t\tfunction(ctor, args, isRoot) {\n\t\t\t\t\tvar useTarget = isRoot && target\n\t\t\t\t\t\t\t&& target.constructor === ctor,\n\t\t\t\t\t\tobj = useTarget ? target\n\t\t\t\t\t\t\t: Base.create(ctor.prototype);\n\t\t\t\t\tif (args.length === 1 && obj instanceof Item\n\t\t\t\t\t\t\t&& (useTarget || !(obj instanceof Layer))) {\n\t\t\t\t\t\tvar arg = args[0];\n\t\t\t\t\t\tif (Base.isPlainObject(arg)) {\n\t\t\t\t\t\t\targ.insert = false;\n\t\t\t\t\t\t\tif (useTarget) {\n\t\t\t\t\t\t\t\targs = args.concat([{ insert: true }]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t(useTarget ? obj.set : ctor).apply(obj, args);\n\t\t\t\t\tif (useTarget)\n\t\t\t\t\t\ttarget = null;\n\t\t\t\t\treturn obj;\n\t\t\t\t});\n\t},\n\n\tpush: function(list, items) {\n\t\tvar itemsLength = items.length;\n\t\tif (itemsLength < 4096) {\n\t\t\tlist.push.apply(list, items);\n\t\t} else {\n\t\t\tvar startLength = list.length;\n\t\t\tlist.length += itemsLength;\n\t\t\tfor (var i = 0; i < itemsLength; i++) {\n\t\t\t\tlist[startLength + i] = items[i];\n\t\t\t}\n\t\t}\n\t\treturn list;\n\t},\n\n\tsplice: function(list, items, index, remove) {\n\t\tvar amount = items && items.length,\n\t\t\tappend = index === undefined;\n\t\tindex = append ? list.length : index;\n\t\tif (index > list.length)\n\t\t\tindex = list.length;\n\t\tfor (var i = 0; i < amount; i++)\n\t\t\titems[i]._index = index + i;\n\t\tif (append) {\n\t\t\tBase.push(list, items);\n\t\t\treturn [];\n\t\t} else {\n\t\t\tvar args = [index, remove];\n\t\t\tif (items)\n\t\t\t\tBase.push(args, items);\n\t\t\tvar removed = list.splice.apply(list, args);\n\t\t\tfor (var i = 0, l = removed.length; i < l; i++)\n\t\t\t\tremoved[i]._index = undefined;\n\t\t\tfor (var i = index + amount, l = list.length; i < l; i++)\n\t\t\t\tlist[i]._index = i;\n\t\t\treturn removed;\n\t\t}\n\t},\n\n\tcapitalize: function(str) {\n\t\treturn str.replace(/\\b[a-z]/g, function(match) {\n\t\t\treturn match.toUpperCase();\n\t\t});\n\t},\n\n\tcamelize: function(str) {\n\t\treturn str.replace(/-(.)/g, function(match, chr) {\n\t\t\treturn chr.toUpperCase();\n\t\t});\n\t},\n\n\thyphenate: function(str) {\n\t\treturn str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n\t}\n}});\n\nvar Emitter = {\n\ton: function(type, func) {\n\t\tif (typeof type !== 'string') {\n\t\t\tBase.each(type, function(value, key) {\n\t\t\t\tthis.on(key, value);\n\t\t\t}, this);\n\t\t} else {\n\t\t\tvar types = this._eventTypes,\n\t\t\t\tentry = types && types[type],\n\t\t\t\thandlers = this._callbacks = this._callbacks || {};\n\t\t\thandlers = handlers[type] = handlers[type] || [];\n\t\t\tif (handlers.indexOf(func) === -1) {\n\t\t\t\thandlers.push(func);\n\t\t\t\tif (entry && entry.install && handlers.length === 1)\n\t\t\t\t\tentry.install.call(this, type);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t},\n\n\toff: function(type, func) {\n\t\tif (typeof type !== 'string') {\n\t\t\tBase.each(type, function(value, key) {\n\t\t\t\tthis.off(key, value);\n\t\t\t}, this);\n\t\t\treturn;\n\t\t}\n\t\tvar types = this._eventTypes,\n\t\t\tentry = types && types[type],\n\t\t\thandlers = this._callbacks && this._callbacks[type],\n\t\t\tindex;\n\t\tif (handlers) {\n\t\t\tif (!func || (index = handlers.indexOf(func)) !== -1\n\t\t\t\t\t&& handlers.length === 1) {\n\t\t\t\tif (entry && entry.uninstall)\n\t\t\t\t\tentry.uninstall.call(this, type);\n\t\t\t\tdelete this._callbacks[type];\n\t\t\t} else if (index !== -1) {\n\t\t\t\thandlers.splice(index, 1);\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t},\n\n\tonce: function(type, func) {\n\t\treturn this.on(type, function handler() {\n\t\t\tfunc.apply(this, arguments);\n\t\t\tthis.off(type, handler);\n\t\t});\n\t},\n\n\temit: function(type, event) {\n\t\tvar handlers = this._callbacks && this._callbacks[type];\n\t\tif (!handlers)\n\t\t\treturn false;\n\t\tvar args = Base.slice(arguments, 1),\n\t\t\tsetTarget = event && event.target && !event.currentTarget;\n\t\thandlers = handlers.slice();\n\t\tif (setTarget)\n\t\t\tevent.currentTarget = this;\n\t\tfor (var i = 0, l = handlers.length; i < l; i++) {\n\t\t\tif (handlers[i].apply(this, args) == false) {\n\t\t\t\tif (event && event.stop)\n\t\t\t\t\tevent.stop();\n\t\t\t\tbreak;\n\t\t }\n\t\t}\n\t\tif (setTarget)\n\t\t\tdelete event.currentTarget;\n\t\treturn true;\n\t},\n\n\tresponds: function(type) {\n\t\treturn !!(this._callbacks && this._callbacks[type]);\n\t},\n\n\tattach: '#on',\n\tdetach: '#off',\n\tfire: '#emit',\n\n\t_installEvents: function(install) {\n\t\tvar types = this._eventTypes,\n\t\t\thandlers = this._callbacks,\n\t\t\tkey = install ? 'install' : 'uninstall';\n\t\tif (types) {\n\t\t\tfor (var type in handlers) {\n\t\t\t\tif (handlers[type].length > 0) {\n\t\t\t\t\tvar entry = types[type],\n\t\t\t\t\t\tfunc = entry && entry[key];\n\t\t\t\t\tif (func)\n\t\t\t\t\t\tfunc.call(this, type);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tstatics: {\n\t\tinject: function inject(src) {\n\t\t\tvar events = src._events;\n\t\t\tif (events) {\n\t\t\t\tvar types = {};\n\t\t\t\tBase.each(events, function(entry, key) {\n\t\t\t\t\tvar isString = typeof entry === 'string',\n\t\t\t\t\t\tname = isString ? entry : key,\n\t\t\t\t\t\tpart = Base.capitalize(name),\n\t\t\t\t\t\ttype = name.substring(2).toLowerCase();\n\t\t\t\t\ttypes[type] = isString ? {} : entry;\n\t\t\t\t\tname = '_' + name;\n\t\t\t\t\tsrc['get' + part] = function() {\n\t\t\t\t\t\treturn this[name];\n\t\t\t\t\t};\n\t\t\t\t\tsrc['set' + part] = function(func) {\n\t\t\t\t\t\tvar prev = this[name];\n\t\t\t\t\t\tif (prev)\n\t\t\t\t\t\t\tthis.off(type, prev);\n\t\t\t\t\t\tif (func)\n\t\t\t\t\t\t\tthis.on(type, func);\n\t\t\t\t\t\tthis[name] = func;\n\t\t\t\t\t};\n\t\t\t\t});\n\t\t\t\tsrc._eventTypes = types;\n\t\t\t}\n\t\t\treturn inject.base.apply(this, arguments);\n\t\t}\n\t}\n};\n\nvar PaperScope = Base.extend({\n\t_class: 'PaperScope',\n\n\tinitialize: function PaperScope() {\n\t\tpaper = this;\n\t\tthis.settings = new Base({\n\t\t\tapplyMatrix: true,\n\t\t\tinsertItems: true,\n\t\t\thandleSize: 4,\n\t\t\thitTolerance: 0\n\t\t});\n\t\tthis.project = null;\n\t\tthis.projects = [];\n\t\tthis.tools = [];\n\t\tthis._id = PaperScope._id++;\n\t\tPaperScope._scopes[this._id] = this;\n\t\tvar proto = PaperScope.prototype;\n\t\tif (!this.support) {\n\t\t\tvar ctx = CanvasProvider.getContext(1, 1) || {};\n\t\t\tproto.support = {\n\t\t\t\tnativeDash: 'setLineDash' in ctx || 'mozDash' in ctx,\n\t\t\t\tnativeBlendModes: BlendMode.nativeModes\n\t\t\t};\n\t\t\tCanvasProvider.release(ctx);\n\t\t}\n\t\tif (!this.agent) {\n\t\t\tvar user = self.navigator.userAgent.toLowerCase(),\n\t\t\t\tos = (/(darwin|win|mac|linux|freebsd|sunos)/.exec(user)||[])[0],\n\t\t\t\tplatform = os === 'darwin' ? 'mac' : os,\n\t\t\t\tagent = proto.agent = proto.browser = { platform: platform };\n\t\t\tif (platform)\n\t\t\t\tagent[platform] = true;\n\t\t\tuser.replace(\n\t\t\t\t/(opera|chrome|safari|webkit|firefox|msie|trident|atom|node|jsdom)\\/?\\s*([.\\d]+)(?:.*version\\/([.\\d]+))?(?:.*rv\\:v?([.\\d]+))?/g,\n\t\t\t\tfunction(match, n, v1, v2, rv) {\n\t\t\t\t\tif (!agent.chrome) {\n\t\t\t\t\t\tvar v = n === 'opera' ? v2 :\n\t\t\t\t\t\t\t\t/^(node|trident)$/.test(n) ? rv : v1;\n\t\t\t\t\t\tagent.version = v;\n\t\t\t\t\t\tagent.versionNumber = parseFloat(v);\n\t\t\t\t\t\tn = { trident: 'msie', jsdom: 'node' }[n] || n;\n\t\t\t\t\t\tagent.name = n;\n\t\t\t\t\t\tagent[n] = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t\tif (agent.chrome)\n\t\t\t\tdelete agent.webkit;\n\t\t\tif (agent.atom)\n\t\t\t\tdelete agent.chrome;\n\t\t}\n\t},\n\n\tversion: \"0.12.7\",\n\n\tgetView: function() {\n\t\tvar project = this.project;\n\t\treturn project && project._view;\n\t},\n\n\tgetPaper: function() {\n\t\treturn this;\n\t},\n\n\texecute: function(code, options) {\n\t\t\tvar exports = paper.PaperScript.execute(code, this, options);\n\t\t\tView.updateFocus();\n\t\t\treturn exports;\n\t},\n\n\tinstall: function(scope) {\n\t\tvar that = this;\n\t\tBase.each(['project', 'view', 'tool'], function(key) {\n\t\t\tBase.define(scope, key, {\n\t\t\t\tconfigurable: true,\n\t\t\t\tget: function() {\n\t\t\t\t\treturn that[key];\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t\tfor (var key in this)\n\t\t\tif (!/^_/.test(key) && this[key])\n\t\t\t\tscope[key] = this[key];\n\t},\n\n\tsetup: function(element) {\n\t\tpaper = this;\n\t\tthis.project = new Project(element);\n\t\treturn this;\n\t},\n\n\tcreateCanvas: function(width, height) {\n\t\treturn CanvasProvider.getCanvas(width, height);\n\t},\n\n\tactivate: function() {\n\t\tpaper = this;\n\t},\n\n\tclear: function() {\n\t\tvar projects = this.projects,\n\t\t\ttools = this.tools;\n\t\tfor (var i = projects.length - 1; i >= 0; i--)\n\t\t\tprojects[i].remove();\n\t\tfor (var i = tools.length - 1; i >= 0; i--)\n\t\t\ttools[i].remove();\n\t},\n\n\tremove: function() {\n\t\tthis.clear();\n\t\tdelete PaperScope._scopes[this._id];\n\t},\n\n\tstatics: new function() {\n\t\tfunction handleAttribute(name) {\n\t\t\tname += 'Attribute';\n\t\t\treturn function(el, attr) {\n\t\t\t\treturn el[name](attr) || el[name]('data-paper-' + attr);\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\t_scopes: {},\n\t\t\t_id: 0,\n\n\t\t\tget: function(id) {\n\t\t\t\treturn this._scopes[id] || null;\n\t\t\t},\n\n\t\t\tgetAttribute: handleAttribute('get'),\n\t\t\thasAttribute: handleAttribute('has')\n\t\t};\n\t}\n});\n\nvar PaperScopeItem = Base.extend(Emitter, {\n\n\tinitialize: function(activate) {\n\t\tthis._scope = paper;\n\t\tthis._index = this._scope[this._list].push(this) - 1;\n\t\tif (activate || !this._scope[this._reference])\n\t\t\tthis.activate();\n\t},\n\n\tactivate: function() {\n\t\tif (!this._scope)\n\t\t\treturn false;\n\t\tvar prev = this._scope[this._reference];\n\t\tif (prev && prev !== this)\n\t\t\tprev.emit('deactivate');\n\t\tthis._scope[this._reference] = this;\n\t\tthis.emit('activate', prev);\n\t\treturn true;\n\t},\n\n\tisActive: function() {\n\t\treturn this._scope[this._reference] === this;\n\t},\n\n\tremove: function() {\n\t\tif (this._index == null)\n\t\t\treturn false;\n\t\tBase.splice(this._scope[this._list], null, this._index, 1);\n\t\tif (this._scope[this._reference] == this)\n\t\t\tthis._scope[this._reference] = null;\n\t\tthis._scope = null;\n\t\treturn true;\n\t},\n\n\tgetView: function() {\n\t\treturn this._scope.getView();\n\t}\n});\n\nvar CollisionDetection = {\n\tfindItemBoundsCollisions: function(items1, items2, tolerance) {\n\t\tfunction getBounds(items) {\n\t\t\tvar bounds = new Array(items.length);\n\t\t\tfor (var i = 0; i < items.length; i++) {\n\t\t\t\tvar rect = items[i].getBounds();\n\t\t\t\tbounds[i] = [rect.left, rect.top, rect.right, rect.bottom];\n\t\t\t}\n\t\t\treturn bounds;\n\t\t}\n\n\t\tvar bounds1 = getBounds(items1),\n\t\t\tbounds2 = !items2 || items2 === items1\n\t\t\t\t? bounds1\n\t\t\t\t: getBounds(items2);\n\t\treturn this.findBoundsCollisions(bounds1, bounds2, tolerance || 0);\n\t},\n\n\tfindCurveBoundsCollisions: function(curves1, curves2, tolerance, bothAxis) {\n\t\tfunction getBounds(curves) {\n\t\t\tvar min = Math.min,\n\t\t\t\tmax = Math.max,\n\t\t\t\tbounds = new Array(curves.length);\n\t\t\tfor (var i = 0; i < curves.length; i++) {\n\t\t\t\tvar v = curves[i];\n\t\t\t\tbounds[i] = [\n\t\t\t\t\tmin(v[0], v[2], v[4], v[6]),\n\t\t\t\t\tmin(v[1], v[3], v[5], v[7]),\n\t\t\t\t\tmax(v[0], v[2], v[4], v[6]),\n\t\t\t\t\tmax(v[1], v[3], v[5], v[7])\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn bounds;\n\t\t}\n\n\t\tvar bounds1 = getBounds(curves1),\n\t\t\tbounds2 = !curves2 || curves2 === curves1\n\t\t\t\t? bounds1\n\t\t\t\t: getBounds(curves2);\n\t\tif (bothAxis) {\n\t\t\tvar hor = this.findBoundsCollisions(\n\t\t\t\t\tbounds1, bounds2, tolerance || 0, false, true),\n\t\t\t\tver = this.findBoundsCollisions(\n\t\t\t\t\tbounds1, bounds2, tolerance || 0, true, true),\n\t\t\t\tlist = [];\n\t\t\tfor (var i = 0, l = hor.length; i < l; i++) {\n\t\t\t\tlist[i] = { hor: hor[i], ver: ver[i] };\n\t\t\t}\n\t\t\treturn list;\n\t\t}\n\t\treturn this.findBoundsCollisions(bounds1, bounds2, tolerance || 0);\n\t},\n\n\tfindBoundsCollisions: function(boundsA, boundsB, tolerance,\n\t\tsweepVertical, onlySweepAxisCollisions) {\n\t\tvar self = !boundsB || boundsA === boundsB,\n\t\t\tallBounds = self ? boundsA : boundsA.concat(boundsB),\n\t\t\tlengthA = boundsA.length,\n\t\t\tlengthAll = allBounds.length;\n\n\t\tfunction binarySearch(indices, coord, value) {\n\t\t\tvar lo = 0,\n\t\t\t\thi = indices.length;\n\t\t\twhile (lo < hi) {\n\t\t\t\tvar mid = (hi + lo) >>> 1;\n\t\t\t\tif (allBounds[indices[mid]][coord] < value) {\n\t\t\t\t\tlo = mid + 1;\n\t\t\t\t} else {\n\t\t\t\t\thi = mid;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn lo - 1;\n\t\t}\n\n\t\tvar pri0 = sweepVertical ? 1 : 0,\n\t\t\tpri1 = pri0 + 2,\n\t\t\tsec0 = sweepVertical ? 0 : 1,\n\t\t\tsec1 = sec0 + 2;\n\t\tvar allIndicesByPri0 = new Array(lengthAll);\n\t\tfor (var i = 0; i < lengthAll; i++) {\n\t\t\tallIndicesByPri0[i] = i;\n\t\t}\n\t\tallIndicesByPri0.sort(function(i1, i2) {\n\t\t\treturn allBounds[i1][pri0] - allBounds[i2][pri0];\n\t\t});\n\t\tvar activeIndicesByPri1 = [],\n\t\t\tallCollisions = new Array(lengthA);\n\t\tfor (var i = 0; i < lengthAll; i++) {\n\t\t\tvar curIndex = allIndicesByPri0[i],\n\t\t\t\tcurBounds = allBounds[curIndex],\n\t\t\t\torigIndex = self ? curIndex : curIndex - lengthA,\n\t\t\t\tisCurrentA = curIndex < lengthA,\n\t\t\t\tisCurrentB = self || !isCurrentA,\n\t\t\t\tcurCollisions = isCurrentA ? [] : null;\n\t\t\tif (activeIndicesByPri1.length) {\n\t\t\t\tvar pruneCount = binarySearch(activeIndicesByPri1, pri1,\n\t\t\t\t\t\tcurBounds[pri0] - tolerance) + 1;\n\t\t\t\tactiveIndicesByPri1.splice(0, pruneCount);\n\t\t\t\tif (self && onlySweepAxisCollisions) {\n\t\t\t\t\tcurCollisions = curCollisions.concat(activeIndicesByPri1);\n\t\t\t\t\tfor (var j = 0; j < activeIndicesByPri1.length; j++) {\n\t\t\t\t\t\tvar activeIndex = activeIndicesByPri1[j];\n\t\t\t\t\t\tallCollisions[activeIndex].push(origIndex);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvar curSec1 = curBounds[sec1],\n\t\t\t\t\t\tcurSec0 = curBounds[sec0];\n\t\t\t\t\tfor (var j = 0; j < activeIndicesByPri1.length; j++) {\n\t\t\t\t\t\tvar activeIndex = activeIndicesByPri1[j],\n\t\t\t\t\t\t\tactiveBounds = allBounds[activeIndex],\n\t\t\t\t\t\t\tisActiveA = activeIndex < lengthA,\n\t\t\t\t\t\t\tisActiveB = self || activeIndex >= lengthA;\n\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tonlySweepAxisCollisions ||\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tisCurrentA && isActiveB ||\n\t\t\t\t\t\t\t\tisCurrentB && isActiveA\n\t\t\t\t\t\t\t) && (\n\t\t\t\t\t\t\t\tcurSec1 >= activeBounds[sec0] - tolerance &&\n\t\t\t\t\t\t\t\tcurSec0 <= activeBounds[sec1] + tolerance\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tif (isCurrentA && isActiveB) {\n\t\t\t\t\t\t\t\tcurCollisions.push(\n\t\t\t\t\t\t\t\t\tself ? activeIndex : activeIndex - lengthA);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (isCurrentB && isActiveA) {\n\t\t\t\t\t\t\t\tallCollisions[activeIndex].push(origIndex);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (isCurrentA) {\n\t\t\t\tif (boundsA === boundsB) {\n\t\t\t\t\tcurCollisions.push(curIndex);\n\t\t\t\t}\n\t\t\t\tallCollisions[curIndex] = curCollisions;\n\t\t\t}\n\t\t\tif (activeIndicesByPri1.length) {\n\t\t\t\tvar curPri1 = curBounds[pri1],\n\t\t\t\t\tindex = binarySearch(activeIndicesByPri1, pri1, curPri1);\n\t\t\t\tactiveIndicesByPri1.splice(index + 1, 0, curIndex);\n\t\t\t} else {\n\t\t\t\tactiveIndicesByPri1.push(curIndex);\n\t\t\t}\n\t\t}\n\t\tfor (var i = 0; i < allCollisions.length; i++) {\n\t\t\tvar collisions = allCollisions[i];\n\t\t\tif (collisions) {\n\t\t\t\tcollisions.sort(function(i1, i2) { return i1 - i2; });\n\t\t\t}\n\t\t}\n\t\treturn allCollisions;\n\t}\n};\n\nvar Formatter = Base.extend({\n\tinitialize: function(precision) {\n\t\tthis.precision = Base.pick(precision, 5);\n\t\tthis.multiplier = Math.pow(10, this.precision);\n\t},\n\n\tnumber: function(val) {\n\t\treturn this.precision < 16\n\t\t\t\t? Math.round(val * this.multiplier) / this.multiplier : val;\n\t},\n\n\tpair: function(val1, val2, separator) {\n\t\treturn this.number(val1) + (separator || ',') + this.number(val2);\n\t},\n\n\tpoint: function(val, separator) {\n\t\treturn this.number(val.x) + (separator || ',') + this.number(val.y);\n\t},\n\n\tsize: function(val, separator) {\n\t\treturn this.number(val.width) + (separator || ',')\n\t\t\t\t+ this.number(val.height);\n\t},\n\n\trectangle: function(val, separator) {\n\t\treturn this.point(val, separator) + (separator || ',')\n\t\t\t\t+ this.size(val, separator);\n\t}\n});\n\nFormatter.instance = new Formatter();\n\nvar Numerical = new function() {\n\n\tvar abscissas = [\n\t\t[ 0.5773502691896257645091488],\n\t\t[0,0.7745966692414833770358531],\n\t\t[ 0.3399810435848562648026658,0.8611363115940525752239465],\n\t\t[0,0.5384693101056830910363144,0.9061798459386639927976269],\n\t\t[ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016],\n\t\t[0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897],\n\t\t[ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609],\n\t\t[0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762],\n\t\t[ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640],\n\t\t[0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380],\n\t\t[ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491],\n\t\t[0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294],\n\t\t[ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973],\n\t\t[0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657],\n\t\t[ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542]\n\t];\n\n\tvar weights = [\n\t\t[1],\n\t\t[0.8888888888888888888888889,0.5555555555555555555555556],\n\t\t[0.6521451548625461426269361,0.3478548451374538573730639],\n\t\t[0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640],\n\t\t[0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961],\n\t\t[0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114],\n\t\t[0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314],\n\t\t[0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922],\n\t\t[0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688],\n\t\t[0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537],\n\t\t[0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160],\n\t\t[0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216],\n\t\t[0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329],\n\t\t[0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284],\n\t\t[0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806]\n\t];\n\n\tvar abs = Math.abs,\n\t\tsqrt = Math.sqrt,\n\t\tpow = Math.pow,\n\t\tlog2 = Math.log2 || function(x) {\n\t\t\treturn Math.log(x) * Math.LOG2E;\n\t\t},\n\t\tEPSILON = 1e-12,\n\t\tMACHINE_EPSILON = 1.12e-16;\n\n\tfunction clamp(value, min, max) {\n\t\treturn value < min ? min : value > max ? max : value;\n\t}\n\n\tfunction getDiscriminant(a, b, c) {\n\t\tfunction split(v) {\n\t\t\tvar x = v * 134217729,\n\t\t\t\ty = v - x,\n\t\t\t\thi = y + x,\n\t\t\t\tlo = v - hi;\n\t\t\treturn [hi, lo];\n\t\t}\n\n\t\tvar D = b * b - a * c,\n\t\t\tE = b * b + a * c;\n\t\tif (abs(D) * 3 < E) {\n\t\t\tvar ad = split(a),\n\t\t\t\tbd = split(b),\n\t\t\t\tcd = split(c),\n\t\t\t\tp = b * b,\n\t\t\t\tdp = (bd[0] * bd[0] - p + 2 * bd[0] * bd[1]) + bd[1] * bd[1],\n\t\t\t\tq = a * c,\n\t\t\t\tdq = (ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0])\n\t\t\t\t\t\t+ ad[1] * cd[1];\n\t\t\tD = (p - q) + (dp - dq);\n\t\t}\n\t\treturn D;\n\t}\n\n\tfunction getNormalizationFactor() {\n\t\tvar norm = Math.max.apply(Math, arguments);\n\t\treturn norm && (norm < 1e-8 || norm > 1e8)\n\t\t\t\t? pow(2, -Math.round(log2(norm)))\n\t\t\t\t: 0;\n\t}\n\n\treturn {\n\t\tEPSILON: EPSILON,\n\t\tMACHINE_EPSILON: MACHINE_EPSILON,\n\t\tCURVETIME_EPSILON: 1e-8,\n\t\tGEOMETRIC_EPSILON: 1e-7,\n\t\tTRIGONOMETRIC_EPSILON: 1e-8,\n\t\tKAPPA: 4 * (sqrt(2) - 1) / 3,\n\n\t\tisZero: function(val) {\n\t\t\treturn val >= -EPSILON && val <= EPSILON;\n\t\t},\n\n\t\tisMachineZero: function(val) {\n\t\t\treturn val >= -MACHINE_EPSILON && val <= MACHINE_EPSILON;\n\t\t},\n\n\t\tclamp: clamp,\n\n\t\tintegrate: function(f, a, b, n) {\n\t\t\tvar x = abscissas[n - 2],\n\t\t\t\tw = weights[n - 2],\n\t\t\t\tA = (b - a) * 0.5,\n\t\t\t\tB = A + a,\n\t\t\t\ti = 0,\n\t\t\t\tm = (n + 1) >> 1,\n\t\t\t\tsum = n & 1 ? w[i++] * f(B) : 0;\n\t\t\twhile (i < m) {\n\t\t\t\tvar Ax = A * x[i];\n\t\t\t\tsum += w[i++] * (f(B + Ax) + f(B - Ax));\n\t\t\t}\n\t\t\treturn A * sum;\n\t\t},\n\n\t\tfindRoot: function(f, df, x, a, b, n, tolerance) {\n\t\t\tfor (var i = 0; i < n; i++) {\n\t\t\t\tvar fx = f(x),\n\t\t\t\t\tdx = fx / df(x),\n\t\t\t\t\tnx = x - dx;\n\t\t\t\tif (abs(dx) < tolerance) {\n\t\t\t\t\tx = nx;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (fx > 0) {\n\t\t\t\t\tb = x;\n\t\t\t\t\tx = nx <= a ? (a + b) * 0.5 : nx;\n\t\t\t\t} else {\n\t\t\t\t\ta = x;\n\t\t\t\t\tx = nx >= b ? (a + b) * 0.5 : nx;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn clamp(x, a, b);\n\t\t},\n\n\t\tsolveQuadratic: function(a, b, c, roots, min, max) {\n\t\t\tvar x1, x2 = Infinity;\n\t\t\tif (abs(a) < EPSILON) {\n\t\t\t\tif (abs(b) < EPSILON)\n\t\t\t\t\treturn abs(c) < EPSILON ? -1 : 0;\n\t\t\t\tx1 = -c / b;\n\t\t\t} else {\n\t\t\t\tb *= -0.5;\n\t\t\t\tvar D = getDiscriminant(a, b, c);\n\t\t\t\tif (D && abs(D) < MACHINE_EPSILON) {\n\t\t\t\t\tvar f = getNormalizationFactor(abs(a), abs(b), abs(c));\n\t\t\t\t\tif (f) {\n\t\t\t\t\t\ta *= f;\n\t\t\t\t\t\tb *= f;\n\t\t\t\t\t\tc *= f;\n\t\t\t\t\t\tD = getDiscriminant(a, b, c);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (D >= -MACHINE_EPSILON) {\n\t\t\t\t\tvar Q = D < 0 ? 0 : sqrt(D),\n\t\t\t\t\t\tR = b + (b < 0 ? -Q : Q);\n\t\t\t\t\tif (R === 0) {\n\t\t\t\t\t\tx1 = c / a;\n\t\t\t\t\t\tx2 = -x1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tx1 = R / a;\n\t\t\t\t\t\tx2 = c / R;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar count = 0,\n\t\t\t\tboundless = min == null,\n\t\t\t\tminB = min - EPSILON,\n\t\t\t\tmaxB = max + EPSILON;\n\t\t\tif (isFinite(x1) && (boundless || x1 > minB && x1 < maxB))\n\t\t\t\troots[count++] = boundless ? x1 : clamp(x1, min, max);\n\t\t\tif (x2 !== x1\n\t\t\t\t\t&& isFinite(x2) && (boundless || x2 > minB && x2 < maxB))\n\t\t\t\troots[count++] = boundless ? x2 : clamp(x2, min, max);\n\t\t\treturn count;\n\t\t},\n\n\t\tsolveCubic: function(a, b, c, d, roots, min, max) {\n\t\t\tvar f = getNormalizationFactor(abs(a), abs(b), abs(c), abs(d)),\n\t\t\t\tx, b1, c2, qd, q;\n\t\t\tif (f) {\n\t\t\t\ta *= f;\n\t\t\t\tb *= f;\n\t\t\t\tc *= f;\n\t\t\t\td *= f;\n\t\t\t}\n\n\t\t\tfunction evaluate(x0) {\n\t\t\t\tx = x0;\n\t\t\t\tvar tmp = a * x;\n\t\t\t\tb1 = tmp + b;\n\t\t\t\tc2 = b1 * x + c;\n\t\t\t\tqd = (tmp + b1) * x + c2;\n\t\t\t\tq = c2 * x + d;\n\t\t\t}\n\n\t\t\tif (abs(a) < EPSILON) {\n\t\t\t\ta = b;\n\t\t\t\tb1 = c;\n\t\t\t\tc2 = d;\n\t\t\t\tx = Infinity;\n\t\t\t} else if (abs(d) < EPSILON) {\n\t\t\t\tb1 = b;\n\t\t\t\tc2 = c;\n\t\t\t\tx = 0;\n\t\t\t} else {\n\t\t\t\tevaluate(-(b / a) / 3);\n\t\t\t\tvar t = q / a,\n\t\t\t\t\tr = pow(abs(t), 1/3),\n\t\t\t\t\ts = t < 0 ? -1 : 1,\n\t\t\t\t\ttd = -qd / a,\n\t\t\t\t\trd = td > 0 ? 1.324717957244746 * Math.max(r, sqrt(td)) : r,\n\t\t\t\t\tx0 = x - s * rd;\n\t\t\t\tif (x0 !== x) {\n\t\t\t\t\tdo {\n\t\t\t\t\t\tevaluate(x0);\n\t\t\t\t\t\tx0 = qd === 0 ? x : x - q / qd / (1 + MACHINE_EPSILON);\n\t\t\t\t\t} while (s * x0 > s * x);\n\t\t\t\t\tif (abs(a) * x * x > abs(d / x)) {\n\t\t\t\t\t\tc2 = -d / x;\n\t\t\t\t\t\tb1 = (c2 - c) / x;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar count = Numerical.solveQuadratic(a, b1, c2, roots, min, max),\n\t\t\t\tboundless = min == null;\n\t\t\tif (isFinite(x) && (count === 0\n\t\t\t\t\t|| count > 0 && x !== roots[0] && x !== roots[1])\n\t\t\t\t\t&& (boundless || x > min - EPSILON && x < max + EPSILON))\n\t\t\t\troots[count++] = boundless ? x : clamp(x, min, max);\n\t\t\treturn count;\n\t\t}\n\t};\n};\n\nvar UID = {\n\t_id: 1,\n\t_pools: {},\n\n\tget: function(name) {\n\t\tif (name) {\n\t\t\tvar pool = this._pools[name];\n\t\t\tif (!pool)\n\t\t\t\tpool = this._pools[name] = { _id: 1 };\n\t\t\treturn pool._id++;\n\t\t} else {\n\t\t\treturn this._id++;\n\t\t}\n\t}\n};\n\nvar Point = Base.extend({\n\t_class: 'Point',\n\t_readIndex: true,\n\n\tinitialize: function Point(arg0, arg1) {\n\t\tvar type = typeof arg0,\n\t\t\treading = this.__read,\n\t\t\tread = 0;\n\t\tif (type === 'number') {\n\t\t\tvar hasY = typeof arg1 === 'number';\n\t\t\tthis._set(arg0, hasY ? arg1 : arg0);\n\t\t\tif (reading)\n\t\t\t\tread = hasY ? 2 : 1;\n\t\t} else if (type === 'undefined' || arg0 === null) {\n\t\t\tthis._set(0, 0);\n\t\t\tif (reading)\n\t\t\t\tread = arg0 === null ? 1 : 0;\n\t\t} else {\n\t\t\tvar obj = type === 'string' ? arg0.split(/[\\s,]+/) || [] : arg0;\n\t\t\tread = 1;\n\t\t\tif (Array.isArray(obj)) {\n\t\t\t\tthis._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0]));\n\t\t\t} else if ('x' in obj) {\n\t\t\t\tthis._set(obj.x || 0, obj.y || 0);\n\t\t\t} else if ('width' in obj) {\n\t\t\t\tthis._set(obj.width || 0, obj.height || 0);\n\t\t\t} else if ('angle' in obj) {\n\t\t\t\tthis._set(obj.length || 0, 0);\n\t\t\t\tthis.setAngle(obj.angle || 0);\n\t\t\t} else {\n\t\t\t\tthis._set(0, 0);\n\t\t\t\tread = 0;\n\t\t\t}\n\t\t}\n\t\tif (reading)\n\t\t\tthis.__read = read;\n\t\treturn this;\n\t},\n\n\tset: '#initialize',\n\n\t_set: function(x, y) {\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t\treturn this;\n\t},\n\n\tequals: function(point) {\n\t\treturn this === point || point\n\t\t\t\t&& (this.x === point.x && this.y === point.y\n\t\t\t\t\t|| Array.isArray(point)\n\t\t\t\t\t\t&& this.x === point[0] && this.y === point[1])\n\t\t\t\t|| false;\n\t},\n\n\tclone: function() {\n\t\treturn new Point(this.x, this.y);\n\t},\n\n\ttoString: function() {\n\t\tvar f = Formatter.instance;\n\t\treturn '{ x: ' + f.number(this.x) + ', y: ' + f.number(this.y) + ' }';\n\t},\n\n\t_serialize: function(options) {\n\t\tvar f = options.formatter;\n\t\treturn [f.number(this.x), f.number(this.y)];\n\t},\n\n\tgetLength: function() {\n\t\treturn Math.sqrt(this.x * this.x + this.y * this.y);\n\t},\n\n\tsetLength: function(length) {\n\t\tif (this.isZero()) {\n\t\t\tvar angle = this._angle || 0;\n\t\t\tthis._set(\n\t\t\t\tMath.cos(angle) * length,\n\t\t\t\tMath.sin(angle) * length\n\t\t\t);\n\t\t} else {\n\t\t\tvar scale = length / this.getLength();\n\t\t\tif (Numerical.isZero(scale))\n\t\t\t\tthis.getAngle();\n\t\t\tthis._set(\n\t\t\t\tthis.x * scale,\n\t\t\t\tthis.y * scale\n\t\t\t);\n\t\t}\n\t},\n\tgetAngle: function() {\n\t\treturn this.getAngleInRadians.apply(this, arguments) * 180 / Math.PI;\n\t},\n\n\tsetAngle: function(angle) {\n\t\tthis.setAngleInRadians.call(this, angle * Math.PI / 180);\n\t},\n\n\tgetAngleInDegrees: '#getAngle',\n\tsetAngleInDegrees: '#setAngle',\n\n\tgetAngleInRadians: function() {\n\t\tif (!arguments.length) {\n\t\t\treturn this.isZero()\n\t\t\t\t\t? this._angle || 0\n\t\t\t\t\t: this._angle = Math.atan2(this.y, this.x);\n\t\t} else {\n\t\t\tvar point = Point.read(arguments),\n\t\t\t\tdiv = this.getLength() * point.getLength();\n\t\t\tif (Numerical.isZero(div)) {\n\t\t\t\treturn NaN;\n\t\t\t} else {\n\t\t\t\tvar a = this.dot(point) / div;\n\t\t\t\treturn Math.acos(a < -1 ? -1 : a > 1 ? 1 : a);\n\t\t\t}\n\t\t}\n\t},\n\n\tsetAngleInRadians: function(angle) {\n\t\tthis._angle = angle;\n\t\tif (!this.isZero()) {\n\t\t\tvar length = this.getLength();\n\t\t\tthis._set(\n\t\t\t\tMath.cos(angle) * length,\n\t\t\t\tMath.sin(angle) * length\n\t\t\t);\n\t\t}\n\t},\n\n\tgetQuadrant: function() {\n\t\treturn this.x >= 0 ? this.y >= 0 ? 1 : 4 : this.y >= 0 ? 2 : 3;\n\t}\n}, {\n\tbeans: false,\n\n\tgetDirectedAngle: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn Math.atan2(this.cross(point), this.dot(point)) * 180 / Math.PI;\n\t},\n\n\tgetDistance: function() {\n\t\tvar args = arguments,\n\t\t\tpoint = Point.read(args),\n\t\t\tx = point.x - this.x,\n\t\t\ty = point.y - this.y,\n\t\t\td = x * x + y * y,\n\t\t\tsquared = Base.read(args);\n\t\treturn squared ? d : Math.sqrt(d);\n\t},\n\n\tnormalize: function(length) {\n\t\tif (length === undefined)\n\t\t\tlength = 1;\n\t\tvar current = this.getLength(),\n\t\t\tscale = current !== 0 ? length / current : 0,\n\t\t\tpoint = new Point(this.x * scale, this.y * scale);\n\t\tif (scale >= 0)\n\t\t\tpoint._angle = this._angle;\n\t\treturn point;\n\t},\n\n\trotate: function(angle, center) {\n\t\tif (angle === 0)\n\t\t\treturn this.clone();\n\t\tangle = angle * Math.PI / 180;\n\t\tvar point = center ? this.subtract(center) : this,\n\t\t\tsin = Math.sin(angle),\n\t\t\tcos = Math.cos(angle);\n\t\tpoint = new Point(\n\t\t\tpoint.x * cos - point.y * sin,\n\t\t\tpoint.x * sin + point.y * cos\n\t\t);\n\t\treturn center ? point.add(center) : point;\n\t},\n\n\ttransform: function(matrix) {\n\t\treturn matrix ? matrix._transformPoint(this) : this;\n\t},\n\n\tadd: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn new Point(this.x + point.x, this.y + point.y);\n\t},\n\n\tsubtract: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn new Point(this.x - point.x, this.y - point.y);\n\t},\n\n\tmultiply: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn new Point(this.x * point.x, this.y * point.y);\n\t},\n\n\tdivide: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn new Point(this.x / point.x, this.y / point.y);\n\t},\n\n\tmodulo: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn new Point(this.x % point.x, this.y % point.y);\n\t},\n\n\tnegate: function() {\n\t\treturn new Point(-this.x, -this.y);\n\t},\n\n\tisInside: function() {\n\t\treturn Rectangle.read(arguments).contains(this);\n\t},\n\n\tisClose: function() {\n\t\tvar args = arguments,\n\t\t\tpoint = Point.read(args),\n\t\t\ttolerance = Base.read(args);\n\t\treturn this.getDistance(point) <= tolerance;\n\t},\n\n\tisCollinear: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn Point.isCollinear(this.x, this.y, point.x, point.y);\n\t},\n\n\tisColinear: '#isCollinear',\n\n\tisOrthogonal: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn Point.isOrthogonal(this.x, this.y, point.x, point.y);\n\t},\n\n\tisZero: function() {\n\t\tvar isZero = Numerical.isZero;\n\t\treturn isZero(this.x) && isZero(this.y);\n\t},\n\n\tisNaN: function() {\n\t\treturn isNaN(this.x) || isNaN(this.y);\n\t},\n\n\tisInQuadrant: function(q) {\n\t\treturn this.x * (q > 1 && q < 4 ? -1 : 1) >= 0\n\t\t\t&& this.y * (q > 2 ? -1 : 1) >= 0;\n\t},\n\n\tdot: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn this.x * point.x + this.y * point.y;\n\t},\n\n\tcross: function() {\n\t\tvar point = Point.read(arguments);\n\t\treturn this.x * point.y - this.y * point.x;\n\t},\n\n\tproject: function() {\n\t\tvar point = Point.read(arguments),\n\t\t\tscale = point.isZero() ? 0 : this.dot(point) / point.dot(point);\n\t\treturn new Point(\n\t\t\tpoint.x * scale,\n\t\t\tpoint.y * scale\n\t\t);\n\t},\n\n\tstatics: {\n\t\tmin: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tpoint1 = Point.read(args),\n\t\t\t\tpoint2 = Point.read(args);\n\t\t\treturn new Point(\n\t\t\t\tMath.min(point1.x, point2.x),\n\t\t\t\tMath.min(point1.y, point2.y)\n\t\t\t);\n\t\t},\n\n\t\tmax: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tpoint1 = Point.read(args),\n\t\t\t\tpoint2 = Point.read(args);\n\t\t\treturn new Point(\n\t\t\t\tMath.max(point1.x, point2.x),\n\t\t\t\tMath.max(point1.y, point2.y)\n\t\t\t);\n\t\t},\n\n\t\trandom: function() {\n\t\t\treturn new Point(Math.random(), Math.random());\n\t\t},\n\n\t\tisCollinear: function(x1, y1, x2, y2) {\n\t\t\treturn Math.abs(x1 * y2 - y1 * x2)\n\t\t\t\t\t<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))\n\t\t\t\t\t\t* 1e-8;\n\t\t},\n\n\t\tisOrthogonal: function(x1, y1, x2, y2) {\n\t\t\treturn Math.abs(x1 * x2 + y1 * y2)\n\t\t\t\t\t<= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2))\n\t\t\t\t\t\t* 1e-8;\n\t\t}\n\t}\n}, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) {\n\tvar op = Math[key];\n\tthis[key] = function() {\n\t\treturn new Point(op(this.x), op(this.y));\n\t};\n}, {}));\n\nvar LinkedPoint = Point.extend({\n\tinitialize: function Point(x, y, owner, setter) {\n\t\tthis._x = x;\n\t\tthis._y = y;\n\t\tthis._owner = owner;\n\t\tthis._setter = setter;\n\t},\n\n\t_set: function(x, y, _dontNotify) {\n\t\tthis._x = x;\n\t\tthis._y = y;\n\t\tif (!_dontNotify)\n\t\t\tthis._owner[this._setter](this);\n\t\treturn this;\n\t},\n\n\tgetX: function() {\n\t\treturn this._x;\n\t},\n\n\tsetX: function(x) {\n\t\tthis._x = x;\n\t\tthis._owner[this._setter](this);\n\t},\n\n\tgetY: function() {\n\t\treturn this._y;\n\t},\n\n\tsetY: function(y) {\n\t\tthis._y = y;\n\t\tthis._owner[this._setter](this);\n\t},\n\n\tisSelected: function() {\n\t\treturn !!(this._owner._selection & this._getSelection());\n\t},\n\n\tsetSelected: function(selected) {\n\t\tthis._owner._changeSelection(this._getSelection(), selected);\n\t},\n\n\t_getSelection: function() {\n\t\treturn this._setter === 'setPosition' ? 4 : 0;\n\t}\n});\n\nvar Size = Base.extend({\n\t_class: 'Size',\n\t_readIndex: true,\n\n\tinitialize: function Size(arg0, arg1) {\n\t\tvar type = typeof arg0,\n\t\t\treading = this.__read,\n\t\t\tread = 0;\n\t\tif (type === 'number') {\n\t\t\tvar hasHeight = typeof arg1 === 'number';\n\t\t\tthis._set(arg0, hasHeight ? arg1 : arg0);\n\t\t\tif (reading)\n\t\t\t\tread = hasHeight ? 2 : 1;\n\t\t} else if (type === 'undefined' || arg0 === null) {\n\t\t\tthis._set(0, 0);\n\t\t\tif (reading)\n\t\t\t\tread = arg0 === null ? 1 : 0;\n\t\t} else {\n\t\t\tvar obj = type === 'string' ? arg0.split(/[\\s,]+/) || [] : arg0;\n\t\t\tread = 1;\n\t\t\tif (Array.isArray(obj)) {\n\t\t\t\tthis._set(+obj[0], +(obj.length > 1 ? obj[1] : obj[0]));\n\t\t\t} else if ('width' in obj) {\n\t\t\t\tthis._set(obj.width || 0, obj.height || 0);\n\t\t\t} else if ('x' in obj) {\n\t\t\t\tthis._set(obj.x || 0, obj.y || 0);\n\t\t\t} else {\n\t\t\t\tthis._set(0, 0);\n\t\t\t\tread = 0;\n\t\t\t}\n\t\t}\n\t\tif (reading)\n\t\t\tthis.__read = read;\n\t\treturn this;\n\t},\n\n\tset: '#initialize',\n\n\t_set: function(width, height) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\treturn this;\n\t},\n\n\tequals: function(size) {\n\t\treturn size === this || size && (this.width === size.width\n\t\t\t\t&& this.height === size.height\n\t\t\t\t|| Array.isArray(size) && this.width === size[0]\n\t\t\t\t\t&& this.height === size[1]) || false;\n\t},\n\n\tclone: function() {\n\t\treturn new Size(this.width, this.height);\n\t},\n\n\ttoString: function() {\n\t\tvar f = Formatter.instance;\n\t\treturn '{ width: ' + f.number(this.width)\n\t\t\t\t+ ', height: ' + f.number(this.height) + ' }';\n\t},\n\n\t_serialize: function(options) {\n\t\tvar f = options.formatter;\n\t\treturn [f.number(this.width),\n\t\t\t\tf.number(this.height)];\n\t},\n\n\tadd: function() {\n\t\tvar size = Size.read(arguments);\n\t\treturn new Size(this.width + size.width, this.height + size.height);\n\t},\n\n\tsubtract: function() {\n\t\tvar size = Size.read(arguments);\n\t\treturn new Size(this.width - size.width, this.height - size.height);\n\t},\n\n\tmultiply: function() {\n\t\tvar size = Size.read(arguments);\n\t\treturn new Size(this.width * size.width, this.height * size.height);\n\t},\n\n\tdivide: function() {\n\t\tvar size = Size.read(arguments);\n\t\treturn new Size(this.width / size.width, this.height / size.height);\n\t},\n\n\tmodulo: function() {\n\t\tvar size = Size.read(arguments);\n\t\treturn new Size(this.width % size.width, this.height % size.height);\n\t},\n\n\tnegate: function() {\n\t\treturn new Size(-this.width, -this.height);\n\t},\n\n\tisZero: function() {\n\t\tvar isZero = Numerical.isZero;\n\t\treturn isZero(this.width) && isZero(this.height);\n\t},\n\n\tisNaN: function() {\n\t\treturn isNaN(this.width) || isNaN(this.height);\n\t},\n\n\tstatics: {\n\t\tmin: function(size1, size2) {\n\t\t\treturn new Size(\n\t\t\t\tMath.min(size1.width, size2.width),\n\t\t\t\tMath.min(size1.height, size2.height));\n\t\t},\n\n\t\tmax: function(size1, size2) {\n\t\t\treturn new Size(\n\t\t\t\tMath.max(size1.width, size2.width),\n\t\t\t\tMath.max(size1.height, size2.height));\n\t\t},\n\n\t\trandom: function() {\n\t\t\treturn new Size(Math.random(), Math.random());\n\t\t}\n\t}\n}, Base.each(['round', 'ceil', 'floor', 'abs'], function(key) {\n\tvar op = Math[key];\n\tthis[key] = function() {\n\t\treturn new Size(op(this.width), op(this.height));\n\t};\n}, {}));\n\nvar LinkedSize = Size.extend({\n\tinitialize: function Size(width, height, owner, setter) {\n\t\tthis._width = width;\n\t\tthis._height = height;\n\t\tthis._owner = owner;\n\t\tthis._setter = setter;\n\t},\n\n\t_set: function(width, height, _dontNotify) {\n\t\tthis._width = width;\n\t\tthis._height = height;\n\t\tif (!_dontNotify)\n\t\t\tthis._owner[this._setter](this);\n\t\treturn this;\n\t},\n\n\tgetWidth: function() {\n\t\treturn this._width;\n\t},\n\n\tsetWidth: function(width) {\n\t\tthis._width = width;\n\t\tthis._owner[this._setter](this);\n\t},\n\n\tgetHeight: function() {\n\t\treturn this._height;\n\t},\n\n\tsetHeight: function(height) {\n\t\tthis._height = height;\n\t\tthis._owner[this._setter](this);\n\t}\n});\n\nvar Rectangle = Base.extend({\n\t_class: 'Rectangle',\n\t_readIndex: true,\n\tbeans: true,\n\n\tinitialize: function Rectangle(arg0, arg1, arg2, arg3) {\n\t\tvar args = arguments,\n\t\t\ttype = typeof arg0,\n\t\t\tread;\n\t\tif (type === 'number') {\n\t\t\tthis._set(arg0, arg1, arg2, arg3);\n\t\t\tread = 4;\n\t\t} else if (type === 'undefined' || arg0 === null) {\n\t\t\tthis._set(0, 0, 0, 0);\n\t\t\tread = arg0 === null ? 1 : 0;\n\t\t} else if (args.length === 1) {\n\t\t\tif (Array.isArray(arg0)) {\n\t\t\t\tthis._set.apply(this, arg0);\n\t\t\t\tread = 1;\n\t\t\t} else if (arg0.x !== undefined || arg0.width !== undefined) {\n\t\t\t\tthis._set(arg0.x || 0, arg0.y || 0,\n\t\t\t\t\t\targ0.width || 0, arg0.height || 0);\n\t\t\t\tread = 1;\n\t\t\t} else if (arg0.from === undefined && arg0.to === undefined) {\n\t\t\t\tthis._set(0, 0, 0, 0);\n\t\t\t\tif (Base.readSupported(args, this)) {\n\t\t\t\t\tread = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (read === undefined) {\n\t\t\tvar frm = Point.readNamed(args, 'from'),\n\t\t\t\tnext = Base.peek(args),\n\t\t\t\tx = frm.x,\n\t\t\t\ty = frm.y,\n\t\t\t\twidth,\n\t\t\t\theight;\n\t\t\tif (next && next.x !== undefined || Base.hasNamed(args, 'to')) {\n\t\t\t\tvar to = Point.readNamed(args, 'to');\n\t\t\t\twidth = to.x - x;\n\t\t\t\theight = to.y - y;\n\t\t\t\tif (width < 0) {\n\t\t\t\t\tx = to.x;\n\t\t\t\t\twidth = -width;\n\t\t\t\t}\n\t\t\t\tif (height < 0) {\n\t\t\t\t\ty = to.y;\n\t\t\t\t\theight = -height;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar size = Size.read(args);\n\t\t\t\twidth = size.width;\n\t\t\t\theight = size.height;\n\t\t\t}\n\t\t\tthis._set(x, y, width, height);\n\t\t\tread = args.__index;\n\t\t}\n\t\tvar filtered = args.__filtered;\n\t\tif (filtered)\n\t\t\tthis.__filtered = filtered;\n\t\tif (this.__read)\n\t\t\tthis.__read = read;\n\t\treturn this;\n\t},\n\n\tset: '#initialize',\n\n\t_set: function(x, y, width, height) {\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\treturn this;\n\t},\n\n\tclone: function() {\n\t\treturn new Rectangle(this.x, this.y, this.width, this.height);\n\t},\n\n\tequals: function(rect) {\n\t\tvar rt = Base.isPlainValue(rect)\n\t\t\t\t? Rectangle.read(arguments)\n\t\t\t\t: rect;\n\t\treturn rt === this\n\t\t\t\t|| rt && this.x === rt.x && this.y === rt.y\n\t\t\t\t\t&& this.width === rt.width && this.height === rt.height\n\t\t\t\t|| false;\n\t},\n\n\ttoString: function() {\n\t\tvar f = Formatter.instance;\n\t\treturn '{ x: ' + f.number(this.x)\n\t\t\t\t+ ', y: ' + f.number(this.y)\n\t\t\t\t+ ', width: ' + f.number(this.width)\n\t\t\t\t+ ', height: ' + f.number(this.height)\n\t\t\t\t+ ' }';\n\t},\n\n\t_serialize: function(options) {\n\t\tvar f = options.formatter;\n\t\treturn [f.number(this.x),\n\t\t\t\tf.number(this.y),\n\t\t\t\tf.number(this.width),\n\t\t\t\tf.number(this.height)];\n\t},\n\n\tgetPoint: function(_dontLink) {\n\t\tvar ctor = _dontLink ? Point : LinkedPoint;\n\t\treturn new ctor(this.x, this.y, this, 'setPoint');\n\t},\n\n\tsetPoint: function() {\n\t\tvar point = Point.read(arguments);\n\t\tthis.x = point.x;\n\t\tthis.y = point.y;\n\t},\n\n\tgetSize: function(_dontLink) {\n\t\tvar ctor = _dontLink ? Size : LinkedSize;\n\t\treturn new ctor(this.width, this.height, this, 'setSize');\n\t},\n\n\t_fw: 1,\n\t_fh: 1,\n\n\tsetSize: function() {\n\t\tvar size = Size.read(arguments),\n\t\t\tsx = this._sx,\n\t\t\tsy = this._sy,\n\t\t\tw = size.width,\n\t\t\th = size.height;\n\t\tif (sx) {\n\t\t\tthis.x += (this.width - w) * sx;\n\t\t}\n\t\tif (sy) {\n\t\t\tthis.y += (this.height - h) * sy;\n\t\t}\n\t\tthis.width = w;\n\t\tthis.height = h;\n\t\tthis._fw = this._fh = 1;\n\t},\n\n\tgetLeft: function() {\n\t\treturn this.x;\n\t},\n\n\tsetLeft: function(left) {\n\t\tif (!this._fw) {\n\t\t\tvar amount = left - this.x;\n\t\t\tthis.width -= this._sx === 0.5 ? amount * 2 : amount;\n\t\t}\n\t\tthis.x = left;\n\t\tthis._sx = this._fw = 0;\n\t},\n\n\tgetTop: function() {\n\t\treturn this.y;\n\t},\n\n\tsetTop: function(top) {\n\t\tif (!this._fh) {\n\t\t\tvar amount = top - this.y;\n\t\t\tthis.height -= this._sy === 0.5 ? amount * 2 : amount;\n\t\t}\n\t\tthis.y = top;\n\t\tthis._sy = this._fh = 0;\n\t},\n\n\tgetRight: function() {\n\t\treturn this.x + this.width;\n\t},\n\n\tsetRight: function(right) {\n\t\tif (!this._fw) {\n\t\t\tvar amount = right - this.x;\n\t\t\tthis.width = this._sx === 0.5 ? amount * 2 : amount;\n\t\t}\n\t\tthis.x = right - this.width;\n\t\tthis._sx = 1;\n\t\tthis._fw = 0;\n\t},\n\n\tgetBottom: function() {\n\t\treturn this.y + this.height;\n\t},\n\n\tsetBottom: function(bottom) {\n\t\tif (!this._fh) {\n\t\t\tvar amount = bottom - this.y;\n\t\t\tthis.height = this._sy === 0.5 ? amount * 2 : amount;\n\t\t}\n\t\tthis.y = bottom - this.height;\n\t\tthis._sy = 1;\n\t\tthis._fh = 0;\n\t},\n\n\tgetCenterX: function() {\n\t\treturn this.x + this.width / 2;\n\t},\n\n\tsetCenterX: function(x) {\n\t\tif (this._fw || this._sx === 0.5) {\n\t\t\tthis.x = x - this.width / 2;\n\t\t} else {\n\t\t\tif (this._sx) {\n\t\t\t\tthis.x += (x - this.x) * 2 * this._sx;\n\t\t\t}\n\t\t\tthis.width = (x - this.x) * 2;\n\t\t}\n\t\tthis._sx = 0.5;\n\t\tthis._fw = 0;\n\t},\n\n\tgetCenterY: function() {\n\t\treturn this.y + this.height / 2;\n\t},\n\n\tsetCenterY: function(y) {\n\t\tif (this._fh || this._sy === 0.5) {\n\t\t\tthis.y = y - this.height / 2;\n\t\t} else {\n\t\t\tif (this._sy) {\n\t\t\t\tthis.y += (y - this.y) * 2 * this._sy;\n\t\t\t}\n\t\t\tthis.height = (y - this.y) * 2;\n\t\t}\n\t\tthis._sy = 0.5;\n\t\tthis._fh = 0;\n\t},\n\n\tgetCenter: function(_dontLink) {\n\t\tvar ctor = _dontLink ? Point : LinkedPoint;\n\t\treturn new ctor(this.getCenterX(), this.getCenterY(), this, 'setCenter');\n\t},\n\n\tsetCenter: function() {\n\t\tvar point = Point.read(arguments);\n\t\tthis.setCenterX(point.x);\n\t\tthis.setCenterY(point.y);\n\t\treturn this;\n\t},\n\n\tgetArea: function() {\n\t\treturn this.width * this.height;\n\t},\n\n\tisEmpty: function() {\n\t\treturn this.width === 0 || this.height === 0;\n\t},\n\n\tcontains: function(arg) {\n\t\treturn arg && arg.width !== undefined\n\t\t\t\t|| (Array.isArray(arg) ? arg : arguments).length === 4\n\t\t\t\t? this._containsRectangle(Rectangle.read(arguments))\n\t\t\t\t: this._containsPoint(Point.read(arguments));\n\t},\n\n\t_containsPoint: function(point) {\n\t\tvar x = point.x,\n\t\t\ty = point.y;\n\t\treturn x >= this.x && y >= this.y\n\t\t\t\t&& x <= this.x + this.width\n\t\t\t\t&& y <= this.y + this.height;\n\t},\n\n\t_containsRectangle: function(rect) {\n\t\tvar x = rect.x,\n\t\t\ty = rect.y;\n\t\treturn x >= this.x && y >= this.y\n\t\t\t\t&& x + rect.width <= this.x + this.width\n\t\t\t\t&& y + rect.height <= this.y + this.height;\n\t},\n\n\tintersects: function() {\n\t\tvar rect = Rectangle.read(arguments),\n\t\t\tepsilon = Base.read(arguments) || 0;\n\t\treturn rect.x + rect.width > this.x - epsilon\n\t\t\t\t&& rect.y + rect.height > this.y - epsilon\n\t\t\t\t&& rect.x < this.x + this.width + epsilon\n\t\t\t\t&& rect.y < this.y + this.height + epsilon;\n\t},\n\n\tintersect: function() {\n\t\tvar rect = Rectangle.read(arguments),\n\t\t\tx1 = Math.max(this.x, rect.x),\n\t\t\ty1 = Math.max(this.y, rect.y),\n\t\t\tx2 = Math.min(this.x + this.width, rect.x + rect.width),\n\t\t\ty2 = Math.min(this.y + this.height, rect.y + rect.height);\n\t\treturn new Rectangle(x1, y1, x2 - x1, y2 - y1);\n\t},\n\n\tunite: function() {\n\t\tvar rect = Rectangle.read(arguments),\n\t\t\tx1 = Math.min(this.x, rect.x),\n\t\t\ty1 = Math.min(this.y, rect.y),\n\t\t\tx2 = Math.max(this.x + this.width, rect.x + rect.width),\n\t\t\ty2 = Math.max(this.y + this.height, rect.y + rect.height);\n\t\treturn new Rectangle(x1, y1, x2 - x1, y2 - y1);\n\t},\n\n\tinclude: function() {\n\t\tvar point = Point.read(arguments);\n\t\tvar x1 = Math.min(this.x, point.x),\n\t\t\ty1 = Math.min(this.y, point.y),\n\t\t\tx2 = Math.max(this.x + this.width, point.x),\n\t\t\ty2 = Math.max(this.y + this.height, point.y);\n\t\treturn new Rectangle(x1, y1, x2 - x1, y2 - y1);\n\t},\n\n\texpand: function() {\n\t\tvar amount = Size.read(arguments),\n\t\t\thor = amount.width,\n\t\t\tver = amount.height;\n\t\treturn new Rectangle(this.x - hor / 2, this.y - ver / 2,\n\t\t\t\tthis.width + hor, this.height + ver);\n\t},\n\n\tscale: function(hor, ver) {\n\t\treturn this.expand(this.width * hor - this.width,\n\t\t\t\tthis.height * (ver === undefined ? hor : ver) - this.height);\n\t}\n}, Base.each([\n\t\t['Top', 'Left'], ['Top', 'Right'],\n\t\t['Bottom', 'Left'], ['Bottom', 'Right'],\n\t\t['Left', 'Center'], ['Top', 'Center'],\n\t\t['Right', 'Center'], ['Bottom', 'Center']\n\t],\n\tfunction(parts, index) {\n\t\tvar part = parts.join(''),\n\t\t\txFirst = /^[RL]/.test(part);\n\t\tif (index >= 4)\n\t\t\tparts[1] += xFirst ? 'Y' : 'X';\n\t\tvar x = parts[xFirst ? 0 : 1],\n\t\t\ty = parts[xFirst ? 1 : 0],\n\t\t\tgetX = 'get' + x,\n\t\t\tgetY = 'get' + y,\n\t\t\tsetX = 'set' + x,\n\t\t\tsetY = 'set' + y,\n\t\t\tget = 'get' + part,\n\t\t\tset = 'set' + part;\n\t\tthis[get] = function(_dontLink) {\n\t\t\tvar ctor = _dontLink ? Point : LinkedPoint;\n\t\t\treturn new ctor(this[getX](), this[getY](), this, set);\n\t\t};\n\t\tthis[set] = function() {\n\t\t\tvar point = Point.read(arguments);\n\t\t\tthis[setX](point.x);\n\t\t\tthis[setY](point.y);\n\t\t};\n\t}, {\n\t\tbeans: true\n\t}\n));\n\nvar LinkedRectangle = Rectangle.extend({\n\tinitialize: function Rectangle(x, y, width, height, owner, setter) {\n\t\tthis._set(x, y, width, height, true);\n\t\tthis._owner = owner;\n\t\tthis._setter = setter;\n\t},\n\n\t_set: function(x, y, width, height, _dontNotify) {\n\t\tthis._x = x;\n\t\tthis._y = y;\n\t\tthis._width = width;\n\t\tthis._height = height;\n\t\tif (!_dontNotify)\n\t\t\tthis._owner[this._setter](this);\n\t\treturn this;\n\t}\n},\nnew function() {\n\tvar proto = Rectangle.prototype;\n\n\treturn Base.each(['x', 'y', 'width', 'height'], function(key) {\n\t\tvar part = Base.capitalize(key),\n\t\t\tinternal = '_' + key;\n\t\tthis['get' + part] = function() {\n\t\t\treturn this[internal];\n\t\t};\n\n\t\tthis['set' + part] = function(value) {\n\t\t\tthis[internal] = value;\n\t\t\tif (!this._dontNotify)\n\t\t\t\tthis._owner[this._setter](this);\n\t\t};\n\t}, Base.each(['Point', 'Size', 'Center',\n\t\t\t'Left', 'Top', 'Right', 'Bottom', 'CenterX', 'CenterY',\n\t\t\t'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',\n\t\t\t'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'],\n\t\tfunction(key) {\n\t\t\tvar name = 'set' + key;\n\t\t\tthis[name] = function() {\n\t\t\t\tthis._dontNotify = true;\n\t\t\t\tproto[name].apply(this, arguments);\n\t\t\t\tthis._dontNotify = false;\n\t\t\t\tthis._owner[this._setter](this);\n\t\t\t};\n\t\t}, {\n\t\t\tisSelected: function() {\n\t\t\t\treturn !!(this._owner._selection & 2);\n\t\t\t},\n\n\t\t\tsetSelected: function(selected) {\n\t\t\t\tvar owner = this._owner;\n\t\t\t\tif (owner._changeSelection) {\n\t\t\t\t\towner._changeSelection(2, selected);\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t);\n});\n\nvar Matrix = Base.extend({\n\t_class: 'Matrix',\n\n\tinitialize: function Matrix(arg, _dontNotify) {\n\t\tvar args = arguments,\n\t\t\tcount = args.length,\n\t\t\tok = true;\n\t\tif (count >= 6) {\n\t\t\tthis._set.apply(this, args);\n\t\t} else if (count === 1 || count === 2) {\n\t\t\tif (arg instanceof Matrix) {\n\t\t\t\tthis._set(arg._a, arg._b, arg._c, arg._d, arg._tx, arg._ty,\n\t\t\t\t\t\t_dontNotify);\n\t\t\t} else if (Array.isArray(arg)) {\n\t\t\t\tthis._set.apply(this,\n\t\t\t\t\t\t_dontNotify ? arg.concat([_dontNotify]) : arg);\n\t\t\t} else {\n\t\t\t\tok = false;\n\t\t\t}\n\t\t} else if (!count) {\n\t\t\tthis.reset();\n\t\t} else {\n\t\t\tok = false;\n\t\t}\n\t\tif (!ok) {\n\t\t\tthrow new Error('Unsupported matrix parameters');\n\t\t}\n\t\treturn this;\n\t},\n\n\tset: '#initialize',\n\n\t_set: function(a, b, c, d, tx, ty, _dontNotify) {\n\t\tthis._a = a;\n\t\tthis._b = b;\n\t\tthis._c = c;\n\t\tthis._d = d;\n\t\tthis._tx = tx;\n\t\tthis._ty = ty;\n\t\tif (!_dontNotify)\n\t\t\tthis._changed();\n\t\treturn this;\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\treturn Base.serialize(this.getValues(), options, true, dictionary);\n\t},\n\n\t_changed: function() {\n\t\tvar owner = this._owner;\n\t\tif (owner) {\n\t\t\tif (owner._applyMatrix) {\n\t\t\t\towner.transform(null, true);\n\t\t\t} else {\n\t\t\t\towner._changed(25);\n\t\t\t}\n\t\t}\n\t},\n\n\tclone: function() {\n\t\treturn new Matrix(this._a, this._b, this._c, this._d,\n\t\t\t\tthis._tx, this._ty);\n\t},\n\n\tequals: function(mx) {\n\t\treturn mx === this || mx && this._a === mx._a && this._b === mx._b\n\t\t\t\t&& this._c === mx._c && this._d === mx._d\n\t\t\t\t&& this._tx === mx._tx && this._ty === mx._ty;\n\t},\n\n\ttoString: function() {\n\t\tvar f = Formatter.instance;\n\t\treturn '[[' + [f.number(this._a), f.number(this._c),\n\t\t\t\t\tf.number(this._tx)].join(', ') + '], ['\n\t\t\t\t+ [f.number(this._b), f.number(this._d),\n\t\t\t\t\tf.number(this._ty)].join(', ') + ']]';\n\t},\n\n\treset: function(_dontNotify) {\n\t\tthis._a = this._d = 1;\n\t\tthis._b = this._c = this._tx = this._ty = 0;\n\t\tif (!_dontNotify)\n\t\t\tthis._changed();\n\t\treturn this;\n\t},\n\n\tapply: function(recursively, _setApplyMatrix) {\n\t\tvar owner = this._owner;\n\t\tif (owner) {\n\t\t\towner.transform(null, Base.pick(recursively, true), _setApplyMatrix);\n\t\t\treturn this.isIdentity();\n\t\t}\n\t\treturn false;\n\t},\n\n\ttranslate: function() {\n\t\tvar point = Point.read(arguments),\n\t\t\tx = point.x,\n\t\t\ty = point.y;\n\t\tthis._tx += x * this._a + y * this._c;\n\t\tthis._ty += x * this._b + y * this._d;\n\t\tthis._changed();\n\t\treturn this;\n\t},\n\n\tscale: function() {\n\t\tvar args = arguments,\n\t\t\tscale = Point.read(args),\n\t\t\tcenter = Point.read(args, 0, { readNull: true });\n\t\tif (center)\n\t\t\tthis.translate(center);\n\t\tthis._a *= scale.x;\n\t\tthis._b *= scale.x;\n\t\tthis._c *= scale.y;\n\t\tthis._d *= scale.y;\n\t\tif (center)\n\t\t\tthis.translate(center.negate());\n\t\tthis._changed();\n\t\treturn this;\n\t},\n\n\trotate: function(angle ) {\n\t\tangle *= Math.PI / 180;\n\t\tvar center = Point.read(arguments, 1),\n\t\t\tx = center.x,\n\t\t\ty = center.y,\n\t\t\tcos = Math.cos(angle),\n\t\t\tsin = Math.sin(angle),\n\t\t\ttx = x - x * cos + y * sin,\n\t\t\tty = y - x * sin - y * cos,\n\t\t\ta = this._a,\n\t\t\tb = this._b,\n\t\t\tc = this._c,\n\t\t\td = this._d;\n\t\tthis._a = cos * a + sin * c;\n\t\tthis._b = cos * b + sin * d;\n\t\tthis._c = -sin * a + cos * c;\n\t\tthis._d = -sin * b + cos * d;\n\t\tthis._tx += tx * a + ty * c;\n\t\tthis._ty += tx * b + ty * d;\n\t\tthis._changed();\n\t\treturn this;\n\t},\n\n\tshear: function() {\n\t\tvar args = arguments,\n\t\t\tshear = Point.read(args),\n\t\t\tcenter = Point.read(args, 0, { readNull: true });\n\t\tif (center)\n\t\t\tthis.translate(center);\n\t\tvar a = this._a,\n\t\t\tb = this._b;\n\t\tthis._a += shear.y * this._c;\n\t\tthis._b += shear.y * this._d;\n\t\tthis._c += shear.x * a;\n\t\tthis._d += shear.x * b;\n\t\tif (center)\n\t\t\tthis.translate(center.negate());\n\t\tthis._changed();\n\t\treturn this;\n\t},\n\n\tskew: function() {\n\t\tvar args = arguments,\n\t\t\tskew = Point.read(args),\n\t\t\tcenter = Point.read(args, 0, { readNull: true }),\n\t\t\ttoRadians = Math.PI / 180,\n\t\t\tshear = new Point(Math.tan(skew.x * toRadians),\n\t\t\t\tMath.tan(skew.y * toRadians));\n\t\treturn this.shear(shear, center);\n\t},\n\n\tappend: function(mx, _dontNotify) {\n\t\tif (mx) {\n\t\t\tvar a1 = this._a,\n\t\t\t\tb1 = this._b,\n\t\t\t\tc1 = this._c,\n\t\t\t\td1 = this._d,\n\t\t\t\ta2 = mx._a,\n\t\t\t\tb2 = mx._c,\n\t\t\t\tc2 = mx._b,\n\t\t\t\td2 = mx._d,\n\t\t\t\ttx2 = mx._tx,\n\t\t\t\tty2 = mx._ty;\n\t\t\tthis._a = a2 * a1 + c2 * c1;\n\t\t\tthis._c = b2 * a1 + d2 * c1;\n\t\t\tthis._b = a2 * b1 + c2 * d1;\n\t\t\tthis._d = b2 * b1 + d2 * d1;\n\t\t\tthis._tx += tx2 * a1 + ty2 * c1;\n\t\t\tthis._ty += tx2 * b1 + ty2 * d1;\n\t\t\tif (!_dontNotify)\n\t\t\t\tthis._changed();\n\t\t}\n\t\treturn this;\n\t},\n\n\tprepend: function(mx, _dontNotify) {\n\t\tif (mx) {\n\t\t\tvar a1 = this._a,\n\t\t\t\tb1 = this._b,\n\t\t\t\tc1 = this._c,\n\t\t\t\td1 = this._d,\n\t\t\t\ttx1 = this._tx,\n\t\t\t\tty1 = this._ty,\n\t\t\t\ta2 = mx._a,\n\t\t\t\tb2 = mx._c,\n\t\t\t\tc2 = mx._b,\n\t\t\t\td2 = mx._d,\n\t\t\t\ttx2 = mx._tx,\n\t\t\t\tty2 = mx._ty;\n\t\t\tthis._a = a2 * a1 + b2 * b1;\n\t\t\tthis._c = a2 * c1 + b2 * d1;\n\t\t\tthis._b = c2 * a1 + d2 * b1;\n\t\t\tthis._d = c2 * c1 + d2 * d1;\n\t\t\tthis._tx = a2 * tx1 + b2 * ty1 + tx2;\n\t\t\tthis._ty = c2 * tx1 + d2 * ty1 + ty2;\n\t\t\tif (!_dontNotify)\n\t\t\t\tthis._changed();\n\t\t}\n\t\treturn this;\n\t},\n\n\tappended: function(mx) {\n\t\treturn this.clone().append(mx);\n\t},\n\n\tprepended: function(mx) {\n\t\treturn this.clone().prepend(mx);\n\t},\n\n\tinvert: function() {\n\t\tvar a = this._a,\n\t\t\tb = this._b,\n\t\t\tc = this._c,\n\t\t\td = this._d,\n\t\t\ttx = this._tx,\n\t\t\tty = this._ty,\n\t\t\tdet = a * d - b * c,\n\t\t\tres = null;\n\t\tif (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) {\n\t\t\tthis._a = d / det;\n\t\t\tthis._b = -b / det;\n\t\t\tthis._c = -c / det;\n\t\t\tthis._d = a / det;\n\t\t\tthis._tx = (c * ty - d * tx) / det;\n\t\t\tthis._ty = (b * tx - a * ty) / det;\n\t\t\tres = this;\n\t\t}\n\t\treturn res;\n\t},\n\n\tinverted: function() {\n\t\treturn this.clone().invert();\n\t},\n\n\tconcatenate: '#append',\n\tpreConcatenate: '#prepend',\n\tchain: '#appended',\n\n\t_shiftless: function() {\n\t\treturn new Matrix(this._a, this._b, this._c, this._d, 0, 0);\n\t},\n\n\t_orNullIfIdentity: function() {\n\t\treturn this.isIdentity() ? null : this;\n\t},\n\n\tisIdentity: function() {\n\t\treturn this._a === 1 && this._b === 0 && this._c === 0 && this._d === 1\n\t\t\t\t&& this._tx === 0 && this._ty === 0;\n\t},\n\n\tisInvertible: function() {\n\t\tvar det = this._a * this._d - this._c * this._b;\n\t\treturn det && !isNaN(det) && isFinite(this._tx) && isFinite(this._ty);\n\t},\n\n\tisSingular: function() {\n\t\treturn !this.isInvertible();\n\t},\n\n\ttransform: function( src, dst, count) {\n\t\treturn arguments.length < 3\n\t\t\t? this._transformPoint(Point.read(arguments))\n\t\t\t: this._transformCoordinates(src, dst, count);\n\t},\n\n\t_transformPoint: function(point, dest, _dontNotify) {\n\t\tvar x = point.x,\n\t\t\ty = point.y;\n\t\tif (!dest)\n\t\t\tdest = new Point();\n\t\treturn dest._set(\n\t\t\t\tx * this._a + y * this._c + this._tx,\n\t\t\t\tx * this._b + y * this._d + this._ty,\n\t\t\t\t_dontNotify);\n\t},\n\n\t_transformCoordinates: function(src, dst, count) {\n\t\tfor (var i = 0, max = 2 * count; i < max; i += 2) {\n\t\t\tvar x = src[i],\n\t\t\t\ty = src[i + 1];\n\t\t\tdst[i] = x * this._a + y * this._c + this._tx;\n\t\t\tdst[i + 1] = x * this._b + y * this._d + this._ty;\n\t\t}\n\t\treturn dst;\n\t},\n\n\t_transformCorners: function(rect) {\n\t\tvar x1 = rect.x,\n\t\t\ty1 = rect.y,\n\t\t\tx2 = x1 + rect.width,\n\t\t\ty2 = y1 + rect.height,\n\t\t\tcoords = [ x1, y1, x2, y1, x2, y2, x1, y2 ];\n\t\treturn this._transformCoordinates(coords, coords, 4);\n\t},\n\n\t_transformBounds: function(bounds, dest, _dontNotify) {\n\t\tvar coords = this._transformCorners(bounds),\n\t\t\tmin = coords.slice(0, 2),\n\t\t\tmax = min.slice();\n\t\tfor (var i = 2; i < 8; i++) {\n\t\t\tvar val = coords[i],\n\t\t\t\tj = i & 1;\n\t\t\tif (val < min[j]) {\n\t\t\t\tmin[j] = val;\n\t\t\t} else if (val > max[j]) {\n\t\t\t\tmax[j] = val;\n\t\t\t}\n\t\t}\n\t\tif (!dest)\n\t\t\tdest = new Rectangle();\n\t\treturn dest._set(min[0], min[1], max[0] - min[0], max[1] - min[1],\n\t\t\t\t_dontNotify);\n\t},\n\n\tinverseTransform: function() {\n\t\treturn this._inverseTransform(Point.read(arguments));\n\t},\n\n\t_inverseTransform: function(point, dest, _dontNotify) {\n\t\tvar a = this._a,\n\t\t\tb = this._b,\n\t\t\tc = this._c,\n\t\t\td = this._d,\n\t\t\ttx = this._tx,\n\t\t\tty = this._ty,\n\t\t\tdet = a * d - b * c,\n\t\t\tres = null;\n\t\tif (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) {\n\t\t\tvar x = point.x - this._tx,\n\t\t\t\ty = point.y - this._ty;\n\t\t\tif (!dest)\n\t\t\t\tdest = new Point();\n\t\t\tres = dest._set(\n\t\t\t\t\t(x * d - y * c) / det,\n\t\t\t\t\t(y * a - x * b) / det,\n\t\t\t\t\t_dontNotify);\n\t\t}\n\t\treturn res;\n\t},\n\n\tdecompose: function() {\n\t\tvar a = this._a,\n\t\t\tb = this._b,\n\t\t\tc = this._c,\n\t\t\td = this._d,\n\t\t\tdet = a * d - b * c,\n\t\t\tsqrt = Math.sqrt,\n\t\t\tatan2 = Math.atan2,\n\t\t\tdegrees = 180 / Math.PI,\n\t\t\trotate,\n\t\t\tscale,\n\t\t\tskew;\n\t\tif (a !== 0 || b !== 0) {\n\t\t\tvar r = sqrt(a * a + b * b);\n\t\t\trotate = Math.acos(a / r) * (b > 0 ? 1 : -1);\n\t\t\tscale = [r, det / r];\n\t\t\tskew = [atan2(a * c + b * d, r * r), 0];\n\t\t} else if (c !== 0 || d !== 0) {\n\t\t\tvar s = sqrt(c * c + d * d);\n\t\t\trotate = Math.asin(c / s) * (d > 0 ? 1 : -1);\n\t\t\tscale = [det / s, s];\n\t\t\tskew = [0, atan2(a * c + b * d, s * s)];\n\t\t} else {\n\t\t\trotate = 0;\n\t\t\tskew = scale = [0, 0];\n\t\t}\n\t\treturn {\n\t\t\ttranslation: this.getTranslation(),\n\t\t\trotation: rotate * degrees,\n\t\t\tscaling: new Point(scale),\n\t\t\tskewing: new Point(skew[0] * degrees, skew[1] * degrees)\n\t\t};\n\t},\n\n\tgetValues: function() {\n\t\treturn [ this._a, this._b, this._c, this._d, this._tx, this._ty ];\n\t},\n\n\tgetTranslation: function() {\n\t\treturn new Point(this._tx, this._ty);\n\t},\n\n\tgetScaling: function() {\n\t\treturn this.decompose().scaling;\n\t},\n\n\tgetRotation: function() {\n\t\treturn this.decompose().rotation;\n\t},\n\n\tapplyToContext: function(ctx) {\n\t\tif (!this.isIdentity()) {\n\t\t\tctx.transform(this._a, this._b, this._c, this._d,\n\t\t\t\t\tthis._tx, this._ty);\n\t\t}\n\t}\n}, Base.each(['a', 'b', 'c', 'd', 'tx', 'ty'], function(key) {\n\tvar part = Base.capitalize(key),\n\t\tprop = '_' + key;\n\tthis['get' + part] = function() {\n\t\treturn this[prop];\n\t};\n\tthis['set' + part] = function(value) {\n\t\tthis[prop] = value;\n\t\tthis._changed();\n\t};\n}, {}));\n\nvar Line = Base.extend({\n\t_class: 'Line',\n\n\tinitialize: function Line(arg0, arg1, arg2, arg3, arg4) {\n\t\tvar asVector = false;\n\t\tif (arguments.length >= 4) {\n\t\t\tthis._px = arg0;\n\t\t\tthis._py = arg1;\n\t\t\tthis._vx = arg2;\n\t\t\tthis._vy = arg3;\n\t\t\tasVector = arg4;\n\t\t} else {\n\t\t\tthis._px = arg0.x;\n\t\t\tthis._py = arg0.y;\n\t\t\tthis._vx = arg1.x;\n\t\t\tthis._vy = arg1.y;\n\t\t\tasVector = arg2;\n\t\t}\n\t\tif (!asVector) {\n\t\t\tthis._vx -= this._px;\n\t\t\tthis._vy -= this._py;\n\t\t}\n\t},\n\n\tgetPoint: function() {\n\t\treturn new Point(this._px, this._py);\n\t},\n\n\tgetVector: function() {\n\t\treturn new Point(this._vx, this._vy);\n\t},\n\n\tgetLength: function() {\n\t\treturn this.getVector().getLength();\n\t},\n\n\tintersect: function(line, isInfinite) {\n\t\treturn Line.intersect(\n\t\t\t\tthis._px, this._py, this._vx, this._vy,\n\t\t\t\tline._px, line._py, line._vx, line._vy,\n\t\t\t\ttrue, isInfinite);\n\t},\n\n\tgetSide: function(point, isInfinite) {\n\t\treturn Line.getSide(\n\t\t\t\tthis._px, this._py, this._vx, this._vy,\n\t\t\t\tpoint.x, point.y, true, isInfinite);\n\t},\n\n\tgetDistance: function(point) {\n\t\treturn Math.abs(this.getSignedDistance(point));\n\t},\n\n\tgetSignedDistance: function(point) {\n\t\treturn Line.getSignedDistance(this._px, this._py, this._vx, this._vy,\n\t\t\t\tpoint.x, point.y, true);\n\t},\n\n\tisCollinear: function(line) {\n\t\treturn Point.isCollinear(this._vx, this._vy, line._vx, line._vy);\n\t},\n\n\tisOrthogonal: function(line) {\n\t\treturn Point.isOrthogonal(this._vx, this._vy, line._vx, line._vy);\n\t},\n\n\tstatics: {\n\t\tintersect: function(p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector,\n\t\t\t\tisInfinite) {\n\t\t\tif (!asVector) {\n\t\t\t\tv1x -= p1x;\n\t\t\t\tv1y -= p1y;\n\t\t\t\tv2x -= p2x;\n\t\t\t\tv2y -= p2y;\n\t\t\t}\n\t\t\tvar cross = v1x * v2y - v1y * v2x;\n\t\t\tif (!Numerical.isMachineZero(cross)) {\n\t\t\t\tvar dx = p1x - p2x,\n\t\t\t\t\tdy = p1y - p2y,\n\t\t\t\t\tu1 = (v2x * dy - v2y * dx) / cross,\n\t\t\t\t\tu2 = (v1x * dy - v1y * dx) / cross,\n\t\t\t\t\tepsilon = 1e-12,\n\t\t\t\t\tuMin = -epsilon,\n\t\t\t\t\tuMax = 1 + epsilon;\n\t\t\t\tif (isInfinite\n\t\t\t\t\t\t|| uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) {\n\t\t\t\t\tif (!isInfinite) {\n\t\t\t\t\t\tu1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1;\n\t\t\t\t\t}\n\t\t\t\t\treturn new Point(\n\t\t\t\t\t\t\tp1x + u1 * v1x,\n\t\t\t\t\t\t\tp1y + u1 * v1y);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tgetSide: function(px, py, vx, vy, x, y, asVector, isInfinite) {\n\t\t\tif (!asVector) {\n\t\t\t\tvx -= px;\n\t\t\t\tvy -= py;\n\t\t\t}\n\t\t\tvar v2x = x - px,\n\t\t\t\tv2y = y - py,\n\t\t\t\tccw = v2x * vy - v2y * vx;\n\t\t\tif (!isInfinite && Numerical.isMachineZero(ccw)) {\n\t\t\t\tccw = (v2x * vx + v2x * vx) / (vx * vx + vy * vy);\n\t\t\t\tif (ccw >= 0 && ccw <= 1)\n\t\t\t\t\tccw = 0;\n\t\t\t}\n\t\t\treturn ccw < 0 ? -1 : ccw > 0 ? 1 : 0;\n\t\t},\n\n\t\tgetSignedDistance: function(px, py, vx, vy, x, y, asVector) {\n\t\t\tif (!asVector) {\n\t\t\t\tvx -= px;\n\t\t\t\tvy -= py;\n\t\t\t}\n\t\t\t return vx === 0 ? (vy > 0 ? x - px : px - x)\n\t\t\t\t\t: vy === 0 ? (vx < 0 ? y - py : py - y)\n\t\t\t\t\t: ((x - px) * vy - (y - py) * vx) / (\n\t\t\t\t\t\tvy > vx\n\t\t\t\t\t\t\t? vy * Math.sqrt(1 + (vx * vx) / (vy * vy))\n\t\t\t\t\t\t\t: vx * Math.sqrt(1 + (vy * vy) / (vx * vx))\n\t\t\t\t\t);\n\t\t},\n\n\t\tgetDistance: function(px, py, vx, vy, x, y, asVector) {\n\t\t\treturn Math.abs(\n\t\t\t\t\tLine.getSignedDistance(px, py, vx, vy, x, y, asVector));\n\t\t}\n\t}\n});\n\nvar Project = PaperScopeItem.extend({\n\t_class: 'Project',\n\t_list: 'projects',\n\t_reference: 'project',\n\t_compactSerialize: true,\n\n\tinitialize: function Project(element) {\n\t\tPaperScopeItem.call(this, true);\n\t\tthis._children = [];\n\t\tthis._namedChildren = {};\n\t\tthis._activeLayer = null;\n\t\tthis._currentStyle = new Style(null, null, this);\n\t\tthis._view = View.create(this,\n\t\t\t\telement || CanvasProvider.getCanvas(1, 1));\n\t\tthis._selectionItems = {};\n\t\tthis._selectionCount = 0;\n\t\tthis._updateVersion = 0;\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\treturn Base.serialize(this._children, options, true, dictionary);\n\t},\n\n\t_changed: function(flags, item) {\n\t\tif (flags & 1) {\n\t\t\tvar view = this._view;\n\t\t\tif (view) {\n\t\t\t\tview._needsUpdate = true;\n\t\t\t\tif (!view._requested && view._autoUpdate)\n\t\t\t\t\tview.requestUpdate();\n\t\t\t}\n\t\t}\n\t\tvar changes = this._changes;\n\t\tif (changes && item) {\n\t\t\tvar changesById = this._changesById,\n\t\t\t\tid = item._id,\n\t\t\t\tentry = changesById[id];\n\t\t\tif (entry) {\n\t\t\t\tentry.flags |= flags;\n\t\t\t} else {\n\t\t\t\tchanges.push(changesById[id] = { item: item, flags: flags });\n\t\t\t}\n\t\t}\n\t},\n\n\tclear: function() {\n\t\tvar children = this._children;\n\t\tfor (var i = children.length - 1; i >= 0; i--)\n\t\t\tchildren[i].remove();\n\t},\n\n\tisEmpty: function() {\n\t\treturn !this._children.length;\n\t},\n\n\tremove: function remove() {\n\t\tif (!remove.base.call(this))\n\t\t\treturn false;\n\t\tif (this._view)\n\t\t\tthis._view.remove();\n\t\treturn true;\n\t},\n\n\tgetView: function() {\n\t\treturn this._view;\n\t},\n\n\tgetCurrentStyle: function() {\n\t\treturn this._currentStyle;\n\t},\n\n\tsetCurrentStyle: function(style) {\n\t\tthis._currentStyle.set(style);\n\t},\n\n\tgetIndex: function() {\n\t\treturn this._index;\n\t},\n\n\tgetOptions: function() {\n\t\treturn this._scope.settings;\n\t},\n\n\tgetLayers: function() {\n\t\treturn this._children;\n\t},\n\n\tgetActiveLayer: function() {\n\t\treturn this._activeLayer || new Layer({ project: this, insert: true });\n\t},\n\n\tgetSymbolDefinitions: function() {\n\t\tvar definitions = [],\n\t\t\tids = {};\n\t\tthis.getItems({\n\t\t\tclass: SymbolItem,\n\t\t\tmatch: function(item) {\n\t\t\t\tvar definition = item._definition,\n\t\t\t\t\tid = definition._id;\n\t\t\t\tif (!ids[id]) {\n\t\t\t\t\tids[id] = true;\n\t\t\t\t\tdefinitions.push(definition);\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t});\n\t\treturn definitions;\n\t},\n\n\tgetSymbols: 'getSymbolDefinitions',\n\n\tgetSelectedItems: function() {\n\t\tvar selectionItems = this._selectionItems,\n\t\t\titems = [];\n\t\tfor (var id in selectionItems) {\n\t\t\tvar item = selectionItems[id],\n\t\t\t\tselection = item._selection;\n\t\t\tif ((selection & 1) && item.isInserted()) {\n\t\t\t\titems.push(item);\n\t\t\t} else if (!selection) {\n\t\t\t\tthis._updateSelection(item);\n\t\t\t}\n\t\t}\n\t\treturn items;\n\t},\n\n\t_updateSelection: function(item) {\n\t\tvar id = item._id,\n\t\t\tselectionItems = this._selectionItems;\n\t\tif (item._selection) {\n\t\t\tif (selectionItems[id] !== item) {\n\t\t\t\tthis._selectionCount++;\n\t\t\t\tselectionItems[id] = item;\n\t\t\t}\n\t\t} else if (selectionItems[id] === item) {\n\t\t\tthis._selectionCount--;\n\t\t\tdelete selectionItems[id];\n\t\t}\n\t},\n\n\tselectAll: function() {\n\t\tvar children = this._children;\n\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\tchildren[i].setFullySelected(true);\n\t},\n\n\tdeselectAll: function() {\n\t\tvar selectionItems = this._selectionItems;\n\t\tfor (var i in selectionItems)\n\t\t\tselectionItems[i].setFullySelected(false);\n\t},\n\n\taddLayer: function(layer) {\n\t\treturn this.insertLayer(undefined, layer);\n\t},\n\n\tinsertLayer: function(index, layer) {\n\t\tif (layer instanceof Layer) {\n\t\t\tlayer._remove(false, true);\n\t\t\tBase.splice(this._children, [layer], index, 0);\n\t\t\tlayer._setProject(this, true);\n\t\t\tvar name = layer._name;\n\t\t\tif (name)\n\t\t\t\tlayer.setName(name);\n\t\t\tif (this._changes)\n\t\t\t\tlayer._changed(5);\n\t\t\tif (!this._activeLayer)\n\t\t\t\tthis._activeLayer = layer;\n\t\t} else {\n\t\t\tlayer = null;\n\t\t}\n\t\treturn layer;\n\t},\n\n\t_insertItem: function(index, item, _created) {\n\t\titem = this.insertLayer(index, item)\n\t\t\t\t|| (this._activeLayer || this._insertItem(undefined,\n\t\t\t\t\t\tnew Layer(Item.NO_INSERT), true))\n\t\t\t\t\t\t.insertChild(index, item);\n\t\tif (_created && item.activate)\n\t\t\titem.activate();\n\t\treturn item;\n\t},\n\n\tgetItems: function(options) {\n\t\treturn Item._getItems(this, options);\n\t},\n\n\tgetItem: function(options) {\n\t\treturn Item._getItems(this, options, null, null, true)[0] || null;\n\t},\n\n\timportJSON: function(json) {\n\t\tthis.activate();\n\t\tvar layer = this._activeLayer;\n\t\treturn Base.importJSON(json, layer && layer.isEmpty() && layer);\n\t},\n\n\tremoveOn: function(type) {\n\t\tvar sets = this._removeSets;\n\t\tif (sets) {\n\t\t\tif (type === 'mouseup')\n\t\t\t\tsets.mousedrag = null;\n\t\t\tvar set = sets[type];\n\t\t\tif (set) {\n\t\t\t\tfor (var id in set) {\n\t\t\t\t\tvar item = set[id];\n\t\t\t\t\tfor (var key in sets) {\n\t\t\t\t\t\tvar other = sets[key];\n\t\t\t\t\t\tif (other && other != set)\n\t\t\t\t\t\t\tdelete other[item._id];\n\t\t\t\t\t}\n\t\t\t\t\titem.remove();\n\t\t\t\t}\n\t\t\t\tsets[type] = null;\n\t\t\t}\n\t\t}\n\t},\n\n\tdraw: function(ctx, matrix, pixelRatio) {\n\t\tthis._updateVersion++;\n\t\tctx.save();\n\t\tmatrix.applyToContext(ctx);\n\t\tvar children = this._children,\n\t\t\tparam = new Base({\n\t\t\t\toffset: new Point(0, 0),\n\t\t\t\tpixelRatio: pixelRatio,\n\t\t\t\tviewMatrix: matrix.isIdentity() ? null : matrix,\n\t\t\t\tmatrices: [new Matrix()],\n\t\t\t\tupdateMatrix: true\n\t\t\t});\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tchildren[i].draw(ctx, param);\n\t\t}\n\t\tctx.restore();\n\n\t\tif (this._selectionCount > 0) {\n\t\t\tctx.save();\n\t\t\tctx.strokeWidth = 1;\n\t\t\tvar items = this._selectionItems,\n\t\t\t\tsize = this._scope.settings.handleSize,\n\t\t\t\tversion = this._updateVersion;\n\t\t\tfor (var id in items) {\n\t\t\t\titems[id]._drawSelection(ctx, matrix, size, items, version);\n\t\t\t}\n\t\t\tctx.restore();\n\t\t}\n\t}\n});\n\nvar Item = Base.extend(Emitter, {\n\tstatics: {\n\t\textend: function extend(src) {\n\t\t\tif (src._serializeFields)\n\t\t\t\tsrc._serializeFields = Base.set({},\n\t\t\t\t\tthis.prototype._serializeFields, src._serializeFields);\n\t\t\treturn extend.base.apply(this, arguments);\n\t\t},\n\n\t\tNO_INSERT: { insert: false }\n\t},\n\n\t_class: 'Item',\n\t_name: null,\n\t_applyMatrix: true,\n\t_canApplyMatrix: true,\n\t_canScaleStroke: false,\n\t_pivot: null,\n\t_visible: true,\n\t_blendMode: 'normal',\n\t_opacity: 1,\n\t_locked: false,\n\t_guide: false,\n\t_clipMask: false,\n\t_selection: 0,\n\t_selectBounds: true,\n\t_selectChildren: false,\n\t_serializeFields: {\n\t\tname: null,\n\t\tapplyMatrix: null,\n\t\tmatrix: new Matrix(),\n\t\tpivot: null,\n\t\tvisible: true,\n\t\tblendMode: 'normal',\n\t\topacity: 1,\n\t\tlocked: false,\n\t\tguide: false,\n\t\tclipMask: false,\n\t\tselected: false,\n\t\tdata: {}\n\t},\n\t_prioritize: ['applyMatrix']\n},\nnew function() {\n\tvar handlers = ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',\n\t\t\t'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'];\n\treturn Base.each(handlers,\n\t\tfunction(name) {\n\t\t\tthis._events[name] = {\n\t\t\t\tinstall: function(type) {\n\t\t\t\t\tthis.getView()._countItemEvent(type, 1);\n\t\t\t\t},\n\n\t\t\t\tuninstall: function(type) {\n\t\t\t\t\tthis.getView()._countItemEvent(type, -1);\n\t\t\t\t}\n\t\t\t};\n\t\t}, {\n\t\t\t_events: {\n\t\t\t\tonFrame: {\n\t\t\t\t\tinstall: function() {\n\t\t\t\t\t\tthis.getView()._animateItem(this, true);\n\t\t\t\t\t},\n\n\t\t\t\t\tuninstall: function() {\n\t\t\t\t\t\tthis.getView()._animateItem(this, false);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tonLoad: {},\n\t\t\t\tonError: {}\n\t\t\t},\n\t\t\tstatics: {\n\t\t\t\t_itemHandlers: handlers\n\t\t\t}\n\t\t}\n\t);\n}, {\n\tinitialize: function Item() {\n\t},\n\n\t_initialize: function(props, point) {\n\t\tvar hasProps = props && Base.isPlainObject(props),\n\t\t\tinternal = hasProps && props.internal === true,\n\t\t\tmatrix = this._matrix = new Matrix(),\n\t\t\tproject = hasProps && props.project || paper.project,\n\t\t\tsettings = paper.settings;\n\t\tthis._id = internal ? null : UID.get();\n\t\tthis._parent = this._index = null;\n\t\tthis._applyMatrix = this._canApplyMatrix && settings.applyMatrix;\n\t\tif (point)\n\t\t\tmatrix.translate(point);\n\t\tmatrix._owner = this;\n\t\tthis._style = new Style(project._currentStyle, this, project);\n\t\tif (internal || hasProps && props.insert == false\n\t\t\t|| !settings.insertItems && !(hasProps && props.insert === true)) {\n\t\t\tthis._setProject(project);\n\t\t} else {\n\t\t\t(hasProps && props.parent || project)\n\t\t\t\t\t._insertItem(undefined, this, true);\n\t\t}\n\t\tif (hasProps && props !== Item.NO_INSERT) {\n\t\t\tthis.set(props, {\n\t\t\t\tinternal: true, insert: true, project: true, parent: true\n\t\t\t});\n\t\t}\n\t\treturn hasProps;\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\tvar props = {},\n\t\t\tthat = this;\n\n\t\tfunction serialize(fields) {\n\t\t\tfor (var key in fields) {\n\t\t\t\tvar value = that[key];\n\t\t\t\tif (!Base.equals(value, key === 'leading'\n\t\t\t\t\t\t? fields.fontSize * 1.2 : fields[key])) {\n\t\t\t\t\tprops[key] = Base.serialize(value, options,\n\t\t\t\t\t\t\tkey !== 'data', dictionary);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tserialize(this._serializeFields);\n\t\tif (!(this instanceof Group))\n\t\t\tserialize(this._style._defaults);\n\t\treturn [ this._class, props ];\n\t},\n\n\t_changed: function(flags) {\n\t\tvar symbol = this._symbol,\n\t\t\tcacheParent = this._parent || symbol,\n\t\t\tproject = this._project;\n\t\tif (flags & 8) {\n\t\t\tthis._bounds = this._position = this._decomposed = undefined;\n\t\t}\n\t\tif (flags & 16) {\n\t\t\tthis._globalMatrix = undefined;\n\t\t}\n\t\tif (cacheParent\n\t\t\t\t&& (flags & 72)) {\n\t\t\tItem._clearBoundsCache(cacheParent);\n\t\t}\n\t\tif (flags & 2) {\n\t\t\tItem._clearBoundsCache(this);\n\t\t}\n\t\tif (project)\n\t\t\tproject._changed(flags, this);\n\t\tif (symbol)\n\t\t\tsymbol._changed(flags);\n\t},\n\n\tgetId: function() {\n\t\treturn this._id;\n\t},\n\n\tgetName: function() {\n\t\treturn this._name;\n\t},\n\n\tsetName: function(name) {\n\n\t\tif (this._name)\n\t\t\tthis._removeNamed();\n\t\tif (name === (+name) + '')\n\t\t\tthrow new Error(\n\t\t\t\t\t'Names consisting only of numbers are not supported.');\n\t\tvar owner = this._getOwner();\n\t\tif (name && owner) {\n\t\t\tvar children = owner._children,\n\t\t\t\tnamedChildren = owner._namedChildren;\n\t\t\t(namedChildren[name] = namedChildren[name] || []).push(this);\n\t\t\tif (!(name in children))\n\t\t\t\tchildren[name] = this;\n\t\t}\n\t\tthis._name = name || undefined;\n\t\tthis._changed(256);\n\t},\n\n\tgetStyle: function() {\n\t\treturn this._style;\n\t},\n\n\tsetStyle: function(style) {\n\t\tthis.getStyle().set(style);\n\t}\n}, Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'],\n\tfunction(name) {\n\t\tvar part = Base.capitalize(name),\n\t\t\tkey = '_' + name,\n\t\t\tflags = {\n\t\t\t\tlocked: 256,\n\t\t\t\tvisible: 265\n\t\t\t};\n\t\tthis['get' + part] = function() {\n\t\t\treturn this[key];\n\t\t};\n\t\tthis['set' + part] = function(value) {\n\t\t\tif (value != this[key]) {\n\t\t\t\tthis[key] = value;\n\t\t\t\tthis._changed(flags[name] || 257);\n\t\t\t}\n\t\t};\n\t},\n{}), {\n\tbeans: true,\n\n\tgetSelection: function() {\n\t\treturn this._selection;\n\t},\n\n\tsetSelection: function(selection) {\n\t\tif (selection !== this._selection) {\n\t\t\tthis._selection = selection;\n\t\t\tvar project = this._project;\n\t\t\tif (project) {\n\t\t\t\tproject._updateSelection(this);\n\t\t\t\tthis._changed(257);\n\t\t\t}\n\t\t}\n\t},\n\n\t_changeSelection: function(flag, selected) {\n\t\tvar selection = this._selection;\n\t\tthis.setSelection(selected ? selection | flag : selection & ~flag);\n\t},\n\n\tisSelected: function() {\n\t\tif (this._selectChildren) {\n\t\t\tvar children = this._children;\n\t\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\t\tif (children[i].isSelected())\n\t\t\t\t\treturn true;\n\t\t}\n\t\treturn !!(this._selection & 1);\n\t},\n\n\tsetSelected: function(selected) {\n\t\tif (this._selectChildren) {\n\t\t\tvar children = this._children;\n\t\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\t\tchildren[i].setSelected(selected);\n\t\t}\n\t\tthis._changeSelection(1, selected);\n\t},\n\n\tisFullySelected: function() {\n\t\tvar children = this._children,\n\t\t\tselected = !!(this._selection & 1);\n\t\tif (children && selected) {\n\t\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\t\tif (!children[i].isFullySelected())\n\t\t\t\t\treturn false;\n\t\t\treturn true;\n\t\t}\n\t\treturn selected;\n\t},\n\n\tsetFullySelected: function(selected) {\n\t\tvar children = this._children;\n\t\tif (children) {\n\t\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\t\tchildren[i].setFullySelected(selected);\n\t\t}\n\t\tthis._changeSelection(1, selected);\n\t},\n\n\tisClipMask: function() {\n\t\treturn this._clipMask;\n\t},\n\n\tsetClipMask: function(clipMask) {\n\t\tif (this._clipMask != (clipMask = !!clipMask)) {\n\t\t\tthis._clipMask = clipMask;\n\t\t\tif (clipMask) {\n\t\t\t\tthis.setFillColor(null);\n\t\t\t\tthis.setStrokeColor(null);\n\t\t\t}\n\t\t\tthis._changed(257);\n\t\t\tif (this._parent)\n\t\t\t\tthis._parent._changed(2048);\n\t\t}\n\t},\n\n\tgetData: function() {\n\t\tif (!this._data)\n\t\t\tthis._data = {};\n\t\treturn this._data;\n\t},\n\n\tsetData: function(data) {\n\t\tthis._data = data;\n\t},\n\n\tgetPosition: function(_dontLink) {\n\t\tvar ctor = _dontLink ? Point : LinkedPoint;\n\t\tvar position = this._position ||\n\t\t\t(this._position = this._getPositionFromBounds());\n\t\treturn new ctor(position.x, position.y, this, 'setPosition');\n\t},\n\n\tsetPosition: function() {\n\t\tthis.translate(Point.read(arguments).subtract(this.getPosition(true)));\n\t},\n\n\t_getPositionFromBounds: function(bounds) {\n\t\treturn this._pivot\n\t\t\t\t? this._matrix._transformPoint(this._pivot)\n\t\t\t\t: (bounds || this.getBounds()).getCenter(true);\n\t},\n\n\tgetPivot: function() {\n\t\tvar pivot = this._pivot;\n\t\treturn pivot\n\t\t\t\t? new LinkedPoint(pivot.x, pivot.y, this, 'setPivot')\n\t\t\t\t: null;\n\t},\n\n\tsetPivot: function() {\n\t\tthis._pivot = Point.read(arguments, 0, { clone: true, readNull: true });\n\t\tthis._position = undefined;\n\t}\n}, Base.each({\n\t\tgetStrokeBounds: { stroke: true },\n\t\tgetHandleBounds: { handle: true },\n\t\tgetInternalBounds: { internal: true },\n\t\tgetDrawnBounds: { stroke: true, drawnTextBounds: true },\n\t},\n\tfunction(options, key) {\n\t\tthis[key] = function(matrix) {\n\t\t\treturn this.getBounds(matrix, options);\n\t\t};\n\t},\n{\n\tbeans: true,\n\n\tgetBounds: function(matrix, options) {\n\t\tvar hasMatrix = options || matrix instanceof Matrix,\n\t\t\topts = Base.set({}, hasMatrix ? options : matrix,\n\t\t\t\t\tthis._boundsOptions);\n\t\tif (!opts.stroke || this.getStrokeScaling())\n\t\t\topts.cacheItem = this;\n\t\tvar rect = this._getCachedBounds(hasMatrix && matrix, opts).rect;\n\t\treturn !arguments.length\n\t\t\t\t? new LinkedRectangle(rect.x, rect.y, rect.width, rect.height,\n\t\t\t\t\tthis, 'setBounds')\n\t\t\t\t: rect;\n\t},\n\n\tsetBounds: function() {\n\t\tvar rect = Rectangle.read(arguments),\n\t\t\tbounds = this.getBounds(),\n\t\t\t_matrix = this._matrix,\n\t\t\tmatrix = new Matrix(),\n\t\t\tcenter = rect.getCenter();\n\t\tmatrix.translate(center);\n\t\tif (rect.width != bounds.width || rect.height != bounds.height) {\n\t\t\tif (!_matrix.isInvertible()) {\n\t\t\t\t_matrix.set(_matrix._backup\n\t\t\t\t\t\t|| new Matrix().translate(_matrix.getTranslation()));\n\t\t\t\tbounds = this.getBounds();\n\t\t\t}\n\t\t\tmatrix.scale(\n\t\t\t\t\tbounds.width !== 0 ? rect.width / bounds.width : 0,\n\t\t\t\t\tbounds.height !== 0 ? rect.height / bounds.height : 0);\n\t\t}\n\t\tcenter = bounds.getCenter();\n\t\tmatrix.translate(-center.x, -center.y);\n\t\tthis.transform(matrix);\n\t},\n\n\t_getBounds: function(matrix, options) {\n\t\tvar children = this._children;\n\t\tif (!children || !children.length)\n\t\t\treturn new Rectangle();\n\t\tItem._updateBoundsCache(this, options.cacheItem);\n\t\treturn Item._getBounds(children, matrix, options);\n\t},\n\n\t_getBoundsCacheKey: function(options, internal) {\n\t\treturn [\n\t\t\toptions.stroke ? 1 : 0,\n\t\t\toptions.handle ? 1 : 0,\n\t\t\toptions.drawnTextBounds? 1 : 0,\n\t\t\tinternal ? 1 : 0\n\t\t].join('');\n\t},\n\n\t_getCachedBounds: function(matrix, options, noInternal) {\n\t\tmatrix = matrix && matrix._orNullIfIdentity();\n\t\tvar internal = options.internal && !noInternal,\n\t\t\tcacheItem = options.cacheItem,\n\t\t\t_matrix = internal ? null : this._matrix._orNullIfIdentity(),\n\t\t\tcacheKey = cacheItem && (!matrix || matrix.equals(_matrix))\n\t\t\t\t&& this._getBoundsCacheKey(options, internal),\n\t\t\tbounds = this._bounds;\n\t\tItem._updateBoundsCache(this._parent || this._symbol, cacheItem);\n\t\tif (cacheKey && bounds && cacheKey in bounds) {\n\t\t\tvar cached = bounds[cacheKey];\n\t\t\treturn {\n\t\t\t\trect: cached.rect.clone(),\n\t\t\t\tnonscaling: cached.nonscaling\n\t\t\t};\n\t\t}\n\t\tvar res = this._getBounds(matrix || _matrix, options),\n\t\t\trect = res.rect || res,\n\t\t\tstyle = this._style,\n\t\t\tnonscaling = res.nonscaling || style.hasStroke()\n\t\t\t\t&& !style.getStrokeScaling();\n\t\tif (cacheKey) {\n\t\t\tif (!bounds) {\n\t\t\t\tthis._bounds = bounds = {};\n\t\t\t}\n\t\t\tvar cached = bounds[cacheKey] = {\n\t\t\t\trect: rect.clone(),\n\t\t\t\tnonscaling: nonscaling,\n\t\t\t\tinternal: internal\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\trect: rect,\n\t\t\tnonscaling: nonscaling\n\t\t};\n\t},\n\n\t_getStrokeMatrix: function(matrix, options) {\n\t\tvar parent = this.getStrokeScaling() ? null\n\t\t\t\t: options && options.internal ? this\n\t\t\t\t\t: this._parent || this._symbol && this._symbol._item,\n\t\t\tmx = parent ? parent.getViewMatrix().invert() : matrix;\n\t\treturn mx && mx._shiftless();\n\t},\n\n\tstatics: {\n\t\t_updateBoundsCache: function(parent, item) {\n\t\t\tif (parent && item) {\n\t\t\t\tvar id = item._id,\n\t\t\t\t\tref = parent._boundsCache = parent._boundsCache || {\n\t\t\t\t\t\tids: {},\n\t\t\t\t\t\tlist: []\n\t\t\t\t\t};\n\t\t\t\tif (!ref.ids[id]) {\n\t\t\t\t\tref.list.push(item);\n\t\t\t\t\tref.ids[id] = item;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t_clearBoundsCache: function(item) {\n\t\t\tvar cache = item._boundsCache;\n\t\t\tif (cache) {\n\t\t\t\titem._bounds = item._position = item._boundsCache = undefined;\n\t\t\t\tfor (var i = 0, list = cache.list, l = list.length; i < l; i++){\n\t\t\t\t\tvar other = list[i];\n\t\t\t\t\tif (other !== item) {\n\t\t\t\t\t\tother._bounds = other._position = undefined;\n\t\t\t\t\t\tif (other._boundsCache)\n\t\t\t\t\t\t\tItem._clearBoundsCache(other);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t_getBounds: function(items, matrix, options) {\n\t\t\tvar x1 = Infinity,\n\t\t\t\tx2 = -x1,\n\t\t\t\ty1 = x1,\n\t\t\t\ty2 = x2,\n\t\t\t\tnonscaling = false;\n\t\t\toptions = options || {};\n\t\t\tfor (var i = 0, l = items.length; i < l; i++) {\n\t\t\t\tvar item = items[i];\n\t\t\t\tif (item._visible && !item.isEmpty(true)) {\n\t\t\t\t\tvar bounds = item._getCachedBounds(\n\t\t\t\t\t\tmatrix && matrix.appended(item._matrix), options, true),\n\t\t\t\t\t\trect = bounds.rect;\n\t\t\t\t\tx1 = Math.min(rect.x, x1);\n\t\t\t\t\ty1 = Math.min(rect.y, y1);\n\t\t\t\t\tx2 = Math.max(rect.x + rect.width, x2);\n\t\t\t\t\ty2 = Math.max(rect.y + rect.height, y2);\n\t\t\t\t\tif (bounds.nonscaling)\n\t\t\t\t\t\tnonscaling = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\trect: isFinite(x1)\n\t\t\t\t\t? new Rectangle(x1, y1, x2 - x1, y2 - y1)\n\t\t\t\t\t: new Rectangle(),\n\t\t\t\tnonscaling: nonscaling\n\t\t\t};\n\t\t}\n\t}\n\n}), {\n\tbeans: true,\n\n\t_decompose: function() {\n\t\treturn this._applyMatrix\n\t\t\t? null\n\t\t\t: this._decomposed || (this._decomposed = this._matrix.decompose());\n\t},\n\n\tgetRotation: function() {\n\t\tvar decomposed = this._decompose();\n\t\treturn decomposed ? decomposed.rotation : 0;\n\t},\n\n\tsetRotation: function(rotation) {\n\t\tvar current = this.getRotation();\n\t\tif (current != null && rotation != null) {\n\t\t\tvar decomposed = this._decomposed;\n\t\t\tthis.rotate(rotation - current);\n\t\t\tif (decomposed) {\n\t\t\t\tdecomposed.rotation = rotation;\n\t\t\t\tthis._decomposed = decomposed;\n\t\t\t}\n\t\t}\n\t},\n\n\tgetScaling: function() {\n\t\tvar decomposed = this._decompose(),\n\t\t\ts = decomposed && decomposed.scaling;\n\t\treturn new LinkedPoint(s ? s.x : 1, s ? s.y : 1, this, 'setScaling');\n\t},\n\n\tsetScaling: function() {\n\t\tvar current = this.getScaling(),\n\t\t\tscaling = Point.read(arguments, 0, { clone: true, readNull: true });\n\t\tif (current && scaling && !current.equals(scaling)) {\n\t\t\tvar rotation = this.getRotation(),\n\t\t\t\tdecomposed = this._decomposed,\n\t\t\t\tmatrix = new Matrix(),\n\t\t\t\tcenter = this.getPosition(true);\n\t\t\tmatrix.translate(center);\n\t\t\tif (rotation)\n\t\t\t\tmatrix.rotate(rotation);\n\t\t\tmatrix.scale(scaling.x / current.x, scaling.y / current.y);\n\t\t\tif (rotation)\n\t\t\t\tmatrix.rotate(-rotation);\n\t\t\tmatrix.translate(center.negate());\n\t\t\tthis.transform(matrix);\n\t\t\tif (decomposed) {\n\t\t\t\tdecomposed.scaling = scaling;\n\t\t\t\tthis._decomposed = decomposed;\n\t\t\t}\n\t\t}\n\t},\n\n\tgetMatrix: function() {\n\t\treturn this._matrix;\n\t},\n\n\tsetMatrix: function() {\n\t\tvar matrix = this._matrix;\n\t\tmatrix.initialize.apply(matrix, arguments);\n\t},\n\n\tgetGlobalMatrix: function(_dontClone) {\n\t\tvar matrix = this._globalMatrix;\n\t\tif (matrix) {\n\t\t\tvar parent = this._parent;\n\t\t\tvar parents = [];\n\t\t\twhile (parent) {\n\t\t\t\tif (!parent._globalMatrix) {\n\t\t\t\t\tmatrix = null;\n\t\t\t\t\tfor (var i = 0, l = parents.length; i < l; i++) {\n\t\t\t\t\t\tparents[i]._globalMatrix = null;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tparents.push(parent);\n\t\t\t\tparent = parent._parent;\n\t\t\t}\n\t\t}\n\t\tif (!matrix) {\n\t\t\tmatrix = this._globalMatrix = this._matrix.clone();\n\t\t\tvar parent = this._parent;\n\t\t\tif (parent)\n\t\t\t\tmatrix.prepend(parent.getGlobalMatrix(true));\n\t\t}\n\t\treturn _dontClone ? matrix : matrix.clone();\n\t},\n\n\tgetViewMatrix: function() {\n\t\treturn this.getGlobalMatrix().prepend(this.getView()._matrix);\n\t},\n\n\tgetApplyMatrix: function() {\n\t\treturn this._applyMatrix;\n\t},\n\n\tsetApplyMatrix: function(apply) {\n\t\tif (this._applyMatrix = this._canApplyMatrix && !!apply)\n\t\t\tthis.transform(null, true);\n\t},\n\n\tgetTransformContent: '#getApplyMatrix',\n\tsetTransformContent: '#setApplyMatrix',\n}, {\n\tgetProject: function() {\n\t\treturn this._project;\n\t},\n\n\t_setProject: function(project, installEvents) {\n\t\tif (this._project !== project) {\n\t\t\tif (this._project)\n\t\t\t\tthis._installEvents(false);\n\t\t\tthis._project = project;\n\t\t\tvar children = this._children;\n\t\t\tfor (var i = 0, l = children && children.length; i < l; i++)\n\t\t\t\tchildren[i]._setProject(project);\n\t\t\tinstallEvents = true;\n\t\t}\n\t\tif (installEvents)\n\t\t\tthis._installEvents(true);\n\t},\n\n\tgetView: function() {\n\t\treturn this._project._view;\n\t},\n\n\t_installEvents: function _installEvents(install) {\n\t\t_installEvents.base.call(this, install);\n\t\tvar children = this._children;\n\t\tfor (var i = 0, l = children && children.length; i < l; i++)\n\t\t\tchildren[i]._installEvents(install);\n\t},\n\n\tgetLayer: function() {\n\t\tvar parent = this;\n\t\twhile (parent = parent._parent) {\n\t\t\tif (parent instanceof Layer)\n\t\t\t\treturn parent;\n\t\t}\n\t\treturn null;\n\t},\n\n\tgetParent: function() {\n\t\treturn this._parent;\n\t},\n\n\tsetParent: function(item) {\n\t\treturn item.addChild(this);\n\t},\n\n\t_getOwner: '#getParent',\n\n\tgetChildren: function() {\n\t\treturn this._children;\n\t},\n\n\tsetChildren: function(items) {\n\t\tthis.removeChildren();\n\t\tthis.addChildren(items);\n\t},\n\n\tgetFirstChild: function() {\n\t\treturn this._children && this._children[0] || null;\n\t},\n\n\tgetLastChild: function() {\n\t\treturn this._children && this._children[this._children.length - 1]\n\t\t\t\t|| null;\n\t},\n\n\tgetNextSibling: function() {\n\t\tvar owner = this._getOwner();\n\t\treturn owner && owner._children[this._index + 1] || null;\n\t},\n\n\tgetPreviousSibling: function() {\n\t\tvar owner = this._getOwner();\n\t\treturn owner && owner._children[this._index - 1] || null;\n\t},\n\n\tgetIndex: function() {\n\t\treturn this._index;\n\t},\n\n\tequals: function(item) {\n\t\treturn item === this || item && this._class === item._class\n\t\t\t\t&& this._style.equals(item._style)\n\t\t\t\t&& this._matrix.equals(item._matrix)\n\t\t\t\t&& this._locked === item._locked\n\t\t\t\t&& this._visible === item._visible\n\t\t\t\t&& this._blendMode === item._blendMode\n\t\t\t\t&& this._opacity === item._opacity\n\t\t\t\t&& this._clipMask === item._clipMask\n\t\t\t\t&& this._guide === item._guide\n\t\t\t\t&& this._equals(item)\n\t\t\t\t|| false;\n\t},\n\n\t_equals: function(item) {\n\t\treturn Base.equals(this._children, item._children);\n\t},\n\n\tclone: function(options) {\n\t\tvar copy = new this.constructor(Item.NO_INSERT),\n\t\t\tchildren = this._children,\n\t\t\tinsert = Base.pick(options ? options.insert : undefined,\n\t\t\t\t\toptions === undefined || options === true),\n\t\t\tdeep = Base.pick(options ? options.deep : undefined, true);\n\t\tif (children)\n\t\t\tcopy.copyAttributes(this);\n\t\tif (!children || deep)\n\t\t\tcopy.copyContent(this);\n\t\tif (!children)\n\t\t\tcopy.copyAttributes(this);\n\t\tif (insert)\n\t\t\tcopy.insertAbove(this);\n\t\tvar name = this._name,\n\t\t\tparent = this._parent;\n\t\tif (name && parent) {\n\t\t\tvar children = parent._children,\n\t\t\t\torig = name,\n\t\t\t\ti = 1;\n\t\t\twhile (children[name])\n\t\t\t\tname = orig + ' ' + (i++);\n\t\t\tif (name !== orig)\n\t\t\t\tcopy.setName(name);\n\t\t}\n\t\treturn copy;\n\t},\n\n\tcopyContent: function(source) {\n\t\tvar children = source._children;\n\t\tfor (var i = 0, l = children && children.length; i < l; i++) {\n\t\t\tthis.addChild(children[i].clone(false), true);\n\t\t}\n\t},\n\n\tcopyAttributes: function(source, excludeMatrix) {\n\t\tthis.setStyle(source._style);\n\t\tvar keys = ['_locked', '_visible', '_blendMode', '_opacity',\n\t\t\t\t'_clipMask', '_guide'];\n\t\tfor (var i = 0, l = keys.length; i < l; i++) {\n\t\t\tvar key = keys[i];\n\t\t\tif (source.hasOwnProperty(key))\n\t\t\t\tthis[key] = source[key];\n\t\t}\n\t\tif (!excludeMatrix)\n\t\t\tthis._matrix.set(source._matrix, true);\n\t\tthis.setApplyMatrix(source._applyMatrix);\n\t\tthis.setPivot(source._pivot);\n\t\tthis.setSelection(source._selection);\n\t\tvar data = source._data,\n\t\t\tname = source._name;\n\t\tthis._data = data ? Base.clone(data) : null;\n\t\tif (name)\n\t\t\tthis.setName(name);\n\t},\n\n\trasterize: function(resolution, insert, boundRect) {\n\t\tvar bounds = boundRect ? boundRect : this.getStrokeBounds(),\n\t\t\tscale = (resolution || this.getView().getResolution()) / 72,\n\t\t\ttopLeft = bounds.getTopLeft().floor(),\n\t\t\tbottomRight = bounds.getBottomRight().ceil(),\n\t\t\tsize = new Size(bottomRight.subtract(topLeft)),\n\t\t\traster = new Raster(Item.NO_INSERT);\n\t\tif (!size.isZero()) {\n\t\t\tvar canvas = CanvasProvider.getCanvas(size.multiply(scale)),\n\t\t\t\tctx = canvas.getContext('2d'),\n\t\t\t\tmatrix = new Matrix().scale(scale).translate(topLeft.negate());\n\t\t\tctx.imageSmoothingEnabled = false;\n\t\t\tctx.save();\n\t\t\tmatrix.applyToContext(ctx);\n\t\t\tthis.draw(ctx, new Base({ matrices: [matrix] }));\n\t\t\tctx.restore();\n\t\t\traster.setCanvas(canvas);\n\t\t}\n\t\traster.transform(new Matrix().translate(topLeft.add(size.divide(2)))\n\t\t\t\t.scale(1 / scale));\n\t\tif (insert === undefined || insert)\n\t\t\traster.insertAbove(this);\n\t\treturn raster;\n\t},\n\n\tcontains: function() {\n\t\tvar matrix = this._matrix;\n\t\treturn (\n\t\t\tmatrix.isInvertible() &&\n\t\t\t!!this._contains(matrix._inverseTransform(Point.read(arguments)))\n\t\t);\n\t},\n\n\t_contains: function(point) {\n\t\tvar children = this._children;\n\t\tif (children) {\n\t\t\tfor (var i = children.length - 1; i >= 0; i--) {\n\t\t\t\tif (children[i].contains(point))\n\t\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\treturn point.isInside(this.getInternalBounds());\n\t},\n\n\tisInside: function() {\n\t\treturn Rectangle.read(arguments).contains(this.getBounds());\n\t},\n\n\t_asPathItem: function() {\n\t\treturn new Path.Rectangle({\n\t\t\trectangle: this.getInternalBounds(),\n\t\t\tmatrix: this._matrix,\n\t\t\tinsert: false,\n\t\t});\n\t},\n\n\tintersects: function(item, _matrix) {\n\t\tif (!(item instanceof Item))\n\t\t\treturn false;\n\t\treturn this._asPathItem().getIntersections(item._asPathItem(), null,\n\t\t\t\t_matrix, true).length > 0;\n\t}\n},\nnew function() {\n\tfunction hitTest() {\n\t\tvar args = arguments;\n\t\treturn this._hitTest(\n\t\t\t\tPoint.read(args),\n\t\t\t\tHitResult.getOptions(args));\n\t}\n\n\tfunction hitTestAll() {\n\t\tvar args = arguments,\n\t\t\tpoint = Point.read(args),\n\t\t\toptions = HitResult.getOptions(args),\n\t\t\tall = [];\n\t\tthis._hitTest(point, new Base({ all: all }, options));\n\t\treturn all;\n\t}\n\n\tfunction hitTestChildren(point, options, viewMatrix, _exclude) {\n\t\tvar children = this._children;\n\t\tif (children) {\n\t\t\tfor (var i = children.length - 1; i >= 0; i--) {\n\t\t\t\tvar child = children[i];\n\t\t\t\tvar res = child !== _exclude && child._hitTest(point, options,\n\t\t\t\t\t\tviewMatrix);\n\t\t\t\tif (res && !options.all)\n\t\t\t\t\treturn res;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tProject.inject({\n\t\thitTest: hitTest,\n\t\thitTestAll: hitTestAll,\n\t\t_hitTest: hitTestChildren\n\t});\n\n\treturn {\n\t\thitTest: hitTest,\n\t\thitTestAll: hitTestAll,\n\t\t_hitTestChildren: hitTestChildren,\n\t};\n}, {\n\n\t_hitTest: function(point, options, parentViewMatrix) {\n\t\tif (this._locked || !this._visible || this._guide && !options.guides\n\t\t\t\t|| this.isEmpty()) {\n\t\t\treturn null;\n\t\t}\n\n\t\tvar matrix = this._matrix,\n\t\t\tviewMatrix = parentViewMatrix\n\t\t\t\t\t? parentViewMatrix.appended(matrix)\n\t\t\t\t\t: this.getGlobalMatrix().prepend(this.getView()._matrix),\n\t\t\ttolerance = Math.max(options.tolerance, 1e-12),\n\t\t\ttolerancePadding = options._tolerancePadding = new Size(\n\t\t\t\t\tPath._getStrokePadding(tolerance,\n\t\t\t\t\t\tmatrix._shiftless().invert()));\n\t\tpoint = matrix._inverseTransform(point);\n\t\tif (!point || !this._children &&\n\t\t\t!this.getBounds({ internal: true, stroke: true, handle: true })\n\t\t\t\t.expand(tolerancePadding.multiply(2))._containsPoint(point)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tvar checkSelf = !(options.guides && !this._guide\n\t\t\t\t|| options.selected && !this.isSelected()\n\t\t\t\t|| options.type && options.type !== Base.hyphenate(this._class)\n\t\t\t\t|| options.class && !(this instanceof options.class)),\n\t\t\tmatch = options.match,\n\t\t\tthat = this,\n\t\t\tbounds,\n\t\t\tres;\n\n\t\tfunction filter(hit) {\n\t\t\tif (hit && match && !match(hit))\n\t\t\t\thit = null;\n\t\t\tif (hit && options.all)\n\t\t\t\toptions.all.push(hit);\n\t\t\treturn hit;\n\t\t}\n\n\t\tfunction checkPoint(type, part) {\n\t\t\tvar pt = part ? bounds['get' + part]() : that.getPosition();\n\t\t\tif (point.subtract(pt).divide(tolerancePadding).length <= 1) {\n\t\t\t\treturn new HitResult(type, that, {\n\t\t\t\t\tname: part ? Base.hyphenate(part) : type,\n\t\t\t\t\tpoint: pt\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tvar checkPosition = options.position,\n\t\t\tcheckCenter = options.center,\n\t\t\tcheckBounds = options.bounds;\n\t\tif (checkSelf && this._parent\n\t\t\t\t&& (checkPosition || checkCenter || checkBounds)) {\n\t\t\tif (checkCenter || checkBounds) {\n\t\t\t\tbounds = this.getInternalBounds();\n\t\t\t}\n\t\t\tres = checkPosition && checkPoint('position') ||\n\t\t\t\t\tcheckCenter && checkPoint('center', 'Center');\n\t\t\tif (!res && checkBounds) {\n\t\t\t\tvar points = [\n\t\t\t\t\t'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',\n\t\t\t\t\t'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'\n\t\t\t\t];\n\t\t\t\tfor (var i = 0; i < 8 && !res; i++) {\n\t\t\t\t\tres = checkPoint('bounds', points[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tres = filter(res);\n\t\t}\n\n\t\tif (!res) {\n\t\t\tres = this._hitTestChildren(point, options, viewMatrix)\n\t\t\t\t|| checkSelf\n\t\t\t\t\t&& filter(this._hitTestSelf(point, options, viewMatrix,\n\t\t\t\t\t\tthis.getStrokeScaling() ? null\n\t\t\t\t\t\t\t: viewMatrix._shiftless().invert()))\n\t\t\t\t|| null;\n\t\t}\n\t\tif (res && res.point) {\n\t\t\tres.point = matrix.transform(res.point);\n\t\t}\n\t\treturn res;\n\t},\n\n\t_hitTestSelf: function(point, options) {\n\t\tif (options.fill && this.hasFill() && this._contains(point))\n\t\t\treturn new HitResult('fill', this);\n\t},\n\n\tmatches: function(name, compare) {\n\t\tfunction matchObject(obj1, obj2) {\n\t\t\tfor (var i in obj1) {\n\t\t\t\tif (obj1.hasOwnProperty(i)) {\n\t\t\t\t\tvar val1 = obj1[i],\n\t\t\t\t\t\tval2 = obj2[i];\n\t\t\t\t\tif (Base.isPlainObject(val1) && Base.isPlainObject(val2)) {\n\t\t\t\t\t\tif (!matchObject(val1, val2))\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t} else if (!Base.equals(val1, val2)) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\tvar type = typeof name;\n\t\tif (type === 'object') {\n\t\t\tfor (var key in name) {\n\t\t\t\tif (name.hasOwnProperty(key) && !this.matches(key, name[key]))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t} else if (type === 'function') {\n\t\t\treturn name(this);\n\t\t} else if (name === 'match') {\n\t\t\treturn compare(this);\n\t\t} else {\n\t\t\tvar value = /^(empty|editable)$/.test(name)\n\t\t\t\t\t? this['is' + Base.capitalize(name)]()\n\t\t\t\t\t: name === 'type'\n\t\t\t\t\t\t? Base.hyphenate(this._class)\n\t\t\t\t\t\t: this[name];\n\t\t\tif (name === 'class') {\n\t\t\t\tif (typeof compare === 'function')\n\t\t\t\t\treturn this instanceof compare;\n\t\t\t\tvalue = this._class;\n\t\t\t}\n\t\t\tif (typeof compare === 'function') {\n\t\t\t\treturn !!compare(value);\n\t\t\t} else if (compare) {\n\t\t\t\tif (compare.test) {\n\t\t\t\t\treturn compare.test(value);\n\t\t\t\t} else if (Base.isPlainObject(compare)) {\n\t\t\t\t\treturn matchObject(compare, value);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn Base.equals(value, compare);\n\t\t}\n\t},\n\n\tgetItems: function(options) {\n\t\treturn Item._getItems(this, options, this._matrix);\n\t},\n\n\tgetItem: function(options) {\n\t\treturn Item._getItems(this, options, this._matrix, null, true)[0]\n\t\t\t\t|| null;\n\t},\n\n\tstatics: {\n\t\t_getItems: function _getItems(item, options, matrix, param, firstOnly) {\n\t\t\tif (!param) {\n\t\t\t\tvar obj = typeof options === 'object' && options,\n\t\t\t\t\toverlapping = obj && obj.overlapping,\n\t\t\t\t\tinside = obj && obj.inside,\n\t\t\t\t\tbounds = overlapping || inside,\n\t\t\t\t\trect = bounds && Rectangle.read([bounds]);\n\t\t\t\tparam = {\n\t\t\t\t\titems: [],\n\t\t\t\t\trecursive: obj && obj.recursive !== false,\n\t\t\t\t\tinside: !!inside,\n\t\t\t\t\toverlapping: !!overlapping,\n\t\t\t\t\trect: rect,\n\t\t\t\t\tpath: overlapping && new Path.Rectangle({\n\t\t\t\t\t\trectangle: rect,\n\t\t\t\t\t\tinsert: false\n\t\t\t\t\t})\n\t\t\t\t};\n\t\t\t\tif (obj) {\n\t\t\t\t\toptions = Base.filter({}, options, {\n\t\t\t\t\t\trecursive: true, inside: true, overlapping: true\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar children = item._children,\n\t\t\t\titems = param.items,\n\t\t\t\trect = param.rect;\n\t\t\tmatrix = rect && (matrix || new Matrix());\n\t\t\tfor (var i = 0, l = children && children.length; i < l; i++) {\n\t\t\t\tvar child = children[i],\n\t\t\t\t\tchildMatrix = matrix && matrix.appended(child._matrix),\n\t\t\t\t\tadd = true;\n\t\t\t\tif (rect) {\n\t\t\t\t\tvar bounds = child.getBounds(childMatrix);\n\t\t\t\t\tif (!rect.intersects(bounds))\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tif (!(rect.contains(bounds)\n\t\t\t\t\t\t\t|| param.overlapping && (bounds.contains(rect)\n\t\t\t\t\t\t\t\t|| param.path.intersects(child, childMatrix))))\n\t\t\t\t\t\tadd = false;\n\t\t\t\t}\n\t\t\t\tif (add && child.matches(options)) {\n\t\t\t\t\titems.push(child);\n\t\t\t\t\tif (firstOnly)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (param.recursive !== false) {\n\t\t\t\t\t_getItems(child, options, childMatrix, param, firstOnly);\n\t\t\t\t}\n\t\t\t\tif (firstOnly && items.length > 0)\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn items;\n\t\t}\n\t}\n}, {\n\n\timportJSON: function(json) {\n\t\tvar res = Base.importJSON(json, this);\n\t\treturn res !== this ? this.addChild(res) : res;\n\t},\n\n\taddChild: function(item) {\n\t\treturn this.insertChild(undefined, item);\n\t},\n\n\tinsertChild: function(index, item) {\n\t\tvar res = item ? this.insertChildren(index, [item]) : null;\n\t\treturn res && res[0];\n\t},\n\n\taddChildren: function(items) {\n\t\treturn this.insertChildren(this._children.length, items);\n\t},\n\n\tinsertChildren: function(index, items) {\n\t\tvar children = this._children;\n\t\tif (children && items && items.length > 0) {\n\t\t\titems = Base.slice(items);\n\t\t\tvar inserted = {};\n\t\t\tfor (var i = items.length - 1; i >= 0; i--) {\n\t\t\t\tvar item = items[i],\n\t\t\t\t\tid = item && item._id;\n\t\t\t\tif (!item || inserted[id]) {\n\t\t\t\t\titems.splice(i, 1);\n\t\t\t\t} else {\n\t\t\t\t\titem._remove(false, true);\n\t\t\t\t\tinserted[id] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tBase.splice(children, items, index, 0);\n\t\t\tvar project = this._project,\n\t\t\t\tnotifySelf = project._changes;\n\t\t\tfor (var i = 0, l = items.length; i < l; i++) {\n\t\t\t\tvar item = items[i],\n\t\t\t\t\tname = item._name;\n\t\t\t\titem._parent = this;\n\t\t\t\titem._setProject(project, true);\n\t\t\t\tif (name)\n\t\t\t\t\titem.setName(name);\n\t\t\t\tif (notifySelf)\n\t\t\t\t\titem._changed(5);\n\t\t\t}\n\t\t\tthis._changed(11);\n\t\t} else {\n\t\t\titems = null;\n\t\t}\n\t\treturn items;\n\t},\n\n\t_insertItem: '#insertChild',\n\n\t_insertAt: function(item, offset) {\n\t\tvar owner = item && item._getOwner(),\n\t\t\tres = item !== this && owner ? this : null;\n\t\tif (res) {\n\t\t\tres._remove(false, true);\n\t\t\towner._insertItem(item._index + offset, res);\n\t\t}\n\t\treturn res;\n\t},\n\n\tinsertAbove: function(item) {\n\t\treturn this._insertAt(item, 1);\n\t},\n\n\tinsertBelow: function(item) {\n\t\treturn this._insertAt(item, 0);\n\t},\n\n\tsendToBack: function() {\n\t\tvar owner = this._getOwner();\n\t\treturn owner ? owner._insertItem(0, this) : null;\n\t},\n\n\tbringToFront: function() {\n\t\tvar owner = this._getOwner();\n\t\treturn owner ? owner._insertItem(undefined, this) : null;\n\t},\n\n\tappendTop: '#addChild',\n\n\tappendBottom: function(item) {\n\t\treturn this.insertChild(0, item);\n\t},\n\n\tmoveAbove: '#insertAbove',\n\n\tmoveBelow: '#insertBelow',\n\n\taddTo: function(owner) {\n\t\treturn owner._insertItem(undefined, this);\n\t},\n\n\tcopyTo: function(owner) {\n\t\treturn this.clone(false).addTo(owner);\n\t},\n\n\treduce: function(options) {\n\t\tvar children = this._children;\n\t\tif (children && children.length === 1) {\n\t\t\tvar child = children[0].reduce(options);\n\t\t\tif (this._parent) {\n\t\t\t\tchild.insertAbove(this);\n\t\t\t\tthis.remove();\n\t\t\t} else {\n\t\t\t\tchild.remove();\n\t\t\t}\n\t\t\treturn child;\n\t\t}\n\t\treturn this;\n\t},\n\n\t_removeNamed: function() {\n\t\tvar owner = this._getOwner();\n\t\tif (owner) {\n\t\t\tvar children = owner._children,\n\t\t\t\tnamedChildren = owner._namedChildren,\n\t\t\t\tname = this._name,\n\t\t\t\tnamedArray = namedChildren[name],\n\t\t\t\tindex = namedArray ? namedArray.indexOf(this) : -1;\n\t\t\tif (index !== -1) {\n\t\t\t\tif (children[name] == this)\n\t\t\t\t\tdelete children[name];\n\t\t\t\tnamedArray.splice(index, 1);\n\t\t\t\tif (namedArray.length) {\n\t\t\t\t\tchildren[name] = namedArray[0];\n\t\t\t\t} else {\n\t\t\t\t\tdelete namedChildren[name];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t_remove: function(notifySelf, notifyParent) {\n\t\tvar owner = this._getOwner(),\n\t\t\tproject = this._project,\n\t\t\tindex = this._index;\n\t\tif (this._style)\n\t\t\tthis._style._dispose();\n\t\tif (owner) {\n\t\t\tif (this._name)\n\t\t\t\tthis._removeNamed();\n\t\t\tif (index != null) {\n\t\t\t\tif (project._activeLayer === this)\n\t\t\t\t\tproject._activeLayer = this.getNextSibling()\n\t\t\t\t\t\t\t|| this.getPreviousSibling();\n\t\t\t\tBase.splice(owner._children, null, index, 1);\n\t\t\t}\n\t\t\tthis._installEvents(false);\n\t\t\tif (notifySelf && project._changes)\n\t\t\t\tthis._changed(5);\n\t\t\tif (notifyParent)\n\t\t\t\towner._changed(11, this);\n\t\t\tthis._parent = null;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n\n\tremove: function() {\n\t\treturn this._remove(true, true);\n\t},\n\n\treplaceWith: function(item) {\n\t\tvar ok = item && item.insertBelow(this);\n\t\tif (ok)\n\t\t\tthis.remove();\n\t\treturn ok;\n\t},\n\n\tremoveChildren: function(start, end) {\n\t\tif (!this._children)\n\t\t\treturn null;\n\t\tstart = start || 0;\n\t\tend = Base.pick(end, this._children.length);\n\t\tvar removed = Base.splice(this._children, null, start, end - start);\n\t\tfor (var i = removed.length - 1; i >= 0; i--) {\n\t\t\tremoved[i]._remove(true, false);\n\t\t}\n\t\tif (removed.length > 0)\n\t\t\tthis._changed(11);\n\t\treturn removed;\n\t},\n\n\tclear: '#removeChildren',\n\n\treverseChildren: function() {\n\t\tif (this._children) {\n\t\t\tthis._children.reverse();\n\t\t\tfor (var i = 0, l = this._children.length; i < l; i++)\n\t\t\t\tthis._children[i]._index = i;\n\t\t\tthis._changed(11);\n\t\t}\n\t},\n\n\tisEmpty: function(recursively) {\n\t\tvar children = this._children;\n\t\tvar numChildren = children ? children.length : 0;\n\t\tif (recursively) {\n\t\t\tfor (var i = 0; i < numChildren; i++) {\n\t\t\t\tif (!children[i].isEmpty(recursively)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t\treturn !numChildren;\n\t},\n\n\tisEditable: function() {\n\t\tvar item = this;\n\t\twhile (item) {\n\t\t\tif (!item._visible || item._locked)\n\t\t\t\treturn false;\n\t\t\titem = item._parent;\n\t\t}\n\t\treturn true;\n\t},\n\n\thasFill: function() {\n\t\treturn this.getStyle().hasFill();\n\t},\n\n\thasStroke: function() {\n\t\treturn this.getStyle().hasStroke();\n\t},\n\n\thasShadow: function() {\n\t\treturn this.getStyle().hasShadow();\n\t},\n\n\t_getOrder: function(item) {\n\t\tfunction getList(item) {\n\t\t\tvar list = [];\n\t\t\tdo {\n\t\t\t\tlist.unshift(item);\n\t\t\t} while (item = item._parent);\n\t\t\treturn list;\n\t\t}\n\t\tvar list1 = getList(this),\n\t\t\tlist2 = getList(item);\n\t\tfor (var i = 0, l = Math.min(list1.length, list2.length); i < l; i++) {\n\t\t\tif (list1[i] != list2[i]) {\n\t\t\t\treturn list1[i]._index < list2[i]._index ? 1 : -1;\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t},\n\n\thasChildren: function() {\n\t\treturn this._children && this._children.length > 0;\n\t},\n\n\tisInserted: function() {\n\t\treturn this._parent ? this._parent.isInserted() : false;\n\t},\n\n\tisAbove: function(item) {\n\t\treturn this._getOrder(item) === -1;\n\t},\n\n\tisBelow: function(item) {\n\t\treturn this._getOrder(item) === 1;\n\t},\n\n\tisParent: function(item) {\n\t\treturn this._parent === item;\n\t},\n\n\tisChild: function(item) {\n\t\treturn item && item._parent === this;\n\t},\n\n\tisDescendant: function(item) {\n\t\tvar parent = this;\n\t\twhile (parent = parent._parent) {\n\t\t\tif (parent === item)\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n\n\tisAncestor: function(item) {\n\t\treturn item ? item.isDescendant(this) : false;\n\t},\n\n\tisSibling: function(item) {\n\t\treturn this._parent === item._parent;\n\t},\n\n\tisGroupedWith: function(item) {\n\t\tvar parent = this._parent;\n\t\twhile (parent) {\n\t\t\tif (parent._parent\n\t\t\t\t&& /^(Group|Layer|CompoundPath)$/.test(parent._class)\n\t\t\t\t&& item.isDescendant(parent))\n\t\t\t\t\treturn true;\n\t\t\tparent = parent._parent;\n\t\t}\n\t\treturn false;\n\t},\n\n}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) {\n\tvar rotate = key === 'rotate';\n\tthis[key] = function() {\n\t\tvar args = arguments,\n\t\t\tvalue = (rotate ? Base : Point).read(args),\n\t\t\tcenter = Point.read(args, 0, { readNull: true });\n\t\treturn this.transform(new Matrix()[key](value,\n\t\t\t\tcenter || this.getPosition(true)));\n\t};\n}, {\n\ttranslate: function() {\n\t\tvar mx = new Matrix();\n\t\treturn this.transform(mx.translate.apply(mx, arguments));\n\t},\n\n\ttransform: function(matrix, _applyRecursively, _setApplyMatrix) {\n\t\tvar _matrix = this._matrix,\n\t\t\ttransformMatrix = matrix && !matrix.isIdentity(),\n\t\t\tapplyMatrix = (\n\t\t\t\t_setApplyMatrix && this._canApplyMatrix ||\n\t\t\t\tthis._applyMatrix && (\n\t\t\t\t\ttransformMatrix || !_matrix.isIdentity() ||\n\t\t\t\t\t_applyRecursively && this._children\n\t\t\t\t)\n\t\t\t);\n\t\tif (!transformMatrix && !applyMatrix)\n\t\t\treturn this;\n\t\tif (transformMatrix) {\n\t\t\tif (!matrix.isInvertible() && _matrix.isInvertible())\n\t\t\t\t_matrix._backup = _matrix.getValues();\n\t\t\t_matrix.prepend(matrix, true);\n\t\t\tvar style = this._style,\n\t\t\t\tfillColor = style.getFillColor(true),\n\t\t\t\tstrokeColor = style.getStrokeColor(true);\n\t\t\tif (fillColor)\n\t\t\t\tfillColor.transform(matrix);\n\t\t\tif (strokeColor)\n\t\t\t\tstrokeColor.transform(matrix);\n\t\t}\n\n\t\tif (applyMatrix && (applyMatrix = this._transformContent(\n\t\t\t\t_matrix, _applyRecursively, _setApplyMatrix))) {\n\t\t\tvar pivot = this._pivot;\n\t\t\tif (pivot)\n\t\t\t\t_matrix._transformPoint(pivot, pivot, true);\n\t\t\t_matrix.reset(true);\n\t\t\tif (_setApplyMatrix && this._canApplyMatrix)\n\t\t\t\tthis._applyMatrix = true;\n\t\t}\n\t\tvar bounds = this._bounds,\n\t\t\tposition = this._position;\n\t\tif (transformMatrix || applyMatrix) {\n\t\t\tthis._changed(25);\n\t\t}\n\t\tvar decomp = transformMatrix && bounds && matrix.decompose();\n\t\tif (decomp && decomp.skewing.isZero() && decomp.rotation % 90 === 0) {\n\t\t\tfor (var key in bounds) {\n\t\t\t\tvar cache = bounds[key];\n\t\t\t\tif (cache.nonscaling) {\n\t\t\t\t\tdelete bounds[key];\n\t\t\t\t} else if (applyMatrix || !cache.internal) {\n\t\t\t\t\tvar rect = cache.rect;\n\t\t\t\t\tmatrix._transformBounds(rect, rect);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._bounds = bounds;\n\t\t\tvar cached = bounds[this._getBoundsCacheKey(\n\t\t\t\tthis._boundsOptions || {})];\n\t\t\tif (cached) {\n\t\t\t\tthis._position = this._getPositionFromBounds(cached.rect);\n\t\t\t}\n\t\t} else if (transformMatrix && position && this._pivot) {\n\t\t\tthis._position = matrix._transformPoint(position, position);\n\t\t}\n\t\treturn this;\n\t},\n\n\t_transformContent: function(matrix, applyRecursively, setApplyMatrix) {\n\t\tvar children = this._children;\n\t\tif (children) {\n\t\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\t\tchildren[i].transform(matrix, applyRecursively, setApplyMatrix);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\t},\n\n\tglobalToLocal: function() {\n\t\treturn this.getGlobalMatrix(true)._inverseTransform(\n\t\t\t\tPoint.read(arguments));\n\t},\n\n\tlocalToGlobal: function() {\n\t\treturn this.getGlobalMatrix(true)._transformPoint(\n\t\t\t\tPoint.read(arguments));\n\t},\n\n\tparentToLocal: function() {\n\t\treturn this._matrix._inverseTransform(Point.read(arguments));\n\t},\n\n\tlocalToParent: function() {\n\t\treturn this._matrix._transformPoint(Point.read(arguments));\n\t},\n\n\tfitBounds: function(rectangle, fill) {\n\t\trectangle = Rectangle.read(arguments);\n\t\tvar bounds = this.getBounds(),\n\t\t\titemRatio = bounds.height / bounds.width,\n\t\t\trectRatio = rectangle.height / rectangle.width,\n\t\t\tscale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio)\n\t\t\t\t\t? rectangle.width / bounds.width\n\t\t\t\t\t: rectangle.height / bounds.height,\n\t\t\tnewBounds = new Rectangle(new Point(),\n\t\t\t\t\tnew Size(bounds.width * scale, bounds.height * scale));\n\t\tnewBounds.setCenter(rectangle.getCenter());\n\t\tthis.setBounds(newBounds);\n\t}\n}), {\n\n\t_setStyles: function(ctx, param, viewMatrix, strokeMatrix) {\n\t\tvar style = this._style,\n\t\t\tmatrix = this._matrix;\n\t\tif (style.hasFill()) {\n\t\t\tctx.fillStyle = style.getFillColor().toCanvasStyle(ctx, matrix, strokeMatrix);\n\t\t}\n\t\tif (style.hasStroke()) {\n\t\t\tctx.strokeStyle = style.getStrokeColor().toCanvasStyle(ctx, matrix, strokeMatrix);\n\t\t\tctx.lineWidth = style.getStrokeWidth();\n\t\t\tvar strokeJoin = style.getStrokeJoin(),\n\t\t\t\tstrokeCap = style.getStrokeCap(),\n\t\t\t\tmiterLimit = style.getMiterLimit();\n\t\t\tif (strokeJoin)\n\t\t\t\tctx.lineJoin = strokeJoin;\n\t\t\tif (strokeCap)\n\t\t\t\tctx.lineCap = strokeCap;\n\t\t\tif (miterLimit)\n\t\t\t\tctx.miterLimit = miterLimit;\n\t\t\tif (paper.support.nativeDash) {\n\t\t\t\tvar dashArray = style.getDashArray(),\n\t\t\t\t\tdashOffset = style.getDashOffset();\n\t\t\t\tif (dashArray && dashArray.length) {\n\t\t\t\t\tif ('setLineDash' in ctx) {\n\t\t\t\t\t\tctx.setLineDash(dashArray);\n\t\t\t\t\t\tctx.lineDashOffset = dashOffset;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tctx.mozDash = dashArray;\n\t\t\t\t\t\tctx.mozDashOffset = dashOffset;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (style.hasShadow()) {\n\t\t\tvar pixelRatio = param.pixelRatio || 1,\n\t\t\t\tmx = viewMatrix._shiftless().prepend(\n\t\t\t\t\tnew Matrix().scale(pixelRatio, pixelRatio)),\n\t\t\t\tblur = mx.transform(new Point(style.getShadowBlur(), 0)),\n\t\t\t\toffset = mx.transform(this.getShadowOffset());\n\t\t\tctx.shadowColor = style.getShadowColor().toCanvasStyle(ctx);\n\t\t\tctx.shadowBlur = blur.getLength();\n\t\t\tctx.shadowOffsetX = offset.x;\n\t\t\tctx.shadowOffsetY = offset.y;\n\t\t}\n\t},\n\n\tdraw: function(ctx, param, parentStrokeMatrix) {\n\t\tvar updateVersion = this._updateVersion = this._project._updateVersion;\n\t\tif (!this._visible || this._opacity === 0)\n\t\t\treturn;\n\t\tvar matrices = param.matrices,\n\t\t\tviewMatrix = param.viewMatrix,\n\t\t\tmatrix = this._matrix,\n\t\t\tglobalMatrix = matrices[matrices.length - 1].appended(matrix);\n\t\tif (!globalMatrix.isInvertible())\n\t\t\treturn;\n\n\t\tviewMatrix = viewMatrix ? viewMatrix.appended(globalMatrix)\n\t\t\t\t: globalMatrix;\n\n\t\tmatrices.push(globalMatrix);\n\t\tif (param.updateMatrix) {\n\t\t\tthis._globalMatrix = globalMatrix;\n\t\t}\n\n\t\tvar blendMode = this._blendMode,\n\t\t\topacity = Numerical.clamp(this._opacity, 0, 1),\n\t\t\tnormalBlend = blendMode === 'normal',\n\t\t\tnativeBlend = BlendMode.nativeModes[blendMode],\n\t\t\tdirect = normalBlend && opacity === 1\n\t\t\t\t\t|| param.dontStart\n\t\t\t\t\t|| param.clip\n\t\t\t\t\t|| (nativeBlend || normalBlend && opacity < 1)\n\t\t\t\t\t\t&& this._canComposite(),\n\t\t\tpixelRatio = param.pixelRatio || 1,\n\t\t\tmainCtx, itemOffset, prevOffset;\n\t\tif (!direct) {\n\t\t\tvar bounds = this.getStrokeBounds(viewMatrix);\n\t\t\tif (!bounds.width || !bounds.height) {\n\t\t\t\tmatrices.pop();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprevOffset = param.offset;\n\t\t\titemOffset = param.offset = bounds.getTopLeft().floor();\n\t\t\tmainCtx = ctx;\n\t\t\tctx = CanvasProvider.getContext(bounds.getSize().ceil().add(1)\n\t\t\t\t\t.multiply(pixelRatio));\n\t\t\tif (pixelRatio !== 1)\n\t\t\t\tctx.scale(pixelRatio, pixelRatio);\n\t\t}\n\t\tctx.save();\n\t\tvar strokeMatrix = parentStrokeMatrix\n\t\t\t\t? parentStrokeMatrix.appended(matrix)\n\t\t\t\t: this._canScaleStroke && !this.getStrokeScaling(true)\n\t\t\t\t\t&& viewMatrix,\n\t\t\tclip = !direct && param.clipItem,\n\t\t\ttransform = !strokeMatrix || clip;\n\t\tif (direct) {\n\t\t\tctx.globalAlpha = opacity;\n\t\t\tif (nativeBlend)\n\t\t\t\tctx.globalCompositeOperation = blendMode;\n\t\t} else if (transform) {\n\t\t\tctx.translate(-itemOffset.x, -itemOffset.y);\n\t\t}\n\t\tif (transform) {\n\t\t\t(direct ? matrix : viewMatrix).applyToContext(ctx);\n\t\t}\n\t\tif (clip) {\n\t\t\tparam.clipItem.draw(ctx, param.extend({ clip: true }));\n\t\t}\n\t\tif (strokeMatrix) {\n\t\t\tctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);\n\t\t\tvar offset = param.offset;\n\t\t\tif (offset)\n\t\t\t\tctx.translate(-offset.x, -offset.y);\n\t\t}\n\t\tthis._draw(ctx, param, viewMatrix, strokeMatrix);\n\t\tctx.restore();\n\t\tmatrices.pop();\n\t\tif (param.clip && !param.dontFinish) {\n\t\t\tctx.clip(this.getFillRule());\n\t\t}\n\t\tif (!direct) {\n\t\t\tBlendMode.process(blendMode, ctx, mainCtx, opacity,\n\t\t\t\t\titemOffset.subtract(prevOffset).multiply(pixelRatio));\n\t\t\tCanvasProvider.release(ctx);\n\t\t\tparam.offset = prevOffset;\n\t\t}\n\t},\n\n\t_isUpdated: function(updateVersion) {\n\t\tvar parent = this._parent;\n\t\tif (parent instanceof CompoundPath)\n\t\t\treturn parent._isUpdated(updateVersion);\n\t\tvar updated = this._updateVersion === updateVersion;\n\t\tif (!updated && parent && parent._visible\n\t\t\t\t&& parent._isUpdated(updateVersion)) {\n\t\t\tthis._updateVersion = updateVersion;\n\t\t\tupdated = true;\n\t\t}\n\t\treturn updated;\n\t},\n\n\t_drawSelection: function(ctx, matrix, size, selectionItems, updateVersion) {\n\t\tvar selection = this._selection,\n\t\t\titemSelected = selection & 1,\n\t\t\tboundsSelected = selection & 2\n\t\t\t\t\t|| itemSelected && this._selectBounds,\n\t\t\tpositionSelected = selection & 4;\n\t\tif (!this._drawSelected)\n\t\t\titemSelected = false;\n\t\tif ((itemSelected || boundsSelected || positionSelected)\n\t\t\t\t&& this._isUpdated(updateVersion)) {\n\t\t\tvar layer,\n\t\t\t\tcolor = this.getSelectedColor(true) || (layer = this.getLayer())\n\t\t\t\t\t&& layer.getSelectedColor(true),\n\t\t\t\tmx = matrix.appended(this.getGlobalMatrix(true)),\n\t\t\t\thalf = size / 2;\n\t\t\tctx.strokeStyle = ctx.fillStyle = color\n\t\t\t\t\t? color.toCanvasStyle(ctx) : '#009dec';\n\t\t\tctx.lineWidth=2.5;\n\t\t\tif (itemSelected)\n\t\t\t\tthis._drawSelected(ctx, mx, selectionItems);\n\t\t\tif (positionSelected) {\n\t\t\t\tvar pos = this.getPosition(true),\n\t\t\t\t\tparent = this._parent,\n\t\t\t\t\tpoint = parent ? parent.localToGlobal(pos) : pos,\n\t\t\t\t\tx = point.x,\n\t\t\t\t\ty = point.y;\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.arc(x, y, half, 0, Math.PI * 2, true);\n\t\t\t\tctx.stroke();\n\t\t\t\tvar deltas = [[0, -1], [1, 0], [0, 1], [-1, 0]],\n\t\t\t\t\tstart = half,\n\t\t\t\t\tend = size + 1;\n\t\t\t\tfor (var i = 0; i < 4; i++) {\n\t\t\t\t\tvar delta = deltas[i],\n\t\t\t\t\t\tdx = delta[0],\n\t\t\t\t\t\tdy = delta[1];\n\t\t\t\t\tctx.moveTo(x + dx * start, y + dy * start);\n\t\t\t\t\tctx.lineTo(x + dx * end, y + dy * end);\n\t\t\t\t\tctx.stroke();\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (boundsSelected) {\n\t\t\t\tvar coords = mx._transformCorners(this.getInternalBounds());\n\t\t\t\tctx.beginPath();\n\t\t\t\tfor (var i = 0; i < 8; i++) {\n\t\t\t\t\tctx[!i ? 'moveTo' : 'lineTo'](coords[i], coords[++i]);\n\t\t\t\t}\n\t\t\t\tctx.closePath();\n\t\t\t\tctx.stroke();\n\t\t\t\tfor (var i = 0; i < 8; i++) {\n\t\t\t\t\tctx.fillRect(coords[i] - half, coords[++i] - half,\n\t\t\t\t\t\t\tsize, size);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t_canComposite: function() {\n\t\treturn false;\n\t}\n}, Base.each(['down', 'drag', 'up', 'move'], function(key) {\n\tthis['removeOn' + Base.capitalize(key)] = function() {\n\t\tvar hash = {};\n\t\thash[key] = true;\n\t\treturn this.removeOn(hash);\n\t};\n}, {\n\n\tremoveOn: function(obj) {\n\t\tfor (var name in obj) {\n\t\t\tif (obj[name]) {\n\t\t\t\tvar key = 'mouse' + name,\n\t\t\t\t\tproject = this._project,\n\t\t\t\t\tsets = project._removeSets = project._removeSets || {};\n\t\t\t\tsets[key] = sets[key] || {};\n\t\t\t\tsets[key][this._id] = this;\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n}), {\n\ttween: function(from, to, options) {\n\t\tif (!options) {\n\t\t\toptions = to;\n\t\t\tto = from;\n\t\t\tfrom = null;\n\t\t\tif (!options) {\n\t\t\t\toptions = to;\n\t\t\t\tto = null;\n\t\t\t}\n\t\t}\n\t\tvar easing = options && options.easing,\n\t\t\tstart = options && options.start,\n\t\t\tduration = options != null && (\n\t\t\t\ttypeof options === 'number' ? options : options.duration\n\t\t\t),\n\t\t\ttween = new Tween(this, from, to, duration, easing, start);\n\t\tfunction onFrame(event) {\n\t\t\ttween._handleFrame(event.time * 1000);\n\t\t\tif (!tween.running) {\n\t\t\t\tthis.off('frame', onFrame);\n\t\t\t}\n\t\t}\n\t\tif (duration) {\n\t\t\tthis.on('frame', onFrame);\n\t\t}\n\t\treturn tween;\n\t},\n\n\ttweenTo: function(to, options) {\n\t\treturn this.tween(null, to, options);\n\t},\n\n\ttweenFrom: function(from, options) {\n\t\treturn this.tween(from, null, options);\n\t}\n});\n\nvar Group = Item.extend({\n\t_class: 'Group',\n\t_selectBounds: false,\n\t_selectChildren: true,\n\t_serializeFields: {\n\t\tchildren: []\n\t},\n\n\tinitialize: function Group(arg) {\n\t\tthis._children = [];\n\t\tthis._namedChildren = {};\n\t\tif (!this._initialize(arg))\n\t\t\tthis.addChildren(Array.isArray(arg) ? arg : arguments);\n\t},\n\n\t_changed: function _changed(flags) {\n\t\t_changed.base.call(this, flags);\n\t\tif (flags & 2050) {\n\t\t\tthis._clipItem = undefined;\n\t\t}\n\t},\n\n\t_getClipItem: function() {\n\t\tvar clipItem = this._clipItem;\n\t\tif (clipItem === undefined) {\n\t\t\tclipItem = null;\n\t\t\tvar children = this._children;\n\t\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\t\tif (children[i]._clipMask) {\n\t\t\t\t\tclipItem = children[i];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._clipItem = clipItem;\n\t\t}\n\t\treturn clipItem;\n\t},\n\n\tisClipped: function() {\n\t\treturn !!this._getClipItem();\n\t},\n\n\tsetClipped: function(clipped) {\n\t\tvar child = this.getFirstChild();\n\t\tif (child)\n\t\t\tchild.setClipMask(clipped);\n\t},\n\n\t_getBounds: function _getBounds(matrix, options) {\n\t\tvar clipItem = this._getClipItem();\n\t\treturn clipItem\n\t\t\t? clipItem._getCachedBounds(clipItem._matrix.prepended(matrix),\n\t\t\t\tBase.set({}, options, { stroke: false }))\n\t\t\t: _getBounds.base.call(this, matrix, options);\n\t},\n\n\t_hitTestChildren: function _hitTestChildren(point, options, viewMatrix) {\n\t\tvar clipItem = this._getClipItem();\n\t\treturn (!clipItem || clipItem.contains(point))\n\t\t\t\t&& _hitTestChildren.base.call(this, point, options, viewMatrix,\n\t\t\t\t\tclipItem);\n\t},\n\n\t_draw: function(ctx, param) {\n\t\tvar clip = param.clip,\n\t\t\tclipItem = !clip && this._getClipItem();\n\t\tparam = param.extend({ clipItem: clipItem, clip: false });\n\t\tif (clip) {\n\t\t\tctx.beginPath();\n\t\t\tparam.dontStart = param.dontFinish = true;\n\t\t} else if (clipItem) {\n\t\t\tclipItem.draw(ctx, param.extend({ clip: true }));\n\t\t}\n\t\tvar children = this._children;\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tvar item = children[i];\n\t\t\tif (item !== clipItem)\n\t\t\t\titem.draw(ctx, param);\n\t\t}\n\t}\n});\n\nvar Layer = Group.extend({\n\t_class: 'Layer',\n\n\tinitialize: function Layer() {\n\t\tGroup.apply(this, arguments);\n\t},\n\n\t_getOwner: function() {\n\t\treturn this._parent || this._index != null && this._project;\n\t},\n\n\tisInserted: function isInserted() {\n\t\treturn this._parent ? isInserted.base.call(this) : this._index != null;\n\t},\n\n\tactivate: function() {\n\t\tthis._project._activeLayer = this;\n\t},\n\n\t_hitTestSelf: function() {\n\t}\n});\n\nvar Shape = Item.extend({\n\t_class: 'Shape',\n\t_applyMatrix: false,\n\t_canApplyMatrix: false,\n\t_canScaleStroke: true,\n\t_serializeFields: {\n\t\ttype: null,\n\t\tsize: null,\n\t\tradius: null\n\t},\n\n\tinitialize: function Shape(props, point) {\n\t\tthis._initialize(props, point);\n\t},\n\n\t_equals: function(item) {\n\t\treturn this._type === item._type\n\t\t\t&& this._size.equals(item._size)\n\t\t\t&& Base.equals(this._radius, item._radius);\n\t},\n\n\tcopyContent: function(source) {\n\t\tthis.setType(source._type);\n\t\tthis.setSize(source._size);\n\t\tthis.setRadius(source._radius);\n\t},\n\n\tgetType: function() {\n\t\treturn this._type;\n\t},\n\n\tsetType: function(type) {\n\t\tthis._type = type;\n\t},\n\n\tgetShape: '#getType',\n\tsetShape: '#setType',\n\n\tgetSize: function() {\n\t\tvar size = this._size;\n\t\treturn new LinkedSize(size.width, size.height, this, 'setSize');\n\t},\n\n\tsetSize: function() {\n\t\tvar size = Size.read(arguments);\n\t\tif (!this._size) {\n\t\t\tthis._size = size.clone();\n\t\t} else if (!this._size.equals(size)) {\n\t\t\tvar type = this._type,\n\t\t\t\twidth = size.width,\n\t\t\t\theight = size.height;\n\t\t\tif (type === 'rectangle') {\n\t\t\t\tthis._radius.set(Size.min(this._radius, size.divide(2).abs()));\n\t\t\t} else if (type === 'circle') {\n\t\t\t\twidth = height = (width + height) / 2;\n\t\t\t\tthis._radius = width / 2;\n\t\t\t} else if (type === 'ellipse') {\n\t\t\t\tthis._radius._set(width / 2, height / 2);\n\t\t\t}\n\t\t\tthis._size._set(width, height);\n\t\t\tthis._changed(9);\n\t\t}\n\t},\n\n\tgetRadius: function() {\n\t\tvar rad = this._radius;\n\t\treturn this._type === 'circle'\n\t\t\t\t? rad\n\t\t\t\t: new LinkedSize(rad.width, rad.height, this, 'setRadius');\n\t},\n\n\tsetRadius: function(radius) {\n\t\tvar type = this._type;\n\t\tif (type === 'circle') {\n\t\t\tif (radius === this._radius)\n\t\t\t\treturn;\n\t\t\tvar size = radius * 2;\n\t\t\tthis._radius = radius;\n\t\t\tthis._size._set(size, size);\n\t\t} else {\n\t\t\tradius = Size.read(arguments);\n\t\t\tif (!this._radius) {\n\t\t\t\tthis._radius = radius.clone();\n\t\t\t} else {\n\t\t\t\tif (this._radius.equals(radius))\n\t\t\t\t\treturn;\n\t\t\t\tthis._radius.set(radius);\n\t\t\t\tif (type === 'rectangle') {\n\t\t\t\t\tvar size = Size.max(this._size, radius.multiply(2));\n\t\t\t\t\tthis._size.set(size);\n\t\t\t\t} else if (type === 'ellipse') {\n\t\t\t\t\tthis._size._set(radius.width * 2, radius.height * 2);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis._changed(9);\n\t},\n\n\tisEmpty: function() {\n\t\treturn false;\n\t},\n\n\ttoPath: function(insert) {\n\t\tvar path = new Path[Base.capitalize(this._type)]({\n\t\t\tcenter: new Point(),\n\t\t\tsize: this._size,\n\t\t\tradius: this._radius,\n\t\t\tinsert: false\n\t\t});\n\t\tpath.copyAttributes(this);\n\t\tif (paper.settings.applyMatrix)\n\t\t\tpath.setApplyMatrix(true);\n\t\tif (insert === undefined || insert)\n\t\t\tpath.insertAbove(this);\n\t\treturn path;\n\t},\n\n\ttoShape: '#clone',\n\n\t_asPathItem: function() {\n\t\treturn this.toPath(false);\n\t},\n\n\t_draw: function(ctx, param, viewMatrix, strokeMatrix) {\n\t\tvar style = this._style,\n\t\t\thasFill = style.hasFill(),\n\t\t\thasStroke = style.hasStroke(),\n\t\t\tdontPaint = param.dontFinish || param.clip,\n\t\t\tuntransformed = !strokeMatrix;\n\t\tif (hasFill || hasStroke || dontPaint) {\n\t\t\tvar type = this._type,\n\t\t\t\tradius = this._radius,\n\t\t\t\tisCircle = type === 'circle';\n\t\t\tif (!param.dontStart)\n\t\t\t\tctx.beginPath();\n\t\t\tif (untransformed && isCircle) {\n\t\t\t\tctx.arc(0, 0, radius, 0, Math.PI * 2, true);\n\t\t\t} else {\n\t\t\t\tvar rx = isCircle ? radius : radius.width,\n\t\t\t\t\try = isCircle ? radius : radius.height,\n\t\t\t\t\tsize = this._size,\n\t\t\t\t\twidth = size.width,\n\t\t\t\t\theight = size.height;\n\t\t\t\tif (untransformed && type === 'rectangle' && rx === 0 && ry === 0) {\n\t\t\t\t\tctx.rect(-width / 2, -height / 2, width, height);\n\t\t\t\t} else {\n\t\t\t\t\tvar x = width / 2,\n\t\t\t\t\t\ty = height / 2,\n\t\t\t\t\t\tkappa = 1 - 0.5522847498307936,\n\t\t\t\t\t\tcx = rx * kappa,\n\t\t\t\t\t\tcy = ry * kappa,\n\t\t\t\t\t\tc = [\n\t\t\t\t\t\t\t-x, -y + ry,\n\t\t\t\t\t\t\t-x, -y + cy,\n\t\t\t\t\t\t\t-x + cx, -y,\n\t\t\t\t\t\t\t-x + rx, -y,\n\t\t\t\t\t\t\tx - rx, -y,\n\t\t\t\t\t\t\tx - cx, -y,\n\t\t\t\t\t\t\tx, -y + cy,\n\t\t\t\t\t\t\tx, -y + ry,\n\t\t\t\t\t\t\tx, y - ry,\n\t\t\t\t\t\t\tx, y - cy,\n\t\t\t\t\t\t\tx - cx, y,\n\t\t\t\t\t\t\tx - rx, y,\n\t\t\t\t\t\t\t-x + rx, y,\n\t\t\t\t\t\t\t-x + cx, y,\n\t\t\t\t\t\t\t-x, y - cy,\n\t\t\t\t\t\t\t-x, y - ry\n\t\t\t\t\t\t];\n\t\t\t\t\tif (strokeMatrix)\n\t\t\t\t\t\tstrokeMatrix.transform(c, c, 32);\n\t\t\t\t\tctx.moveTo(c[0], c[1]);\n\t\t\t\t\tctx.bezierCurveTo(c[2], c[3], c[4], c[5], c[6], c[7]);\n\t\t\t\t\tif (x !== rx)\n\t\t\t\t\t\tctx.lineTo(c[8], c[9]);\n\t\t\t\t\tctx.bezierCurveTo(c[10], c[11], c[12], c[13], c[14], c[15]);\n\t\t\t\t\tif (y !== ry)\n\t\t\t\t\t\tctx.lineTo(c[16], c[17]);\n\t\t\t\t\tctx.bezierCurveTo(c[18], c[19], c[20], c[21], c[22], c[23]);\n\t\t\t\t\tif (x !== rx)\n\t\t\t\t\t\tctx.lineTo(c[24], c[25]);\n\t\t\t\t\tctx.bezierCurveTo(c[26], c[27], c[28], c[29], c[30], c[31]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.closePath();\n\t\t}\n\t\tif (!dontPaint && (hasFill || hasStroke)) {\n\t\t\tthis._setStyles(ctx, param, viewMatrix, strokeMatrix);\n\t\t\tif (hasFill) {\n\t\t\t\tctx.fill(style.getFillRule());\n\t\t\t\tctx.shadowColor = 'rgba(0,0,0,0)';\n\t\t\t}\n\t\t\tif (hasStroke)\n\t\t\t\tctx.stroke();\n\t\t}\n\t},\n\n\t_canComposite: function() {\n\t\treturn !(this.hasFill() && this.hasStroke());\n\t},\n\n\t_getBounds: function(matrix, options) {\n\t\tvar rect = new Rectangle(this._size).setCenter(0, 0),\n\t\t\tstyle = this._style,\n\t\t\tstrokeWidth = options.stroke && style.hasStroke()\n\t\t\t\t\t&& style.getStrokeWidth();\n\t\tif (matrix)\n\t\t\trect = matrix._transformBounds(rect);\n\t\treturn strokeWidth\n\t\t\t\t? rect.expand(Path._getStrokePadding(strokeWidth,\n\t\t\t\t\tthis._getStrokeMatrix(matrix, options)))\n\t\t\t\t: rect;\n\t}\n},\nnew function() {\n\tfunction getCornerCenter(that, point, expand) {\n\t\tvar radius = that._radius;\n\t\tif (!radius.isZero()) {\n\t\t\tvar halfSize = that._size.divide(2);\n\t\t\tfor (var q = 1; q <= 4; q++) {\n\t\t\t\tvar dir = new Point(q > 1 && q < 4 ? -1 : 1, q > 2 ? -1 : 1),\n\t\t\t\t\tcorner = dir.multiply(halfSize),\n\t\t\t\t\tcenter = corner.subtract(dir.multiply(radius)),\n\t\t\t\t\trect = new Rectangle(\n\t\t\t\t\t\t\texpand ? corner.add(dir.multiply(expand)) : corner,\n\t\t\t\t\t\t\tcenter);\n\t\t\t\tif (rect.contains(point))\n\t\t\t\t\treturn { point: center, quadrant: q };\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction isOnEllipseStroke(point, radius, padding, quadrant) {\n\t\tvar vector = point.divide(radius);\n\t\treturn (!quadrant || vector.isInQuadrant(quadrant)) &&\n\t\t\t\tvector.subtract(vector.normalize()).multiply(radius)\n\t\t\t\t\t.divide(padding).length <= 1;\n\t}\n\n\treturn {\n\t\t_contains: function _contains(point) {\n\t\t\tif (this._type === 'rectangle') {\n\t\t\t\tvar center = getCornerCenter(this, point);\n\t\t\t\treturn center\n\t\t\t\t\t\t? point.subtract(center.point).divide(this._radius)\n\t\t\t\t\t\t\t.getLength() <= 1\n\t\t\t\t\t\t: _contains.base.call(this, point);\n\t\t\t} else {\n\t\t\t\treturn point.divide(this.size).getLength() <= 0.5;\n\t\t\t}\n\t\t},\n\n\t\t_hitTestSelf: function _hitTestSelf(point, options, viewMatrix,\n\t\t\t\tstrokeMatrix) {\n\t\t\tvar hit = false,\n\t\t\t\tstyle = this._style,\n\t\t\t\thitStroke = options.stroke && style.hasStroke(),\n\t\t\t\thitFill = options.fill && style.hasFill();\n\t\t\tif (hitStroke || hitFill) {\n\t\t\t\tvar type = this._type,\n\t\t\t\t\tradius = this._radius,\n\t\t\t\t\tstrokeRadius = hitStroke ? style.getStrokeWidth() / 2 : 0,\n\t\t\t\t\tstrokePadding = options._tolerancePadding.add(\n\t\t\t\t\t\tPath._getStrokePadding(strokeRadius,\n\t\t\t\t\t\t\t!style.getStrokeScaling() && strokeMatrix));\n\t\t\t\tif (type === 'rectangle') {\n\t\t\t\t\tvar padding = strokePadding.multiply(2),\n\t\t\t\t\t\tcenter = getCornerCenter(this, point, padding);\n\t\t\t\t\tif (center) {\n\t\t\t\t\t\thit = isOnEllipseStroke(point.subtract(center.point),\n\t\t\t\t\t\t\t\tradius, strokePadding, center.quadrant);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar rect = new Rectangle(this._size).setCenter(0, 0),\n\t\t\t\t\t\t\touter = rect.expand(padding),\n\t\t\t\t\t\t\tinner = rect.expand(padding.negate());\n\t\t\t\t\t\thit = outer._containsPoint(point)\n\t\t\t\t\t\t\t\t&& !inner._containsPoint(point);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\thit = isOnEllipseStroke(point, radius, strokePadding);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn hit ? new HitResult(hitStroke ? 'stroke' : 'fill', this)\n\t\t\t\t\t: _hitTestSelf.base.apply(this, arguments);\n\t\t}\n\t};\n}, {\n\nstatics: new function() {\n\tfunction createShape(type, point, size, radius, args) {\n\t\tvar item = Base.create(Shape.prototype);\n\t\titem._type = type;\n\t\titem._size = size;\n\t\titem._radius = radius;\n\t\titem._initialize(Base.getNamed(args), point);\n\t\treturn item;\n\t}\n\n\treturn {\n\t\tCircle: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tcenter = Point.readNamed(args, 'center'),\n\t\t\t\tradius = Base.readNamed(args, 'radius');\n\t\t\treturn createShape('circle', center, new Size(radius * 2), radius,\n\t\t\t\t\targs);\n\t\t},\n\n\t\tRectangle: function() {\n\t\t\tvar args = arguments,\n\t\t\t\trect = Rectangle.readNamed(args, 'rectangle'),\n\t\t\t\tradius = Size.min(Size.readNamed(args, 'radius'),\n\t\t\t\t\t\trect.getSize(true).divide(2));\n\t\t\treturn createShape('rectangle', rect.getCenter(true),\n\t\t\t\t\trect.getSize(true), radius, args);\n\t\t},\n\n\t\tEllipse: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tellipse = Shape._readEllipse(args),\n\t\t\t\tradius = ellipse.radius;\n\t\t\treturn createShape('ellipse', ellipse.center, radius.multiply(2),\n\t\t\t\t\tradius, args);\n\t\t},\n\n\t\t_readEllipse: function(args) {\n\t\t\tvar center,\n\t\t\t\tradius;\n\t\t\tif (Base.hasNamed(args, 'radius')) {\n\t\t\t\tcenter = Point.readNamed(args, 'center');\n\t\t\t\tradius = Size.readNamed(args, 'radius');\n\t\t\t} else {\n\t\t\t\tvar rect = Rectangle.readNamed(args, 'rectangle');\n\t\t\t\tcenter = rect.getCenter(true);\n\t\t\t\tradius = rect.getSize(true).divide(2);\n\t\t\t}\n\t\t\treturn { center: center, radius: radius };\n\t\t}\n\t};\n}});\n\nvar Raster = Item.extend({\n\t_class: 'Raster',\n\t_applyMatrix: false,\n\t_canApplyMatrix: false,\n\t_boundsOptions: { stroke: false, handle: false },\n\t_serializeFields: {\n\t\tcrossOrigin: null,\n\t\tsource: null\n\t},\n\t_prioritize: ['crossOrigin'],\n\t_smoothing: false,\n\tbeans: true,\n\n\tinitialize: function Raster(source, position) {\n\t\tif (!this._initialize(source,\n\t\t\t\tposition !== undefined && Point.read(arguments))) {\n\t\t\tvar image,\n\t\t\t\ttype = typeof source,\n\t\t\t\tobject = type === 'string'\n\t\t\t\t\t? document.getElementById(source)\n\t\t\t\t\t: type === 'object'\n\t\t\t\t\t\t? source\n\t\t\t\t\t\t: null;\n\t\t\tif (object && object !== Item.NO_INSERT) {\n\t\t\t\tif (object.getContext || object.naturalHeight != null) {\n\t\t\t\t\timage = object;\n\t\t\t\t} else if (object) {\n\t\t\t\t\tvar size = Size.read(arguments);\n\t\t\t\t\tif (!size.isZero()) {\n\t\t\t\t\t\timage = CanvasProvider.getCanvas(size);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (image) {\n\t\t\t\tthis.setImage(image);\n\t\t\t} else {\n\t\t\t\tthis.setSource(source);\n\t\t\t}\n\t\t}\n\t\tif (!this._size) {\n\t\t\tthis._size = new Size();\n\t\t\tthis._loaded = false;\n\t\t}\n\t},\n\n\t_equals: function(item) {\n\t\treturn this.getSource() === item.getSource();\n\t},\n\n\tcopyContent: function(source) {\n\t\tvar image = source._image,\n\t\t\tcanvas = source._canvas;\n\t\tif (image) {\n\t\t\tthis._setImage(image);\n\t\t} else if (canvas) {\n\t\t\tvar copyCanvas = CanvasProvider.getCanvas(source._size);\n\t\t\tcopyCanvas.getContext('2d').drawImage(canvas, 0, 0);\n\t\t\tthis._setImage(copyCanvas);\n\t\t}\n\t\tthis._crossOrigin = source._crossOrigin;\n\t},\n\n\tgetSize: function() {\n\t\tvar size = this._size;\n\t\treturn new LinkedSize(size ? size.width : 0, size ? size.height : 0,\n\t\t\t\tthis, 'setSize');\n\t},\n\n\tsetSize: function() {\n\t\tvar size = Size.read(arguments);\n\t\tif (!size.equals(this._size)) {\n\t\t\tif (size.width > 0 && size.height > 0) {\n\t\t\t\tvar element = this.getElement();\n\t\t\t\tthis._setImage(CanvasProvider.getCanvas(size));\n\t\t\t\tif (element)\n\t\t\t\t\tthis.getContext(true).drawImage(element, 0, 0,\n\t\t\t\t\t\t\tsize.width, size.height);\n\t\t\t} else {\n\t\t\t\tif (this._canvas)\n\t\t\t\t\tCanvasProvider.release(this._canvas);\n\t\t\t\tthis._size = size.clone();\n\t\t\t}\n\t\t}\n\t},\n\n\tgetWidth: function() {\n\t\treturn this._size ? this._size.width : 0;\n\t},\n\n\tsetWidth: function(width) {\n\t\tthis.setSize(width, this.getHeight());\n\t},\n\n\tgetHeight: function() {\n\t\treturn this._size ? this._size.height : 0;\n\t},\n\n\tsetHeight: function(height) {\n\t\tthis.setSize(this.getWidth(), height);\n\t},\n\n\tgetLoaded: function() {\n\t\treturn this._loaded;\n\t},\n\n\tisEmpty: function() {\n\t\tvar size = this._size;\n\t\treturn !size || size.width === 0 && size.height === 0;\n\t},\n\n\tgetResolution: function() {\n\t\tvar matrix = this._matrix,\n\t\t\torig = new Point(0, 0).transform(matrix),\n\t\t\tu = new Point(1, 0).transform(matrix).subtract(orig),\n\t\t\tv = new Point(0, 1).transform(matrix).subtract(orig);\n\t\treturn new Size(\n\t\t\t72 / u.getLength(),\n\t\t\t72 / v.getLength()\n\t\t);\n\t},\n\n\tgetPpi: '#getResolution',\n\n\tgetImage: function() {\n\t\treturn this._image;\n\t},\n\n\tsetImage: function(image) {\n\t\tvar that = this;\n\n\t\tfunction emit(event) {\n\t\t\tvar view = that.getView(),\n\t\t\t\ttype = event && event.type || 'load';\n\t\t\tif (view && that.responds(type)) {\n\t\t\t\tpaper = view._scope;\n\t\t\t\tthat.emit(type, new Event(event));\n\t\t\t}\n\t\t}\n\n\t\tthis._setImage(image);\n\t\tif (this._loaded) {\n\t\t\tsetTimeout(emit, 0);\n\t\t} else if (image) {\n\t\t\tDomEvent.add(image, {\n\t\t\t\tload: function(event) {\n\t\t\t\t\tthat._setImage(image);\n\t\t\t\t\temit(event);\n\t\t\t\t},\n\t\t\t\terror: emit\n\t\t\t});\n\t\t}\n\t},\n\n\t_setImage: function(image) {\n\t\tif (this._canvas)\n\t\t\tCanvasProvider.release(this._canvas);\n\t\tif (image && image.getContext) {\n\t\t\tthis._image = null;\n\t\t\tthis._canvas = image;\n\t\t\tthis._loaded = true;\n\t\t} else {\n\t\t\tthis._image = image;\n\t\t\tthis._canvas = null;\n\t\t\tthis._loaded = !!(image && image.src && image.complete);\n\t\t}\n\t\tthis._size = new Size(\n\t\t\t\timage ? image.naturalWidth || image.width : 0,\n\t\t\t\timage ? image.naturalHeight || image.height : 0);\n\t\tthis._context = null;\n\t\tthis._changed(1033);\n\t},\n\n\tgetCanvas: function() {\n\t\tif (!this._canvas) {\n\t\t\tvar ctx = CanvasProvider.getContext(this._size);\n\t\t\ttry {\n\t\t\t\tif (this._image)\n\t\t\t\t\tctx.drawImage(this._image, 0, 0);\n\t\t\t\tthis._canvas = ctx.canvas;\n\t\t\t} catch (e) {\n\t\t\t\tCanvasProvider.release(ctx);\n\t\t\t}\n\t\t}\n\t\treturn this._canvas;\n\t},\n\n\tsetCanvas: '#setImage',\n\n\tgetContext: function(_change) {\n\t\tif (!this._context)\n\t\t\tthis._context = this.getCanvas().getContext('2d');\n\t\tif (_change) {\n\t\t\tthis._image = null;\n\t\t\tthis._changed(1025);\n\t\t}\n\t\treturn this._context;\n\t},\n\n\tsetContext: function(context) {\n\t\tthis._context = context;\n\t},\n\n\tgetSource: function() {\n\t\tvar image = this._image;\n\t\treturn image && image.src || this.toDataURL();\n\t},\n\n\tsetSource: function(src) {\n\t\tvar image = new self.Image(),\n\t\t\tcrossOrigin = this._crossOrigin;\n\t\tif (crossOrigin)\n\t\t\timage.crossOrigin = crossOrigin;\n\t\tif (src)\n\t\t\timage.src = src;\n\t\tthis.setImage(image);\n\t},\n\n\tgetCrossOrigin: function() {\n\t\tvar image = this._image;\n\t\treturn image && image.crossOrigin || this._crossOrigin || '';\n\t},\n\n\tsetCrossOrigin: function(crossOrigin) {\n\t\tthis._crossOrigin = crossOrigin;\n\t\tvar image = this._image;\n\t\tif (image)\n\t\t\timage.crossOrigin = crossOrigin;\n\t},\n\n\tgetSmoothing: function() {\n\t\treturn this._smoothing;\n\t},\n\n\tsetSmoothing: function(smoothing) {\n\t\tthis._smoothing = smoothing;\n\t\tthis._changed(257);\n\t},\n\n\tgetElement: function() {\n\t\treturn this._canvas || this._loaded && this._image;\n\t}\n}, {\n\tbeans: false,\n\n\tgetSubCanvas: function() {\n\t\tvar rect = Rectangle.read(arguments),\n\t\t\tctx = CanvasProvider.getContext(rect.getSize());\n\t\tvar clippedStartX = Math.max(0, rect.x);\n\t\tvar clippedStartY = Math.max(0, rect.y);\n\t\tvar clippedEndX = Math.min(this.getCanvas().width, rect.x + rect.width);\n\t\tvar clippedEndY = Math.min(this.getCanvas().height, rect.y + rect.height);\n\t\tctx.drawImage(this.getCanvas(),\n\t\t\tclippedStartX, clippedStartY,\n\t\t\tclippedEndX - clippedStartX, clippedEndY - clippedStartY,\n\t\t\tclippedStartX - rect.x, clippedStartY - rect.y,\n\t\t\tclippedEndX - clippedStartX, clippedEndY - clippedStartY\n\t\t);\n\t\treturn ctx.canvas;\n\t},\n\n\tgetSubRaster: function() {\n\t\tvar rect = Rectangle.read(arguments),\n\t\t\traster = new Raster(Item.NO_INSERT);\n\t\traster._setImage(this.getSubCanvas(rect));\n\t\traster.translate(rect.getCenter().subtract(this.getSize().divide(2)));\n\t\traster._matrix.prepend(this._matrix);\n\t\traster.insertAbove(this);\n\t\treturn raster;\n\t},\n\n\ttoDataURL: function() {\n\t\tvar image = this._image,\n\t\t\tsrc = image && image.src;\n\t\tif (/^data:/.test(src))\n\t\t\treturn src;\n\t\tvar canvas = this.getCanvas();\n\t\treturn canvas ? canvas.toDataURL.apply(canvas, arguments) : null;\n\t},\n\n\tdrawImage: function(image ) {\n\t\tvar point = Point.read(arguments, 1);\n\t\tthis.getContext(true).drawImage(image, point.x, point.y);\n\t},\n\n\tgetAverageColor: function(object) {\n\t\tvar bounds, path;\n\t\tif (!object) {\n\t\t\tbounds = this.getBounds();\n\t\t} else if (object instanceof PathItem) {\n\t\t\tpath = object;\n\t\t\tbounds = object.getBounds();\n\t\t} else if (typeof object === 'object') {\n\t\t\tif ('width' in object) {\n\t\t\t\tbounds = new Rectangle(object);\n\t\t\t} else if ('x' in object) {\n\t\t\t\tbounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1);\n\t\t\t}\n\t\t}\n\t\tif (!bounds)\n\t\t\treturn null;\n\t\tvar sampleSize = 32,\n\t\t\twidth = Math.min(bounds.width, sampleSize),\n\t\t\theight = Math.min(bounds.height, sampleSize);\n\t\tvar ctx = Raster._sampleContext;\n\t\tif (!ctx) {\n\t\t\tctx = Raster._sampleContext = CanvasProvider.getContext(\n\t\t\t\t\tnew Size(sampleSize));\n\t\t} else {\n\t\t\tctx.clearRect(0, 0, sampleSize + 1, sampleSize + 1);\n\t\t}\n\t\tctx.save();\n\t\tvar matrix = new Matrix()\n\t\t\t\t.scale(width / bounds.width, height / bounds.height)\n\t\t\t\t.translate(-bounds.x, -bounds.y);\n\t\tmatrix.applyToContext(ctx);\n\t\tif (path)\n\t\t\tpath.draw(ctx, new Base({ clip: true, matrices: [matrix] }));\n\t\tthis._matrix.applyToContext(ctx);\n\t\tvar element = this.getElement(),\n\t\t\tsize = this._size;\n\t\tif (element)\n\t\t\tctx.drawImage(element, -size.width / 2, -size.height / 2);\n\t\tctx.restore();\n\t\tvar pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width),\n\t\t\t\tMath.ceil(height)).data,\n\t\t\tchannels = [0, 0, 0],\n\t\t\ttotal = 0;\n\t\tfor (var i = 0, l = pixels.length; i < l; i += 4) {\n\t\t\tvar alpha = pixels[i + 3];\n\t\t\ttotal += alpha;\n\t\t\talpha /= 255;\n\t\t\tchannels[0] += pixels[i] * alpha;\n\t\t\tchannels[1] += pixels[i + 1] * alpha;\n\t\t\tchannels[2] += pixels[i + 2] * alpha;\n\t\t}\n\t\tfor (var i = 0; i < 3; i++)\n\t\t\tchannels[i] /= total;\n\t\treturn total ? Color.read(channels) : null;\n\t},\n\n\tgetPixel: function() {\n\t\tvar point = Point.read(arguments);\n\t\tvar data = this.getContext().getImageData(point.x, point.y, 1, 1).data;\n\t\treturn new Color('rgb', [data[0] / 255, data[1] / 255, data[2] / 255],\n\t\t\t\tdata[3] / 255);\n\t},\n\n\tsetPixel: function() {\n\t\tvar args = arguments,\n\t\t\tpoint = Point.read(args),\n\t\t\tcolor = Color.read(args),\n\t\t\tcomponents = color._convert('rgb'),\n\t\t\talpha = color._alpha,\n\t\t\tctx = this.getContext(true),\n\t\t\timageData = ctx.createImageData(1, 1),\n\t\t\tdata = imageData.data;\n\t\tdata[0] = components[0] * 255;\n\t\tdata[1] = components[1] * 255;\n\t\tdata[2] = components[2] * 255;\n\t\tdata[3] = alpha != null ? alpha * 255 : 255;\n\t\tctx.putImageData(imageData, point.x, point.y);\n\t},\n\n\tclear: function() {\n\t\tvar size = this._size;\n\t\tthis.getContext(true).clearRect(0, 0, size.width + 1, size.height + 1);\n\t},\n\n\tcreateImageData: function() {\n\t\tvar size = Size.read(arguments);\n\t\treturn this.getContext().createImageData(size.width, size.height);\n\t},\n\n\tgetImageData: function() {\n\t\tvar rect = Rectangle.read(arguments);\n\t\tif (rect.isEmpty())\n\t\t\trect = new Rectangle(this._size);\n\t\treturn this.getContext().getImageData(rect.x, rect.y,\n\t\t\t\trect.width, rect.height);\n\t},\n\n\tsetImageData: function(data ) {\n\t\tvar point = Point.read(arguments, 1);\n\t\tthis.getContext(true).putImageData(data, point.x, point.y);\n\t},\n\n\t_getBounds: function(matrix, options) {\n\t\tvar rect = new Rectangle(this._size).setCenter(0, 0);\n\t\treturn matrix ? matrix._transformBounds(rect) : rect;\n\t},\n\n\t_hitTestSelf: function(point) {\n\t\tif (this._contains(point)) {\n\t\t\tvar that = this;\n\t\t\treturn new HitResult('pixel', that, {\n\t\t\t\toffset: point.add(that._size.divide(2)).round(),\n\t\t\t\tcolor: {\n\t\t\t\t\tget: function() {\n\t\t\t\t\t\treturn that.getPixel(this.offset);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t},\n\n\t_draw: function(ctx, param, viewMatrix) {\n\t\tvar element = this.getElement();\n\t\tif (element && element.width > 0 && element.height > 0) {\n\t\t\tctx.globalAlpha = Numerical.clamp(this._opacity, 0, 1);\n\n\t\t\tthis._setStyles(ctx, param, viewMatrix);\n\n\t\t\tDomElement.setPrefixed(\n\t\t\t\tctx, 'imageSmoothingEnabled', this._smoothing\n\t\t\t);\n\n\t\t\tctx.drawImage(element,\n\t\t\t\t\t-this._size.width / 2, -this._size.height / 2);\n\t\t}\n\t},\n\n\t_canComposite: function() {\n\t\treturn true;\n\t}\n});\n\nvar SymbolItem = Item.extend({\n\t_class: 'SymbolItem',\n\t_applyMatrix: false,\n\t_canApplyMatrix: false,\n\t_boundsOptions: { stroke: true },\n\t_serializeFields: {\n\t\tsymbol: null\n\t},\n\n\tinitialize: function SymbolItem(arg0, arg1) {\n\t\tif (!this._initialize(arg0,\n\t\t\t\targ1 !== undefined && Point.read(arguments, 1)))\n\t\t\tthis.setDefinition(arg0 instanceof SymbolDefinition ?\n\t\t\t\t\targ0 : new SymbolDefinition(arg0));\n\t},\n\n\t_equals: function(item) {\n\t\treturn this._definition === item._definition;\n\t},\n\n\tcopyContent: function(source) {\n\t\tthis.setDefinition(source._definition);\n\t},\n\n\tgetDefinition: function() {\n\t\treturn this._definition;\n\t},\n\n\tsetDefinition: function(definition) {\n\t\tthis._definition = definition;\n\t\tthis._changed(9);\n\t},\n\n\tgetSymbol: '#getDefinition',\n\tsetSymbol: '#setDefinition',\n\n\tisEmpty: function() {\n\t\treturn this._definition._item.isEmpty();\n\t},\n\n\t_getBounds: function(matrix, options) {\n\t\tvar item = this._definition._item;\n\t\treturn item._getCachedBounds(item._matrix.prepended(matrix), options);\n\t},\n\n\t_hitTestSelf: function(point, options, viewMatrix) {\n\t\tvar opts = options.extend({ all: false });\n\t\tvar res = this._definition._item._hitTest(point, opts, viewMatrix);\n\t\tif (res)\n\t\t\tres.item = this;\n\t\treturn res;\n\t},\n\n\t_draw: function(ctx, param) {\n\t\tthis._definition._item.draw(ctx, param);\n\t}\n\n});\n\nvar SymbolDefinition = Base.extend({\n\t_class: 'SymbolDefinition',\n\n\tinitialize: function SymbolDefinition(item, dontCenter) {\n\t\tthis._id = UID.get();\n\t\tthis.project = paper.project;\n\t\tif (item)\n\t\t\tthis.setItem(item, dontCenter);\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\treturn dictionary.add(this, function() {\n\t\t\treturn Base.serialize([this._class, this._item],\n\t\t\t\t\toptions, false, dictionary);\n\t\t});\n\t},\n\n\t_changed: function(flags) {\n\t\tif (flags & 8)\n\t\t\tItem._clearBoundsCache(this);\n\t\tif (flags & 1)\n\t\t\tthis.project._changed(flags);\n\t},\n\n\tgetItem: function() {\n\t\treturn this._item;\n\t},\n\n\tsetItem: function(item, _dontCenter) {\n\t\tif (item._symbol)\n\t\t\titem = item.clone();\n\t\tif (this._item)\n\t\t\tthis._item._symbol = null;\n\t\tthis._item = item;\n\t\titem.remove();\n\t\titem.setSelected(false);\n\t\tif (!_dontCenter)\n\t\t\titem.setPosition(new Point());\n\t\titem._symbol = this;\n\t\tthis._changed(9);\n\t},\n\n\tgetDefinition: '#getItem',\n\tsetDefinition: '#setItem',\n\n\tplace: function(position) {\n\t\treturn new SymbolItem(this, position);\n\t},\n\n\tclone: function() {\n\t\treturn new SymbolDefinition(this._item.clone(false));\n\t},\n\n\tequals: function(symbol) {\n\t\treturn symbol === this\n\t\t\t\t|| symbol && this._item.equals(symbol._item)\n\t\t\t\t|| false;\n\t}\n});\n\nvar HitResult = Base.extend({\n\t_class: 'HitResult',\n\n\tinitialize: function HitResult(type, item, values) {\n\t\tthis.type = type;\n\t\tthis.item = item;\n\t\tif (values)\n\t\t\tthis.inject(values);\n\t},\n\n\tstatics: {\n\t\tgetOptions: function(args) {\n\t\t\tvar options = args && Base.read(args);\n\t\t\treturn new Base({\n\t\t\t\ttype: null,\n\t\t\t\ttolerance: paper.settings.hitTolerance,\n\t\t\t\tfill: !options,\n\t\t\t\tstroke: !options,\n\t\t\t\tsegments: !options,\n\t\t\t\thandles: false,\n\t\t\t\tends: false,\n\t\t\t\tposition: false,\n\t\t\t\tcenter: false,\n\t\t\t\tbounds: false,\n\t\t\t\tguides: false,\n\t\t\t\tselected: false\n\t\t\t}, options);\n\t\t}\n\t}\n});\n\nvar Segment = Base.extend({\n\t_class: 'Segment',\n\tbeans: true,\n\t_selection: 0,\n\n\tinitialize: function Segment(arg0, arg1, arg2, arg3, arg4, arg5) {\n\t\tvar count = arguments.length,\n\t\t\tpoint, handleIn, handleOut, selection;\n\t\tif (count > 0) {\n\t\t\tif (arg0 == null || typeof arg0 === 'object') {\n\t\t\t\tif (count === 1 && arg0 && 'point' in arg0) {\n\t\t\t\t\tpoint = arg0.point;\n\t\t\t\t\thandleIn = arg0.handleIn;\n\t\t\t\t\thandleOut = arg0.handleOut;\n\t\t\t\t\tselection = arg0.selection;\n\t\t\t\t} else {\n\t\t\t\t\tpoint = arg0;\n\t\t\t\t\thandleIn = arg1;\n\t\t\t\t\thandleOut = arg2;\n\t\t\t\t\tselection = arg3;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tpoint = [ arg0, arg1 ];\n\t\t\t\thandleIn = arg2 !== undefined ? [ arg2, arg3 ] : null;\n\t\t\t\thandleOut = arg4 !== undefined ? [ arg4, arg5 ] : null;\n\t\t\t}\n\t\t}\n\t\tnew SegmentPoint(point, this, '_point');\n\t\tnew SegmentPoint(handleIn, this, '_handleIn');\n\t\tnew SegmentPoint(handleOut, this, '_handleOut');\n\t\tif (selection)\n\t\t\tthis.setSelection(selection);\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\tvar point = this._point,\n\t\t\tselection = this._selection,\n\t\t\tobj = selection || this.hasHandles()\n\t\t\t\t\t? [point, this._handleIn, this._handleOut]\n\t\t\t\t\t: point;\n\t\tif (selection)\n\t\t\tobj.push(selection);\n\t\treturn Base.serialize(obj, options, true, dictionary);\n\t},\n\n\t_changed: function(point) {\n\t\tvar path = this._path;\n\t\tif (!path)\n\t\t\treturn;\n\t\tvar curves = path._curves,\n\t\t\tindex = this._index,\n\t\t\tcurve;\n\t\tif (curves) {\n\t\t\tif ((!point || point === this._point || point === this._handleIn)\n\t\t\t\t\t&& (curve = index > 0 ? curves[index - 1] : path._closed\n\t\t\t\t\t\t? curves[curves.length - 1] : null))\n\t\t\t\tcurve._changed();\n\t\t\tif ((!point || point === this._point || point === this._handleOut)\n\t\t\t\t\t&& (curve = curves[index]))\n\t\t\t\tcurve._changed();\n\t\t}\n\t\tpath._changed(41);\n\t},\n\n\tgetPoint: function() {\n\t\treturn this._point;\n\t},\n\n\tsetPoint: function() {\n\t\tthis._point.set(Point.read(arguments));\n\t},\n\n\tgetHandleIn: function() {\n\t\treturn this._handleIn;\n\t},\n\n\tsetHandleIn: function() {\n\t\tthis._handleIn.set(Point.read(arguments));\n\t},\n\n\tgetHandleOut: function() {\n\t\treturn this._handleOut;\n\t},\n\n\tsetHandleOut: function() {\n\t\tthis._handleOut.set(Point.read(arguments));\n\t},\n\n\thasHandles: function() {\n\t\treturn !this._handleIn.isZero() || !this._handleOut.isZero();\n\t},\n\n\tisSmooth: function() {\n\t\tvar handleIn = this._handleIn,\n\t\t\thandleOut = this._handleOut;\n\t\treturn !handleIn.isZero() && !handleOut.isZero()\n\t\t\t\t&& handleIn.isCollinear(handleOut);\n\t},\n\n\tclearHandles: function() {\n\t\tthis._handleIn._set(0, 0);\n\t\tthis._handleOut._set(0, 0);\n\t},\n\n\tgetSelection: function() {\n\t\treturn this._selection;\n\t},\n\n\tsetSelection: function(selection) {\n\t\tvar oldSelection = this._selection,\n\t\t\tpath = this._path;\n\t\tthis._selection = selection = selection || 0;\n\t\tif (path && selection !== oldSelection) {\n\t\t\tpath._updateSelection(this, oldSelection, selection);\n\t\t\tpath._changed(257);\n\t\t}\n\t},\n\n\t_changeSelection: function(flag, selected) {\n\t\tvar selection = this._selection;\n\t\tthis.setSelection(selected ? selection | flag : selection & ~flag);\n\t},\n\n\tisSelected: function() {\n\t\treturn !!(this._selection & 7);\n\t},\n\n\tsetSelected: function(selected) {\n\t\tthis._changeSelection(7, selected);\n\t},\n\n\tgetIndex: function() {\n\t\treturn this._index !== undefined ? this._index : null;\n\t},\n\n\tgetPath: function() {\n\t\treturn this._path || null;\n\t},\n\n\tgetCurve: function() {\n\t\tvar path = this._path,\n\t\t\tindex = this._index;\n\t\tif (path) {\n\t\t\tif (index > 0 && !path._closed\n\t\t\t\t\t&& index === path._segments.length - 1)\n\t\t\t\tindex--;\n\t\t\treturn path.getCurves()[index] || null;\n\t\t}\n\t\treturn null;\n\t},\n\n\tgetLocation: function() {\n\t\tvar curve = this.getCurve();\n\t\treturn curve\n\t\t\t\t? new CurveLocation(curve, this === curve._segment1 ? 0 : 1)\n\t\t\t\t: null;\n\t},\n\n\tgetNext: function() {\n\t\tvar segments = this._path && this._path._segments;\n\t\treturn segments && (segments[this._index + 1]\n\t\t\t\t|| this._path._closed && segments[0]) || null;\n\t},\n\n\tsmooth: function(options, _first, _last) {\n\t\tvar opts = options || {},\n\t\t\ttype = opts.type,\n\t\t\tfactor = opts.factor,\n\t\t\tprev = this.getPrevious(),\n\t\t\tnext = this.getNext(),\n\t\t\tp0 = (prev || this)._point,\n\t\t\tp1 = this._point,\n\t\t\tp2 = (next || this)._point,\n\t\t\td1 = p0.getDistance(p1),\n\t\t\td2 = p1.getDistance(p2);\n\t\tif (!type || type === 'catmull-rom') {\n\t\t\tvar a = factor === undefined ? 0.5 : factor,\n\t\t\t\td1_a = Math.pow(d1, a),\n\t\t\t\td1_2a = d1_a * d1_a,\n\t\t\t\td2_a = Math.pow(d2, a),\n\t\t\t\td2_2a = d2_a * d2_a;\n\t\t\tif (!_first && prev) {\n\t\t\t\tvar A = 2 * d2_2a + 3 * d2_a * d1_a + d1_2a,\n\t\t\t\t\tN = 3 * d2_a * (d2_a + d1_a);\n\t\t\t\tthis.setHandleIn(N !== 0\n\t\t\t\t\t? new Point(\n\t\t\t\t\t\t(d2_2a * p0._x + A * p1._x - d1_2a * p2._x) / N - p1._x,\n\t\t\t\t\t\t(d2_2a * p0._y + A * p1._y - d1_2a * p2._y) / N - p1._y)\n\t\t\t\t\t: new Point());\n\t\t\t}\n\t\t\tif (!_last && next) {\n\t\t\t\tvar A = 2 * d1_2a + 3 * d1_a * d2_a + d2_2a,\n\t\t\t\t\tN = 3 * d1_a * (d1_a + d2_a);\n\t\t\t\tthis.setHandleOut(N !== 0\n\t\t\t\t\t? new Point(\n\t\t\t\t\t\t(d1_2a * p2._x + A * p1._x - d2_2a * p0._x) / N - p1._x,\n\t\t\t\t\t\t(d1_2a * p2._y + A * p1._y - d2_2a * p0._y) / N - p1._y)\n\t\t\t\t\t: new Point());\n\t\t\t}\n\t\t} else if (type === 'geometric') {\n\t\t\tif (prev && next) {\n\t\t\t\tvar vector = p0.subtract(p2),\n\t\t\t\t\tt = factor === undefined ? 0.4 : factor,\n\t\t\t\t\tk = t * d1 / (d1 + d2);\n\t\t\t\tif (!_first)\n\t\t\t\t\tthis.setHandleIn(vector.multiply(k));\n\t\t\t\tif (!_last)\n\t\t\t\t\tthis.setHandleOut(vector.multiply(k - t));\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new Error('Smoothing method \\'' + type + '\\' not supported.');\n\t\t}\n\t},\n\n\tgetPrevious: function() {\n\t\tvar segments = this._path && this._path._segments;\n\t\treturn segments && (segments[this._index - 1]\n\t\t\t\t|| this._path._closed && segments[segments.length - 1]) || null;\n\t},\n\n\tisFirst: function() {\n\t\treturn !this._index;\n\t},\n\n\tisLast: function() {\n\t\tvar path = this._path;\n\t\treturn path && this._index === path._segments.length - 1 || false;\n\t},\n\n\treverse: function() {\n\t\tvar handleIn = this._handleIn,\n\t\t\thandleOut = this._handleOut,\n\t\t\ttmp = handleIn.clone();\n\t\thandleIn.set(handleOut);\n\t\thandleOut.set(tmp);\n\t},\n\n\treversed: function() {\n\t\treturn new Segment(this._point, this._handleOut, this._handleIn);\n\t},\n\n\tremove: function() {\n\t\treturn this._path ? !!this._path.removeSegment(this._index) : false;\n\t},\n\n\tclone: function() {\n\t\treturn new Segment(this._point, this._handleIn, this._handleOut);\n\t},\n\n\tequals: function(segment) {\n\t\treturn segment === this || segment && this._class === segment._class\n\t\t\t\t&& this._point.equals(segment._point)\n\t\t\t\t&& this._handleIn.equals(segment._handleIn)\n\t\t\t\t&& this._handleOut.equals(segment._handleOut)\n\t\t\t\t|| false;\n\t},\n\n\ttoString: function() {\n\t\tvar parts = [ 'point: ' + this._point ];\n\t\tif (!this._handleIn.isZero())\n\t\t\tparts.push('handleIn: ' + this._handleIn);\n\t\tif (!this._handleOut.isZero())\n\t\t\tparts.push('handleOut: ' + this._handleOut);\n\t\treturn '{ ' + parts.join(', ') + ' }';\n\t},\n\n\ttransform: function(matrix) {\n\t\tthis._transformCoordinates(matrix, new Array(6), true);\n\t\tthis._changed();\n\t},\n\n\tinterpolate: function(from, to, factor) {\n\t\tvar u = 1 - factor,\n\t\t\tv = factor,\n\t\t\tpoint1 = from._point,\n\t\t\tpoint2 = to._point,\n\t\t\thandleIn1 = from._handleIn,\n\t\t\thandleIn2 = to._handleIn,\n\t\t\thandleOut2 = to._handleOut,\n\t\t\thandleOut1 = from._handleOut;\n\t\tthis._point._set(\n\t\t\t\tu * point1._x + v * point2._x,\n\t\t\t\tu * point1._y + v * point2._y, true);\n\t\tthis._handleIn._set(\n\t\t\t\tu * handleIn1._x + v * handleIn2._x,\n\t\t\t\tu * handleIn1._y + v * handleIn2._y, true);\n\t\tthis._handleOut._set(\n\t\t\t\tu * handleOut1._x + v * handleOut2._x,\n\t\t\t\tu * handleOut1._y + v * handleOut2._y, true);\n\t\tthis._changed();\n\t},\n\n\t_transformCoordinates: function(matrix, coords, change) {\n\t\tvar point = this._point,\n\t\t\thandleIn = !change || !this._handleIn.isZero()\n\t\t\t\t\t? this._handleIn : null,\n\t\t\thandleOut = !change || !this._handleOut.isZero()\n\t\t\t\t\t? this._handleOut : null,\n\t\t\tx = point._x,\n\t\t\ty = point._y,\n\t\t\ti = 2;\n\t\tcoords[0] = x;\n\t\tcoords[1] = y;\n\t\tif (handleIn) {\n\t\t\tcoords[i++] = handleIn._x + x;\n\t\t\tcoords[i++] = handleIn._y + y;\n\t\t}\n\t\tif (handleOut) {\n\t\t\tcoords[i++] = handleOut._x + x;\n\t\t\tcoords[i++] = handleOut._y + y;\n\t\t}\n\t\tif (matrix) {\n\t\t\tmatrix._transformCoordinates(coords, coords, i / 2);\n\t\t\tx = coords[0];\n\t\t\ty = coords[1];\n\t\t\tif (change) {\n\t\t\t\tpoint._x = x;\n\t\t\t\tpoint._y = y;\n\t\t\t\ti = 2;\n\t\t\t\tif (handleIn) {\n\t\t\t\t\thandleIn._x = coords[i++] - x;\n\t\t\t\t\thandleIn._y = coords[i++] - y;\n\t\t\t\t}\n\t\t\t\tif (handleOut) {\n\t\t\t\t\thandleOut._x = coords[i++] - x;\n\t\t\t\t\thandleOut._y = coords[i++] - y;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (!handleIn) {\n\t\t\t\t\tcoords[i++] = x;\n\t\t\t\t\tcoords[i++] = y;\n\t\t\t\t}\n\t\t\t\tif (!handleOut) {\n\t\t\t\t\tcoords[i++] = x;\n\t\t\t\t\tcoords[i++] = y;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn coords;\n\t}\n});\n\nvar SegmentPoint = Point.extend({\n\tinitialize: function SegmentPoint(point, owner, key) {\n\t\tvar x, y,\n\t\t\tselected;\n\t\tif (!point) {\n\t\t\tx = y = 0;\n\t\t} else if ((x = point[0]) !== undefined) {\n\t\t\ty = point[1];\n\t\t} else {\n\t\t\tvar pt = point;\n\t\t\tif ((x = pt.x) === undefined) {\n\t\t\t\tpt = Point.read(arguments);\n\t\t\t\tx = pt.x;\n\t\t\t}\n\t\t\ty = pt.y;\n\t\t\tselected = pt.selected;\n\t\t}\n\t\tthis._x = x;\n\t\tthis._y = y;\n\t\tthis._owner = owner;\n\t\towner[key] = this;\n\t\tif (selected)\n\t\t\tthis.setSelected(true);\n\t},\n\n\t_set: function(x, y) {\n\t\tthis._x = x;\n\t\tthis._y = y;\n\t\tthis._owner._changed(this);\n\t\treturn this;\n\t},\n\n\tgetX: function() {\n\t\treturn this._x;\n\t},\n\n\tsetX: function(x) {\n\t\tthis._x = x;\n\t\tthis._owner._changed(this);\n\t},\n\n\tgetY: function() {\n\t\treturn this._y;\n\t},\n\n\tsetY: function(y) {\n\t\tthis._y = y;\n\t\tthis._owner._changed(this);\n\t},\n\n\tisZero: function() {\n\t\tvar isZero = Numerical.isZero;\n\t\treturn isZero(this._x) && isZero(this._y);\n\t},\n\n\tisSelected: function() {\n\t\treturn !!(this._owner._selection & this._getSelection());\n\t},\n\n\tsetSelected: function(selected) {\n\t\tthis._owner._changeSelection(this._getSelection(), selected);\n\t},\n\n\t_getSelection: function() {\n\t\tvar owner = this._owner;\n\t\treturn this === owner._point ? 1\n\t\t\t: this === owner._handleIn ? 2\n\t\t\t: this === owner._handleOut ? 4\n\t\t\t: 0;\n\t}\n});\n\nvar Curve = Base.extend({\n\t_class: 'Curve',\n\tbeans: true,\n\n\tinitialize: function Curve(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {\n\t\tvar count = arguments.length,\n\t\t\tseg1, seg2,\n\t\t\tpoint1, point2,\n\t\t\thandle1, handle2;\n\t\tif (count === 3) {\n\t\t\tthis._path = arg0;\n\t\t\tseg1 = arg1;\n\t\t\tseg2 = arg2;\n\t\t} else if (!count) {\n\t\t\tseg1 = new Segment();\n\t\t\tseg2 = new Segment();\n\t\t} else if (count === 1) {\n\t\t\tif ('segment1' in arg0) {\n\t\t\t\tseg1 = new Segment(arg0.segment1);\n\t\t\t\tseg2 = new Segment(arg0.segment2);\n\t\t\t} else if ('point1' in arg0) {\n\t\t\t\tpoint1 = arg0.point1;\n\t\t\t\thandle1 = arg0.handle1;\n\t\t\t\thandle2 = arg0.handle2;\n\t\t\t\tpoint2 = arg0.point2;\n\t\t\t} else if (Array.isArray(arg0)) {\n\t\t\t\tpoint1 = [arg0[0], arg0[1]];\n\t\t\t\tpoint2 = [arg0[6], arg0[7]];\n\t\t\t\thandle1 = [arg0[2] - arg0[0], arg0[3] - arg0[1]];\n\t\t\t\thandle2 = [arg0[4] - arg0[6], arg0[5] - arg0[7]];\n\t\t\t}\n\t\t} else if (count === 2) {\n\t\t\tseg1 = new Segment(arg0);\n\t\t\tseg2 = new Segment(arg1);\n\t\t} else if (count === 4) {\n\t\t\tpoint1 = arg0;\n\t\t\thandle1 = arg1;\n\t\t\thandle2 = arg2;\n\t\t\tpoint2 = arg3;\n\t\t} else if (count === 8) {\n\t\t\tpoint1 = [arg0, arg1];\n\t\t\tpoint2 = [arg6, arg7];\n\t\t\thandle1 = [arg2 - arg0, arg3 - arg1];\n\t\t\thandle2 = [arg4 - arg6, arg5 - arg7];\n\t\t}\n\t\tthis._segment1 = seg1 || new Segment(point1, null, handle1);\n\t\tthis._segment2 = seg2 || new Segment(point2, handle2, null);\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\treturn Base.serialize(this.hasHandles()\n\t\t\t\t? [this.getPoint1(), this.getHandle1(), this.getHandle2(),\n\t\t\t\t\tthis.getPoint2()]\n\t\t\t\t: [this.getPoint1(), this.getPoint2()],\n\t\t\t\toptions, true, dictionary);\n\t},\n\n\t_changed: function() {\n\t\tthis._length = this._bounds = undefined;\n\t},\n\n\tclone: function() {\n\t\treturn new Curve(this._segment1, this._segment2);\n\t},\n\n\ttoString: function() {\n\t\tvar parts = [ 'point1: ' + this._segment1._point ];\n\t\tif (!this._segment1._handleOut.isZero())\n\t\t\tparts.push('handle1: ' + this._segment1._handleOut);\n\t\tif (!this._segment2._handleIn.isZero())\n\t\t\tparts.push('handle2: ' + this._segment2._handleIn);\n\t\tparts.push('point2: ' + this._segment2._point);\n\t\treturn '{ ' + parts.join(', ') + ' }';\n\t},\n\n\tclassify: function() {\n\t\treturn Curve.classify(this.getValues());\n\t},\n\n\tremove: function() {\n\t\tvar removed = false;\n\t\tif (this._path) {\n\t\t\tvar segment2 = this._segment2,\n\t\t\t\thandleOut = segment2._handleOut;\n\t\t\tremoved = segment2.remove();\n\t\t\tif (removed)\n\t\t\t\tthis._segment1._handleOut.set(handleOut);\n\t\t}\n\t\treturn removed;\n\t},\n\n\tgetPoint1: function() {\n\t\treturn this._segment1._point;\n\t},\n\n\tsetPoint1: function() {\n\t\tthis._segment1._point.set(Point.read(arguments));\n\t},\n\n\tgetPoint2: function() {\n\t\treturn this._segment2._point;\n\t},\n\n\tsetPoint2: function() {\n\t\tthis._segment2._point.set(Point.read(arguments));\n\t},\n\n\tgetHandle1: function() {\n\t\treturn this._segment1._handleOut;\n\t},\n\n\tsetHandle1: function() {\n\t\tthis._segment1._handleOut.set(Point.read(arguments));\n\t},\n\n\tgetHandle2: function() {\n\t\treturn this._segment2._handleIn;\n\t},\n\n\tsetHandle2: function() {\n\t\tthis._segment2._handleIn.set(Point.read(arguments));\n\t},\n\n\tgetSegment1: function() {\n\t\treturn this._segment1;\n\t},\n\n\tgetSegment2: function() {\n\t\treturn this._segment2;\n\t},\n\n\tgetPath: function() {\n\t\treturn this._path;\n\t},\n\n\tgetIndex: function() {\n\t\treturn this._segment1._index;\n\t},\n\n\tgetNext: function() {\n\t\tvar curves = this._path && this._path._curves;\n\t\treturn curves && (curves[this._segment1._index + 1]\n\t\t\t\t|| this._path._closed && curves[0]) || null;\n\t},\n\n\tgetPrevious: function() {\n\t\tvar curves = this._path && this._path._curves;\n\t\treturn curves && (curves[this._segment1._index - 1]\n\t\t\t\t|| this._path._closed && curves[curves.length - 1]) || null;\n\t},\n\n\tisFirst: function() {\n\t\treturn !this._segment1._index;\n\t},\n\n\tisLast: function() {\n\t\tvar path = this._path;\n\t\treturn path && this._segment1._index === path._curves.length - 1\n\t\t\t\t|| false;\n\t},\n\n\tisSelected: function() {\n\t\treturn this.getPoint1().isSelected()\n\t\t\t\t&& this.getHandle1().isSelected()\n\t\t\t\t&& this.getHandle2().isSelected()\n\t\t\t\t&& this.getPoint2().isSelected();\n\t},\n\n\tsetSelected: function(selected) {\n\t\tthis.getPoint1().setSelected(selected);\n\t\tthis.getHandle1().setSelected(selected);\n\t\tthis.getHandle2().setSelected(selected);\n\t\tthis.getPoint2().setSelected(selected);\n\t},\n\n\tgetValues: function(matrix) {\n\t\treturn Curve.getValues(this._segment1, this._segment2, matrix);\n\t},\n\n\tgetPoints: function() {\n\t\tvar coords = this.getValues(),\n\t\t\tpoints = [];\n\t\tfor (var i = 0; i < 8; i += 2)\n\t\t\tpoints.push(new Point(coords[i], coords[i + 1]));\n\t\treturn points;\n\t}\n}, {\n\tgetLength: function() {\n\t\tif (this._length == null)\n\t\t\tthis._length = Curve.getLength(this.getValues(), 0, 1);\n\t\treturn this._length;\n\t},\n\n\tgetArea: function() {\n\t\treturn Curve.getArea(this.getValues());\n\t},\n\n\tgetLine: function() {\n\t\treturn new Line(this._segment1._point, this._segment2._point);\n\t},\n\n\tgetPart: function(from, to) {\n\t\treturn new Curve(Curve.getPart(this.getValues(), from, to));\n\t},\n\n\tgetPartLength: function(from, to) {\n\t\treturn Curve.getLength(this.getValues(), from, to);\n\t},\n\n\tdivideAt: function(location) {\n\t\treturn this.divideAtTime(location && location.curve === this\n\t\t\t\t? location.time : this.getTimeAt(location));\n\t},\n\n\tdivideAtTime: function(time, _setHandles) {\n\t\tvar tMin = 1e-8,\n\t\t\ttMax = 1 - tMin,\n\t\t\tres = null;\n\t\tif (time >= tMin && time <= tMax) {\n\t\t\tvar parts = Curve.subdivide(this.getValues(), time),\n\t\t\t\tleft = parts[0],\n\t\t\t\tright = parts[1],\n\t\t\t\tsetHandles = _setHandles || this.hasHandles(),\n\t\t\t\tseg1 = this._segment1,\n\t\t\t\tseg2 = this._segment2,\n\t\t\t\tpath = this._path;\n\t\t\tif (setHandles) {\n\t\t\t\tseg1._handleOut._set(left[2] - left[0], left[3] - left[1]);\n\t\t\t\tseg2._handleIn._set(right[4] - right[6],right[5] - right[7]);\n\t\t\t}\n\t\t\tvar x = left[6], y = left[7],\n\t\t\t\tsegment = new Segment(new Point(x, y),\n\t\t\t\t\t\tsetHandles && new Point(left[4] - x, left[5] - y),\n\t\t\t\t\t\tsetHandles && new Point(right[2] - x, right[3] - y));\n\t\t\tif (path) {\n\t\t\t\tpath.insert(seg1._index + 1, segment);\n\t\t\t\tres = this.getNext();\n\t\t\t} else {\n\t\t\t\tthis._segment2 = segment;\n\t\t\t\tthis._changed();\n\t\t\t\tres = new Curve(segment, seg2);\n\t\t\t}\n\t\t}\n\t\treturn res;\n\t},\n\n\tsplitAt: function(location) {\n\t\tvar path = this._path;\n\t\treturn path ? path.splitAt(location) : null;\n\t},\n\n\tsplitAtTime: function(time) {\n\t\treturn this.splitAt(this.getLocationAtTime(time));\n\t},\n\n\tdivide: function(offset, isTime) {\n\t\treturn this.divideAtTime(offset === undefined ? 0.5 : isTime ? offset\n\t\t\t\t: this.getTimeAt(offset));\n\t},\n\n\tsplit: function(offset, isTime) {\n\t\treturn this.splitAtTime(offset === undefined ? 0.5 : isTime ? offset\n\t\t\t\t: this.getTimeAt(offset));\n\t},\n\n\treversed: function() {\n\t\treturn new Curve(this._segment2.reversed(), this._segment1.reversed());\n\t},\n\n\tclearHandles: function() {\n\t\tthis._segment1._handleOut._set(0, 0);\n\t\tthis._segment2._handleIn._set(0, 0);\n\t},\n\nstatics: {\n\tgetValues: function(segment1, segment2, matrix, straight) {\n\t\tvar p1 = segment1._point,\n\t\t\th1 = segment1._handleOut,\n\t\t\th2 = segment2._handleIn,\n\t\t\tp2 = segment2._point,\n\t\t\tx1 = p1.x, y1 = p1.y,\n\t\t\tx2 = p2.x, y2 = p2.y,\n\t\t\tvalues = straight\n\t\t\t\t? [ x1, y1, x1, y1, x2, y2, x2, y2 ]\n\t\t\t\t: [\n\t\t\t\t\tx1, y1,\n\t\t\t\t\tx1 + h1._x, y1 + h1._y,\n\t\t\t\t\tx2 + h2._x, y2 + h2._y,\n\t\t\t\t\tx2, y2\n\t\t\t\t];\n\t\tif (matrix)\n\t\t\tmatrix._transformCoordinates(values, values, 4);\n\t\treturn values;\n\t},\n\n\tsubdivide: function(v, t) {\n\t\tvar x0 = v[0], y0 = v[1],\n\t\t\tx1 = v[2], y1 = v[3],\n\t\t\tx2 = v[4], y2 = v[5],\n\t\t\tx3 = v[6], y3 = v[7];\n\t\tif (t === undefined)\n\t\t\tt = 0.5;\n\t\tvar u = 1 - t,\n\t\t\tx4 = u * x0 + t * x1, y4 = u * y0 + t * y1,\n\t\t\tx5 = u * x1 + t * x2, y5 = u * y1 + t * y2,\n\t\t\tx6 = u * x2 + t * x3, y6 = u * y2 + t * y3,\n\t\t\tx7 = u * x4 + t * x5, y7 = u * y4 + t * y5,\n\t\t\tx8 = u * x5 + t * x6, y8 = u * y5 + t * y6,\n\t\t\tx9 = u * x7 + t * x8, y9 = u * y7 + t * y8;\n\t\treturn [\n\t\t\t[x0, y0, x4, y4, x7, y7, x9, y9],\n\t\t\t[x9, y9, x8, y8, x6, y6, x3, y3]\n\t\t];\n\t},\n\n\tgetMonoCurves: function(v, dir) {\n\t\tvar curves = [],\n\t\t\tio = dir ? 0 : 1,\n\t\t\to0 = v[io + 0],\n\t\t\to1 = v[io + 2],\n\t\t\to2 = v[io + 4],\n\t\t\to3 = v[io + 6];\n\t\tif ((o0 >= o1) === (o1 >= o2) && (o1 >= o2) === (o2 >= o3)\n\t\t\t\t|| Curve.isStraight(v)) {\n\t\t\tcurves.push(v);\n\t\t} else {\n\t\t\tvar a = 3 * (o1 - o2) - o0 + o3,\n\t\t\t\tb = 2 * (o0 + o2) - 4 * o1,\n\t\t\t\tc = o1 - o0,\n\t\t\t\ttMin = 1e-8,\n\t\t\t\ttMax = 1 - tMin,\n\t\t\t\troots = [],\n\t\t\t\tn = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax);\n\t\t\tif (!n) {\n\t\t\t\tcurves.push(v);\n\t\t\t} else {\n\t\t\t\troots.sort();\n\t\t\t\tvar t = roots[0],\n\t\t\t\t\tparts = Curve.subdivide(v, t);\n\t\t\t\tcurves.push(parts[0]);\n\t\t\t\tif (n > 1) {\n\t\t\t\t\tt = (roots[1] - t) / (1 - t);\n\t\t\t\t\tparts = Curve.subdivide(parts[1], t);\n\t\t\t\t\tcurves.push(parts[0]);\n\t\t\t\t}\n\t\t\t\tcurves.push(parts[1]);\n\t\t\t}\n\t\t}\n\t\treturn curves;\n\t},\n\n\tsolveCubic: function (v, coord, val, roots, min, max) {\n\t\tvar v0 = v[coord],\n\t\t\tv1 = v[coord + 2],\n\t\t\tv2 = v[coord + 4],\n\t\t\tv3 = v[coord + 6],\n\t\t\tres = 0;\n\t\tif ( !(v0 < val && v3 < val && v1 < val && v2 < val ||\n\t\t\t\tv0 > val && v3 > val && v1 > val && v2 > val)) {\n\t\t\tvar c = 3 * (v1 - v0),\n\t\t\t\tb = 3 * (v2 - v1) - c,\n\t\t\t\ta = v3 - v0 - c - b;\n\t\t\tres = Numerical.solveCubic(a, b, c, v0 - val, roots, min, max);\n\t\t}\n\t\treturn res;\n\t},\n\n\tgetTimeOf: function(v, point) {\n\t\tvar p0 = new Point(v[0], v[1]),\n\t\t\tp3 = new Point(v[6], v[7]),\n\t\t\tepsilon = 1e-12,\n\t\t\tgeomEpsilon = 1e-7,\n\t\t\tt = point.isClose(p0, epsilon) ? 0\n\t\t\t : point.isClose(p3, epsilon) ? 1\n\t\t\t : null;\n\t\tif (t === null) {\n\t\t\tvar coords = [point.x, point.y],\n\t\t\t\troots = [];\n\t\t\tfor (var c = 0; c < 2; c++) {\n\t\t\t\tvar count = Curve.solveCubic(v, c, coords[c], roots, 0, 1);\n\t\t\t\tfor (var i = 0; i < count; i++) {\n\t\t\t\t\tvar u = roots[i];\n\t\t\t\t\tif (point.isClose(Curve.getPoint(v, u), geomEpsilon))\n\t\t\t\t\t\treturn u;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn point.isClose(p0, geomEpsilon) ? 0\n\t\t\t : point.isClose(p3, geomEpsilon) ? 1\n\t\t\t : null;\n\t},\n\n\tgetNearestTime: function(v, point) {\n\t\tif (Curve.isStraight(v)) {\n\t\t\tvar x0 = v[0], y0 = v[1],\n\t\t\t\tx3 = v[6], y3 = v[7],\n\t\t\t\tvx = x3 - x0, vy = y3 - y0,\n\t\t\t\tdet = vx * vx + vy * vy;\n\t\t\tif (det === 0)\n\t\t\t\treturn 0;\n\t\t\tvar u = ((point.x - x0) * vx + (point.y - y0) * vy) / det;\n\t\t\treturn u < 1e-12 ? 0\n\t\t\t\t : u > 0.999999999999 ? 1\n\t\t\t\t : Curve.getTimeOf(v,\n\t\t\t\t\tnew Point(x0 + u * vx, y0 + u * vy));\n\t\t}\n\n\t\tvar count = 100,\n\t\t\tminDist = Infinity,\n\t\t\tminT = 0;\n\n\t\tfunction refine(t) {\n\t\t\tif (t >= 0 && t <= 1) {\n\t\t\t\tvar dist = point.getDistance(Curve.getPoint(v, t), true);\n\t\t\t\tif (dist < minDist) {\n\t\t\t\t\tminDist = dist;\n\t\t\t\t\tminT = t;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (var i = 0; i <= count; i++)\n\t\t\trefine(i / count);\n\n\t\tvar step = 1 / (count * 2);\n\t\twhile (step > 1e-8) {\n\t\t\tif (!refine(minT - step) && !refine(minT + step))\n\t\t\t\tstep /= 2;\n\t\t}\n\t\treturn minT;\n\t},\n\n\tgetPart: function(v, from, to) {\n\t\tvar flip = from > to;\n\t\tif (flip) {\n\t\t\tvar tmp = from;\n\t\t\tfrom = to;\n\t\t\tto = tmp;\n\t\t}\n\t\tif (from > 0)\n\t\t\tv = Curve.subdivide(v, from)[1];\n\t\tif (to < 1)\n\t\t\tv = Curve.subdivide(v, (to - from) / (1 - from))[0];\n\t\treturn flip\n\t\t\t\t? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]]\n\t\t\t\t: v;\n\t},\n\n\tisFlatEnough: function(v, flatness) {\n\t\tvar x0 = v[0], y0 = v[1],\n\t\t\tx1 = v[2], y1 = v[3],\n\t\t\tx2 = v[4], y2 = v[5],\n\t\t\tx3 = v[6], y3 = v[7],\n\t\t\tux = 3 * x1 - 2 * x0 - x3,\n\t\t\tuy = 3 * y1 - 2 * y0 - y3,\n\t\t\tvx = 3 * x2 - 2 * x3 - x0,\n\t\t\tvy = 3 * y2 - 2 * y3 - y0;\n\t\treturn Math.max(ux * ux, vx * vx) + Math.max(uy * uy, vy * vy)\n\t\t\t\t<= 16 * flatness * flatness;\n\t},\n\n\tgetArea: function(v) {\n\t\tvar x0 = v[0], y0 = v[1],\n\t\t\tx1 = v[2], y1 = v[3],\n\t\t\tx2 = v[4], y2 = v[5],\n\t\t\tx3 = v[6], y3 = v[7];\n\t\treturn 3 * ((y3 - y0) * (x1 + x2) - (x3 - x0) * (y1 + y2)\n\t\t\t\t+ y1 * (x0 - x2) - x1 * (y0 - y2)\n\t\t\t\t+ y3 * (x2 + x0 / 3) - x3 * (y2 + y0 / 3)) / 20;\n\t},\n\n\tgetBounds: function(v) {\n\t\tvar min = v.slice(0, 2),\n\t\t\tmax = min.slice(),\n\t\t\troots = [0, 0];\n\t\tfor (var i = 0; i < 2; i++)\n\t\t\tCurve._addBounds(v[i], v[i + 2], v[i + 4], v[i + 6],\n\t\t\t\t\ti, 0, min, max, roots);\n\t\treturn new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);\n\t},\n\n\t_addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) {\n\t\tfunction add(value, padding) {\n\t\t\tvar left = value - padding,\n\t\t\t\tright = value + padding;\n\t\t\tif (left < min[coord])\n\t\t\t\tmin[coord] = left;\n\t\t\tif (right > max[coord])\n\t\t\t\tmax[coord] = right;\n\t\t}\n\n\t\tpadding /= 2;\n\t\tvar minPad = min[coord] - padding,\n\t\t\tmaxPad = max[coord] + padding;\n\t\tif ( v0 < minPad || v1 < minPad || v2 < minPad || v3 < minPad ||\n\t\t\t\tv0 > maxPad || v1 > maxPad || v2 > maxPad || v3 > maxPad) {\n\t\t\tif (v1 < v0 != v1 < v3 && v2 < v0 != v2 < v3) {\n\t\t\t\tadd(v0, padding);\n\t\t\t\tadd(v3, padding);\n\t\t\t} else {\n\t\t\t\tvar a = 3 * (v1 - v2) - v0 + v3,\n\t\t\t\t\tb = 2 * (v0 + v2) - 4 * v1,\n\t\t\t\t\tc = v1 - v0,\n\t\t\t\t\tcount = Numerical.solveQuadratic(a, b, c, roots),\n\t\t\t\t\ttMin = 1e-8,\n\t\t\t\t\ttMax = 1 - tMin;\n\t\t\t\tadd(v3, 0);\n\t\t\t\tfor (var i = 0; i < count; i++) {\n\t\t\t\t\tvar t = roots[i],\n\t\t\t\t\t\tu = 1 - t;\n\t\t\t\t\tif (tMin <= t && t <= tMax)\n\t\t\t\t\t\tadd(u * u * u * v0\n\t\t\t\t\t\t\t+ 3 * u * u * t * v1\n\t\t\t\t\t\t\t+ 3 * u * t * t * v2\n\t\t\t\t\t\t\t+ t * t * t * v3,\n\t\t\t\t\t\t\tpadding);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}}, Base.each(\n\t['getBounds', 'getStrokeBounds', 'getHandleBounds'],\n\tfunction(name) {\n\t\tthis[name] = function() {\n\t\t\tif (!this._bounds)\n\t\t\t\tthis._bounds = {};\n\t\t\tvar bounds = this._bounds[name];\n\t\t\tif (!bounds) {\n\t\t\t\tbounds = this._bounds[name] = Path[name](\n\t\t\t\t\t\t[this._segment1, this._segment2], false, this._path);\n\t\t\t}\n\t\t\treturn bounds.clone();\n\t\t};\n\t},\n{\n\n}), Base.each({\n\tisStraight: function(p1, h1, h2, p2) {\n\t\tif (h1.isZero() && h2.isZero()) {\n\t\t\treturn true;\n\t\t} else {\n\t\t\tvar v = p2.subtract(p1);\n\t\t\tif (v.isZero()) {\n\t\t\t\treturn false;\n\t\t\t} else if (v.isCollinear(h1) && v.isCollinear(h2)) {\n\t\t\t\tvar l = new Line(p1, p2),\n\t\t\t\t\tepsilon = 1e-7;\n\t\t\t\tif (l.getDistance(p1.add(h1)) < epsilon &&\n\t\t\t\t\tl.getDistance(p2.add(h2)) < epsilon) {\n\t\t\t\t\tvar div = v.dot(v),\n\t\t\t\t\t\ts1 = v.dot(h1) / div,\n\t\t\t\t\t\ts2 = v.dot(h2) / div;\n\t\t\t\t\treturn s1 >= 0 && s1 <= 1 && s2 <= 0 && s2 >= -1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t},\n\n\tisLinear: function(p1, h1, h2, p2) {\n\t\tvar third = p2.subtract(p1).divide(3);\n\t\treturn h1.equals(third) && h2.negate().equals(third);\n\t}\n}, function(test, name) {\n\tthis[name] = function(epsilon) {\n\t\tvar seg1 = this._segment1,\n\t\t\tseg2 = this._segment2;\n\t\treturn test(seg1._point, seg1._handleOut, seg2._handleIn, seg2._point,\n\t\t\t\tepsilon);\n\t};\n\n\tthis.statics[name] = function(v, epsilon) {\n\t\tvar x0 = v[0], y0 = v[1],\n\t\t\tx3 = v[6], y3 = v[7];\n\t\treturn test(\n\t\t\t\tnew Point(x0, y0),\n\t\t\t\tnew Point(v[2] - x0, v[3] - y0),\n\t\t\t\tnew Point(v[4] - x3, v[5] - y3),\n\t\t\t\tnew Point(x3, y3), epsilon);\n\t};\n}, {\n\tstatics: {},\n\n\thasHandles: function() {\n\t\treturn !this._segment1._handleOut.isZero()\n\t\t\t\t|| !this._segment2._handleIn.isZero();\n\t},\n\n\thasLength: function(epsilon) {\n\t\treturn (!this.getPoint1().equals(this.getPoint2()) || this.hasHandles())\n\t\t\t\t&& this.getLength() > (epsilon || 0);\n\t},\n\n\tisCollinear: function(curve) {\n\t\treturn curve && this.isStraight() && curve.isStraight()\n\t\t\t\t&& this.getLine().isCollinear(curve.getLine());\n\t},\n\n\tisHorizontal: function() {\n\t\treturn this.isStraight() && Math.abs(this.getTangentAtTime(0.5).y)\n\t\t\t\t< 1e-8;\n\t},\n\n\tisVertical: function() {\n\t\treturn this.isStraight() && Math.abs(this.getTangentAtTime(0.5).x)\n\t\t\t\t< 1e-8;\n\t}\n}), {\n\tbeans: false,\n\n\tgetLocationAt: function(offset, _isTime) {\n\t\treturn this.getLocationAtTime(\n\t\t\t\t_isTime ? offset : this.getTimeAt(offset));\n\t},\n\n\tgetLocationAtTime: function(t) {\n\t\treturn t != null && t >= 0 && t <= 1\n\t\t\t\t? new CurveLocation(this, t)\n\t\t\t\t: null;\n\t},\n\n\tgetTimeAt: function(offset, start) {\n\t\treturn Curve.getTimeAt(this.getValues(), offset, start);\n\t},\n\n\tgetParameterAt: '#getTimeAt',\n\n\tgetTimesWithTangent: function () {\n\t\tvar tangent = Point.read(arguments);\n\t\treturn tangent.isZero()\n\t\t\t\t? []\n\t\t\t\t: Curve.getTimesWithTangent(this.getValues(), tangent);\n\t},\n\n\tgetOffsetAtTime: function(t) {\n\t\treturn this.getPartLength(0, t);\n\t},\n\n\tgetLocationOf: function() {\n\t\treturn this.getLocationAtTime(this.getTimeOf(Point.read(arguments)));\n\t},\n\n\tgetOffsetOf: function() {\n\t\tvar loc = this.getLocationOf.apply(this, arguments);\n\t\treturn loc ? loc.getOffset() : null;\n\t},\n\n\tgetTimeOf: function() {\n\t\treturn Curve.getTimeOf(this.getValues(), Point.read(arguments));\n\t},\n\n\tgetParameterOf: '#getTimeOf',\n\n\tgetNearestLocation: function() {\n\t\tvar point = Point.read(arguments),\n\t\t\tvalues = this.getValues(),\n\t\t\tt = Curve.getNearestTime(values, point),\n\t\t\tpt = Curve.getPoint(values, t);\n\t\treturn new CurveLocation(this, t, pt, null, point.getDistance(pt));\n\t},\n\n\tgetNearestPoint: function() {\n\t\tvar loc = this.getNearestLocation.apply(this, arguments);\n\t\treturn loc ? loc.getPoint() : loc;\n\t}\n\n},\nnew function() {\n\tvar methods = ['getPoint', 'getTangent', 'getNormal', 'getWeightedTangent',\n\t\t'getWeightedNormal', 'getCurvature'];\n\treturn Base.each(methods,\n\t\tfunction(name) {\n\t\t\tthis[name + 'At'] = function(location, _isTime) {\n\t\t\t\tvar values = this.getValues();\n\t\t\t\treturn Curve[name](values, _isTime ? location\n\t\t\t\t\t\t: Curve.getTimeAt(values, location));\n\t\t\t};\n\n\t\t\tthis[name + 'AtTime'] = function(time) {\n\t\t\t\treturn Curve[name](this.getValues(), time);\n\t\t\t};\n\t\t}, {\n\t\t\tstatics: {\n\t\t\t\t_evaluateMethods: methods\n\t\t\t}\n\t\t}\n\t);\n},\nnew function() {\n\n\tfunction getLengthIntegrand(v) {\n\t\tvar x0 = v[0], y0 = v[1],\n\t\t\tx1 = v[2], y1 = v[3],\n\t\t\tx2 = v[4], y2 = v[5],\n\t\t\tx3 = v[6], y3 = v[7],\n\n\t\t\tax = 9 * (x1 - x2) + 3 * (x3 - x0),\n\t\t\tbx = 6 * (x0 + x2) - 12 * x1,\n\t\t\tcx = 3 * (x1 - x0),\n\n\t\t\tay = 9 * (y1 - y2) + 3 * (y3 - y0),\n\t\t\tby = 6 * (y0 + y2) - 12 * y1,\n\t\t\tcy = 3 * (y1 - y0);\n\n\t\treturn function(t) {\n\t\t\tvar dx = (ax * t + bx) * t + cx,\n\t\t\t\tdy = (ay * t + by) * t + cy;\n\t\t\treturn Math.sqrt(dx * dx + dy * dy);\n\t\t};\n\t}\n\n\tfunction getIterations(a, b) {\n\t\treturn Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));\n\t}\n\n\tfunction evaluate(v, t, type, normalized) {\n\t\tif (t == null || t < 0 || t > 1)\n\t\t\treturn null;\n\t\tvar x0 = v[0], y0 = v[1],\n\t\t\tx1 = v[2], y1 = v[3],\n\t\t\tx2 = v[4], y2 = v[5],\n\t\t\tx3 = v[6], y3 = v[7],\n\t\t\tisZero = Numerical.isZero;\n\t\tif (isZero(x1 - x0) && isZero(y1 - y0)) {\n\t\t\tx1 = x0;\n\t\t\ty1 = y0;\n\t\t}\n\t\tif (isZero(x2 - x3) && isZero(y2 - y3)) {\n\t\t\tx2 = x3;\n\t\t\ty2 = y3;\n\t\t}\n\t\tvar cx = 3 * (x1 - x0),\n\t\t\tbx = 3 * (x2 - x1) - cx,\n\t\t\tax = x3 - x0 - cx - bx,\n\t\t\tcy = 3 * (y1 - y0),\n\t\t\tby = 3 * (y2 - y1) - cy,\n\t\t\tay = y3 - y0 - cy - by,\n\t\t\tx, y;\n\t\tif (type === 0) {\n\t\t\tx = t === 0 ? x0 : t === 1 ? x3\n\t\t\t\t\t: ((ax * t + bx) * t + cx) * t + x0;\n\t\t\ty = t === 0 ? y0 : t === 1 ? y3\n\t\t\t\t\t: ((ay * t + by) * t + cy) * t + y0;\n\t\t} else {\n\t\t\tvar tMin = 1e-8,\n\t\t\t\ttMax = 1 - tMin;\n\t\t\tif (t < tMin) {\n\t\t\t\tx = cx;\n\t\t\t\ty = cy;\n\t\t\t} else if (t > tMax) {\n\t\t\t\tx = 3 * (x3 - x2);\n\t\t\t\ty = 3 * (y3 - y2);\n\t\t\t} else {\n\t\t\t\tx = (3 * ax * t + 2 * bx) * t + cx;\n\t\t\t\ty = (3 * ay * t + 2 * by) * t + cy;\n\t\t\t}\n\t\t\tif (normalized) {\n\t\t\t\tif (x === 0 && y === 0 && (t < tMin || t > tMax)) {\n\t\t\t\t\tx = x2 - x1;\n\t\t\t\t\ty = y2 - y1;\n\t\t\t\t}\n\t\t\t\tvar len = Math.sqrt(x * x + y * y);\n\t\t\t\tif (len) {\n\t\t\t\t\tx /= len;\n\t\t\t\t\ty /= len;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (type === 3) {\n\t\t\t\tvar x2 = 6 * ax * t + 2 * bx,\n\t\t\t\t\ty2 = 6 * ay * t + 2 * by,\n\t\t\t\t\td = Math.pow(x * x + y * y, 3 / 2);\n\t\t\t\tx = d !== 0 ? (x * y2 - y * x2) / d : 0;\n\t\t\t\ty = 0;\n\t\t\t}\n\t\t}\n\t\treturn type === 2 ? new Point(y, -x) : new Point(x, y);\n\t}\n\n\treturn { statics: {\n\n\t\tclassify: function(v) {\n\n\t\t\tvar x0 = v[0], y0 = v[1],\n\t\t\t\tx1 = v[2], y1 = v[3],\n\t\t\t\tx2 = v[4], y2 = v[5],\n\t\t\t\tx3 = v[6], y3 = v[7],\n\t\t\t\ta1 = x0 * (y3 - y2) + y0 * (x2 - x3) + x3 * y2 - y3 * x2,\n\t\t\t\ta2 = x1 * (y0 - y3) + y1 * (x3 - x0) + x0 * y3 - y0 * x3,\n\t\t\t\ta3 = x2 * (y1 - y0) + y2 * (x0 - x1) + x1 * y0 - y1 * x0,\n\t\t\t\td3 = 3 * a3,\n\t\t\t\td2 = d3 - a2,\n\t\t\t\td1 = d2 - a2 + a1,\n\t\t\t\tl = Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3),\n\t\t\t\ts = l !== 0 ? 1 / l : 0,\n\t\t\t\tisZero = Numerical.isZero,\n\t\t\t\tserpentine = 'serpentine';\n\t\t\td1 *= s;\n\t\t\td2 *= s;\n\t\t\td3 *= s;\n\n\t\t\tfunction type(type, t1, t2) {\n\t\t\t\tvar hasRoots = t1 !== undefined,\n\t\t\t\t\tt1Ok = hasRoots && t1 > 0 && t1 < 1,\n\t\t\t\t\tt2Ok = hasRoots && t2 > 0 && t2 < 1;\n\t\t\t\tif (hasRoots && (!(t1Ok || t2Ok)\n\t\t\t\t\t\t|| type === 'loop' && !(t1Ok && t2Ok))) {\n\t\t\t\t\ttype = 'arch';\n\t\t\t\t\tt1Ok = t2Ok = false;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\ttype: type,\n\t\t\t\t\troots: t1Ok || t2Ok\n\t\t\t\t\t\t\t? t1Ok && t2Ok\n\t\t\t\t\t\t\t\t? t1 < t2 ? [t1, t2] : [t2, t1]\n\t\t\t\t\t\t\t\t: [t1Ok ? t1 : t2]\n\t\t\t\t\t\t\t: null\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (isZero(d1)) {\n\t\t\t\treturn isZero(d2)\n\t\t\t\t\t\t? type(isZero(d3) ? 'line' : 'quadratic')\n\t\t\t\t\t\t: type(serpentine, d3 / (3 * d2));\n\t\t\t}\n\t\t\tvar d = 3 * d2 * d2 - 4 * d1 * d3;\n\t\t\tif (isZero(d)) {\n\t\t\t\treturn type('cusp', d2 / (2 * d1));\n\t\t\t}\n\t\t\tvar f1 = d > 0 ? Math.sqrt(d / 3) : Math.sqrt(-d),\n\t\t\t\tf2 = 2 * d1;\n\t\t\treturn type(d > 0 ? serpentine : 'loop',\n\t\t\t\t\t(d2 + f1) / f2,\n\t\t\t\t\t(d2 - f1) / f2);\n\t\t},\n\n\t\tgetLength: function(v, a, b, ds) {\n\t\t\tif (a === undefined)\n\t\t\t\ta = 0;\n\t\t\tif (b === undefined)\n\t\t\t\tb = 1;\n\t\t\tif (Curve.isStraight(v)) {\n\t\t\t\tvar c = v;\n\t\t\t\tif (b < 1) {\n\t\t\t\t\tc = Curve.subdivide(c, b)[0];\n\t\t\t\t\ta /= b;\n\t\t\t\t}\n\t\t\t\tif (a > 0) {\n\t\t\t\t\tc = Curve.subdivide(c, a)[1];\n\t\t\t\t}\n\t\t\t\tvar dx = c[6] - c[0],\n\t\t\t\t\tdy = c[7] - c[1];\n\t\t\t\treturn Math.sqrt(dx * dx + dy * dy);\n\t\t\t}\n\t\t\treturn Numerical.integrate(ds || getLengthIntegrand(v), a, b,\n\t\t\t\t\tgetIterations(a, b));\n\t\t},\n\n\t\tgetTimeAt: function(v, offset, start) {\n\t\t\tif (start === undefined)\n\t\t\t\tstart = offset < 0 ? 1 : 0;\n\t\t\tif (offset === 0)\n\t\t\t\treturn start;\n\t\t\tvar abs = Math.abs,\n\t\t\t\tepsilon = 1e-12,\n\t\t\t\tforward = offset > 0,\n\t\t\t\ta = forward ? start : 0,\n\t\t\t\tb = forward ? 1 : start,\n\t\t\t\tds = getLengthIntegrand(v),\n\t\t\t\trangeLength = Curve.getLength(v, a, b, ds),\n\t\t\t\tdiff = abs(offset) - rangeLength;\n\t\t\tif (abs(diff) < epsilon) {\n\t\t\t\treturn forward ? b : a;\n\t\t\t} else if (diff > epsilon) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tvar guess = offset / rangeLength,\n\t\t\t\tlength = 0;\n\t\t\tfunction f(t) {\n\t\t\t\tlength += Numerical.integrate(ds, start, t,\n\t\t\t\t\t\tgetIterations(start, t));\n\t\t\t\tstart = t;\n\t\t\t\treturn length - offset;\n\t\t\t}\n\t\t\treturn Numerical.findRoot(f, ds, start + guess, a, b, 32,\n\t\t\t\t\t1e-12);\n\t\t},\n\n\t\tgetPoint: function(v, t) {\n\t\t\treturn evaluate(v, t, 0, false);\n\t\t},\n\n\t\tgetTangent: function(v, t) {\n\t\t\treturn evaluate(v, t, 1, true);\n\t\t},\n\n\t\tgetWeightedTangent: function(v, t) {\n\t\t\treturn evaluate(v, t, 1, false);\n\t\t},\n\n\t\tgetNormal: function(v, t) {\n\t\t\treturn evaluate(v, t, 2, true);\n\t\t},\n\n\t\tgetWeightedNormal: function(v, t) {\n\t\t\treturn evaluate(v, t, 2, false);\n\t\t},\n\n\t\tgetCurvature: function(v, t) {\n\t\t\treturn evaluate(v, t, 3, false).x;\n\t\t},\n\n\t\tgetPeaks: function(v) {\n\t\t\tvar x0 = v[0], y0 = v[1],\n\t\t\t\tx1 = v[2], y1 = v[3],\n\t\t\t\tx2 = v[4], y2 = v[5],\n\t\t\t\tx3 = v[6], y3 = v[7],\n\t\t\t\tax = -x0 + 3 * x1 - 3 * x2 + x3,\n\t\t\t\tbx = 3 * x0 - 6 * x1 + 3 * x2,\n\t\t\t\tcx = -3 * x0 + 3 * x1,\n\t\t\t\tay = -y0 + 3 * y1 - 3 * y2 + y3,\n\t\t\t\tby = 3 * y0 - 6 * y1 + 3 * y2,\n\t\t\t\tcy = -3 * y0 + 3 * y1,\n\t\t\t\ttMin = 1e-8,\n\t\t\t\ttMax = 1 - tMin,\n\t\t\t\troots = [];\n\t\t\tNumerical.solveCubic(\n\t\t\t\t\t9 * (ax * ax + ay * ay),\n\t\t\t\t\t9 * (ax * bx + by * ay),\n\t\t\t\t\t2 * (bx * bx + by * by) + 3 * (cx * ax + cy * ay),\n\t\t\t\t\t(cx * bx + by * cy),\n\t\t\t\t\troots, tMin, tMax);\n\t\t\treturn roots.sort();\n\t\t}\n\t}};\n},\nnew function() {\n\n\tfunction addLocation(locations, include, c1, t1, c2, t2, overlap) {\n\t\tvar excludeStart = !overlap && c1.getPrevious() === c2,\n\t\t\texcludeEnd = !overlap && c1 !== c2 && c1.getNext() === c2,\n\t\t\ttMin = 1e-8,\n\t\t\ttMax = 1 - tMin;\n\t\tif (t1 !== null && t1 >= (excludeStart ? tMin : 0) &&\n\t\t\tt1 <= (excludeEnd ? tMax : 1)) {\n\t\t\tif (t2 !== null && t2 >= (excludeEnd ? tMin : 0) &&\n\t\t\t\tt2 <= (excludeStart ? tMax : 1)) {\n\t\t\t\tvar loc1 = new CurveLocation(c1, t1, null, overlap),\n\t\t\t\t\tloc2 = new CurveLocation(c2, t2, null, overlap);\n\t\t\t\tloc1._intersection = loc2;\n\t\t\t\tloc2._intersection = loc1;\n\t\t\t\tif (!include || include(loc1)) {\n\t\t\t\t\tCurveLocation.insert(locations, loc1, true);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction addCurveIntersections(v1, v2, c1, c2, locations, include, flip,\n\t\t\trecursion, calls, tMin, tMax, uMin, uMax) {\n\t\tif (++calls >= 4096 || ++recursion >= 40)\n\t\t\treturn calls;\n\t\tvar fatLineEpsilon = 1e-9,\n\t\t\tq0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7],\n\t\t\tgetSignedDistance = Line.getSignedDistance,\n\t\t\td1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]),\n\t\t\td2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]),\n\t\t\tfactor = d1 * d2 > 0 ? 3 / 4 : 4 / 9,\n\t\t\tdMin = factor * Math.min(0, d1, d2),\n\t\t\tdMax = factor * Math.max(0, d1, d2),\n\t\t\tdp0 = getSignedDistance(q0x, q0y, q3x, q3y, v1[0], v1[1]),\n\t\t\tdp1 = getSignedDistance(q0x, q0y, q3x, q3y, v1[2], v1[3]),\n\t\t\tdp2 = getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]),\n\t\t\tdp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]),\n\t\t\thull = getConvexHull(dp0, dp1, dp2, dp3),\n\t\t\ttop = hull[0],\n\t\t\tbottom = hull[1],\n\t\t\ttMinClip,\n\t\t\ttMaxClip;\n\t\tif (d1 === 0 && d2 === 0\n\t\t\t\t&& dp0 === 0 && dp1 === 0 && dp2 === 0 && dp3 === 0\n\t\t\t|| (tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null\n\t\t\t|| (tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(),\n\t\t\t\tdMin, dMax)) == null)\n\t\t\treturn calls;\n\t\tvar tMinNew = tMin + (tMax - tMin) * tMinClip,\n\t\t\ttMaxNew = tMin + (tMax - tMin) * tMaxClip;\n\t\tif (Math.max(uMax - uMin, tMaxNew - tMinNew) < fatLineEpsilon) {\n\t\t\tvar t = (tMinNew + tMaxNew) / 2,\n\t\t\t\tu = (uMin + uMax) / 2;\n\t\t\taddLocation(locations, include,\n\t\t\t\t\tflip ? c2 : c1, flip ? u : t,\n\t\t\t\t\tflip ? c1 : c2, flip ? t : u);\n\t\t} else {\n\t\t\tv1 = Curve.getPart(v1, tMinClip, tMaxClip);\n\t\t\tvar uDiff = uMax - uMin;\n\t\t\tif (tMaxClip - tMinClip > 0.8) {\n\t\t\t\tif (tMaxNew - tMinNew > uDiff) {\n\t\t\t\t\tvar parts = Curve.subdivide(v1, 0.5),\n\t\t\t\t\t\tt = (tMinNew + tMaxNew) / 2;\n\t\t\t\t\tcalls = addCurveIntersections(\n\t\t\t\t\t\t\tv2, parts[0], c2, c1, locations, include, !flip,\n\t\t\t\t\t\t\trecursion, calls, uMin, uMax, tMinNew, t);\n\t\t\t\t\tcalls = addCurveIntersections(\n\t\t\t\t\t\t\tv2, parts[1], c2, c1, locations, include, !flip,\n\t\t\t\t\t\t\trecursion, calls, uMin, uMax, t, tMaxNew);\n\t\t\t\t} else {\n\t\t\t\t\tvar parts = Curve.subdivide(v2, 0.5),\n\t\t\t\t\t\tu = (uMin + uMax) / 2;\n\t\t\t\t\tcalls = addCurveIntersections(\n\t\t\t\t\t\t\tparts[0], v1, c2, c1, locations, include, !flip,\n\t\t\t\t\t\t\trecursion, calls, uMin, u, tMinNew, tMaxNew);\n\t\t\t\t\tcalls = addCurveIntersections(\n\t\t\t\t\t\t\tparts[1], v1, c2, c1, locations, include, !flip,\n\t\t\t\t\t\t\trecursion, calls, u, uMax, tMinNew, tMaxNew);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (uDiff === 0 || uDiff >= fatLineEpsilon) {\n\t\t\t\t\tcalls = addCurveIntersections(\n\t\t\t\t\t\t\tv2, v1, c2, c1, locations, include, !flip,\n\t\t\t\t\t\t\trecursion, calls, uMin, uMax, tMinNew, tMaxNew);\n\t\t\t\t} else {\n\t\t\t\t\tcalls = addCurveIntersections(\n\t\t\t\t\t\t\tv1, v2, c1, c2, locations, include, flip,\n\t\t\t\t\t\t\trecursion, calls, tMinNew, tMaxNew, uMin, uMax);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn calls;\n\t}\n\n\tfunction getConvexHull(dq0, dq1, dq2, dq3) {\n\t\tvar p0 = [ 0, dq0 ],\n\t\t\tp1 = [ 1 / 3, dq1 ],\n\t\t\tp2 = [ 2 / 3, dq2 ],\n\t\t\tp3 = [ 1, dq3 ],\n\t\t\tdist1 = dq1 - (2 * dq0 + dq3) / 3,\n\t\t\tdist2 = dq2 - (dq0 + 2 * dq3) / 3,\n\t\t\thull;\n\t\tif (dist1 * dist2 < 0) {\n\t\t\thull = [[p0, p1, p3], [p0, p2, p3]];\n\t\t} else {\n\t\t\tvar distRatio = dist1 / dist2;\n\t\t\thull = [\n\t\t\t\tdistRatio >= 2 ? [p0, p1, p3]\n\t\t\t\t: distRatio <= 0.5 ? [p0, p2, p3]\n\t\t\t\t: [p0, p1, p2, p3],\n\t\t\t\t[p0, p3]\n\t\t\t];\n\t\t}\n\t\treturn (dist1 || dist2) < 0 ? hull.reverse() : hull;\n\t}\n\n\tfunction clipConvexHull(hullTop, hullBottom, dMin, dMax) {\n\t\tif (hullTop[0][1] < dMin) {\n\t\t\treturn clipConvexHullPart(hullTop, true, dMin);\n\t\t} else if (hullBottom[0][1] > dMax) {\n\t\t\treturn clipConvexHullPart(hullBottom, false, dMax);\n\t\t} else {\n\t\t\treturn hullTop[0][0];\n\t\t}\n\t}\n\n\tfunction clipConvexHullPart(part, top, threshold) {\n\t\tvar px = part[0][0],\n\t\t\tpy = part[0][1];\n\t\tfor (var i = 1, l = part.length; i < l; i++) {\n\t\t\tvar qx = part[i][0],\n\t\t\t\tqy = part[i][1];\n\t\t\tif (top ? qy >= threshold : qy <= threshold) {\n\t\t\t\treturn qy === threshold ? qx\n\t\t\t\t\t\t: px + (threshold - py) * (qx - px) / (qy - py);\n\t\t\t}\n\t\t\tpx = qx;\n\t\t\tpy = qy;\n\t\t}\n\t\treturn null;\n\t}\n\n\tfunction getCurveLineIntersections(v, px, py, vx, vy) {\n\t\tvar isZero = Numerical.isZero;\n\t\tif (isZero(vx) && isZero(vy)) {\n\t\t\tvar t = Curve.getTimeOf(v, new Point(px, py));\n\t\t\treturn t === null ? [] : [t];\n\t\t}\n\t\tvar angle = Math.atan2(-vy, vx),\n\t\t\tsin = Math.sin(angle),\n\t\t\tcos = Math.cos(angle),\n\t\t\trv = [],\n\t\t\troots = [];\n\t\tfor (var i = 0; i < 8; i += 2) {\n\t\t\tvar x = v[i] - px,\n\t\t\t\ty = v[i + 1] - py;\n\t\t\trv.push(\n\t\t\t\tx * cos - y * sin,\n\t\t\t\tx * sin + y * cos);\n\t\t}\n\t\tCurve.solveCubic(rv, 1, 0, roots, 0, 1);\n\t\treturn roots;\n\t}\n\n\tfunction addCurveLineIntersections(v1, v2, c1, c2, locations, include,\n\t\t\tflip) {\n\t\tvar x1 = v2[0], y1 = v2[1],\n\t\t\tx2 = v2[6], y2 = v2[7],\n\t\t\troots = getCurveLineIntersections(v1, x1, y1, x2 - x1, y2 - y1);\n\t\tfor (var i = 0, l = roots.length; i < l; i++) {\n\t\t\tvar t1 = roots[i],\n\t\t\t\tp1 = Curve.getPoint(v1, t1),\n\t\t\t\tt2 = Curve.getTimeOf(v2, p1);\n\t\t\tif (t2 !== null) {\n\t\t\t\taddLocation(locations, include,\n\t\t\t\t\t\tflip ? c2 : c1, flip ? t2 : t1,\n\t\t\t\t\t\tflip ? c1 : c2, flip ? t1 : t2);\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction addLineIntersection(v1, v2, c1, c2, locations, include) {\n\t\tvar pt = Line.intersect(\n\t\t\t\tv1[0], v1[1], v1[6], v1[7],\n\t\t\t\tv2[0], v2[1], v2[6], v2[7]);\n\t\tif (pt) {\n\t\t\taddLocation(locations, include,\n\t\t\t\t\tc1, Curve.getTimeOf(v1, pt),\n\t\t\t\t\tc2, Curve.getTimeOf(v2, pt));\n\t\t}\n\t}\n\n\tfunction getCurveIntersections(v1, v2, c1, c2, locations, include) {\n\t\tvar epsilon = 1e-12,\n\t\t\tmin = Math.min,\n\t\t\tmax = Math.max;\n\n\t\tif (max(v1[0], v1[2], v1[4], v1[6]) + epsilon >\n\t\t\tmin(v2[0], v2[2], v2[4], v2[6]) &&\n\t\t\tmin(v1[0], v1[2], v1[4], v1[6]) - epsilon <\n\t\t\tmax(v2[0], v2[2], v2[4], v2[6]) &&\n\t\t\tmax(v1[1], v1[3], v1[5], v1[7]) + epsilon >\n\t\t\tmin(v2[1], v2[3], v2[5], v2[7]) &&\n\t\t\tmin(v1[1], v1[3], v1[5], v1[7]) - epsilon <\n\t\t\tmax(v2[1], v2[3], v2[5], v2[7])) {\n\t\t\tvar overlaps = getOverlaps(v1, v2);\n\t\t\tif (overlaps) {\n\t\t\t\tfor (var i = 0; i < 2; i++) {\n\t\t\t\t\tvar overlap = overlaps[i];\n\t\t\t\t\taddLocation(locations, include,\n\t\t\t\t\t\t\tc1, overlap[0],\n\t\t\t\t\t\t\tc2, overlap[1], true);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar straight1 = Curve.isStraight(v1),\n\t\t\t\t\tstraight2 = Curve.isStraight(v2),\n\t\t\t\t\tstraight = straight1 && straight2,\n\t\t\t\t\tflip = straight1 && !straight2,\n\t\t\t\t\tbefore = locations.length;\n\t\t\t\t(straight\n\t\t\t\t\t? addLineIntersection\n\t\t\t\t\t: straight1 || straight2\n\t\t\t\t\t\t? addCurveLineIntersections\n\t\t\t\t\t\t: addCurveIntersections)(\n\t\t\t\t\t\t\tflip ? v2 : v1, flip ? v1 : v2,\n\t\t\t\t\t\t\tflip ? c2 : c1, flip ? c1 : c2,\n\t\t\t\t\t\t\tlocations, include, flip,\n\t\t\t\t\t\t\t0, 0, 0, 1, 0, 1);\n\t\t\t\tif (!straight || locations.length === before) {\n\t\t\t\t\tfor (var i = 0; i < 4; i++) {\n\t\t\t\t\t\tvar t1 = i >> 1,\n\t\t\t\t\t\t\tt2 = i & 1,\n\t\t\t\t\t\t\ti1 = t1 * 6,\n\t\t\t\t\t\t\ti2 = t2 * 6,\n\t\t\t\t\t\t\tp1 = new Point(v1[i1], v1[i1 + 1]),\n\t\t\t\t\t\t\tp2 = new Point(v2[i2], v2[i2 + 1]);\n\t\t\t\t\t\tif (p1.isClose(p2, epsilon)) {\n\t\t\t\t\t\t\taddLocation(locations, include,\n\t\t\t\t\t\t\t\t\tc1, t1,\n\t\t\t\t\t\t\t\t\tc2, t2);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn locations;\n\t}\n\n\tfunction getSelfIntersection(v1, c1, locations, include) {\n\t\tvar info = Curve.classify(v1);\n\t\tif (info.type === 'loop') {\n\t\t\tvar roots = info.roots;\n\t\t\taddLocation(locations, include,\n\t\t\t\t\tc1, roots[0],\n\t\t\t\t\tc1, roots[1]);\n\t\t}\n\t return locations;\n\t}\n\n\tfunction getIntersections(curves1, curves2, include, matrix1, matrix2,\n\t\t\t_returnFirst) {\n\t\tvar epsilon = 1e-7,\n\t\t\tself = !curves2;\n\t\tif (self)\n\t\t\tcurves2 = curves1;\n\t\tvar length1 = curves1.length,\n\t\t\tlength2 = curves2.length,\n\t\t\tvalues1 = new Array(length1),\n\t\t\tvalues2 = self ? values1 : new Array(length2),\n\t\t\tlocations = [];\n\n\t\tfor (var i = 0; i < length1; i++) {\n\t\t\tvalues1[i] = curves1[i].getValues(matrix1);\n\t\t}\n\t\tif (!self) {\n\t\t\tfor (var i = 0; i < length2; i++) {\n\t\t\t\tvalues2[i] = curves2[i].getValues(matrix2);\n\t\t\t}\n\t\t}\n\t\tvar boundsCollisions = CollisionDetection.findCurveBoundsCollisions(\n\t\t\t\tvalues1, values2, epsilon);\n\t\tfor (var index1 = 0; index1 < length1; index1++) {\n\t\t\tvar curve1 = curves1[index1],\n\t\t\t\tv1 = values1[index1];\n\t\t\tif (self) {\n\t\t\t\tgetSelfIntersection(v1, curve1, locations, include);\n\t\t\t}\n\t\t\tvar collisions1 = boundsCollisions[index1];\n\t\t\tif (collisions1) {\n\t\t\t\tfor (var j = 0; j < collisions1.length; j++) {\n\t\t\t\t\tif (_returnFirst && locations.length)\n\t\t\t\t\t\treturn locations;\n\t\t\t\t\tvar index2 = collisions1[j];\n\t\t\t\t\tif (!self || index2 > index1) {\n\t\t\t\t\t\tvar curve2 = curves2[index2],\n\t\t\t\t\t\t\tv2 = values2[index2];\n\t\t\t\t\t\tgetCurveIntersections(\n\t\t\t\t\t\t\t\tv1, v2, curve1, curve2, locations, include);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn locations;\n\t}\n\n\tfunction getOverlaps(v1, v2) {\n\n\t\tfunction getSquaredLineLength(v) {\n\t\t\tvar x = v[6] - v[0],\n\t\t\t\ty = v[7] - v[1];\n\t\t\treturn x * x + y * y;\n\t\t}\n\n\t\tvar abs = Math.abs,\n\t\t\tgetDistance = Line.getDistance,\n\t\t\ttimeEpsilon = 1e-8,\n\t\t\tgeomEpsilon = 1e-7,\n\t\t\tstraight1 = Curve.isStraight(v1),\n\t\t\tstraight2 = Curve.isStraight(v2),\n\t\t\tstraightBoth = straight1 && straight2,\n\t\t\tflip = getSquaredLineLength(v1) < getSquaredLineLength(v2),\n\t\t\tl1 = flip ? v2 : v1,\n\t\t\tl2 = flip ? v1 : v2,\n\t\t\tpx = l1[0], py = l1[1],\n\t\t\tvx = l1[6] - px, vy = l1[7] - py;\n\t\tif (getDistance(px, py, vx, vy, l2[0], l2[1], true) < geomEpsilon &&\n\t\t\tgetDistance(px, py, vx, vy, l2[6], l2[7], true) < geomEpsilon) {\n\t\t\tif (!straightBoth &&\n\t\t\t\tgetDistance(px, py, vx, vy, l1[2], l1[3], true) < geomEpsilon &&\n\t\t\t\tgetDistance(px, py, vx, vy, l1[4], l1[5], true) < geomEpsilon &&\n\t\t\t\tgetDistance(px, py, vx, vy, l2[2], l2[3], true) < geomEpsilon &&\n\t\t\t\tgetDistance(px, py, vx, vy, l2[4], l2[5], true) < geomEpsilon) {\n\t\t\t\tstraight1 = straight2 = straightBoth = true;\n\t\t\t}\n\t\t} else if (straightBoth) {\n\t\t\treturn null;\n\t\t}\n\t\tif (straight1 ^ straight2) {\n\t\t\treturn null;\n\t\t}\n\n\t\tvar v = [v1, v2],\n\t\t\tpairs = [];\n\t\tfor (var i = 0; i < 4 && pairs.length < 2; i++) {\n\t\t\tvar i1 = i & 1,\n\t\t\t\ti2 = i1 ^ 1,\n\t\t\t\tt1 = i >> 1,\n\t\t\t\tt2 = Curve.getTimeOf(v[i1], new Point(\n\t\t\t\t\tv[i2][t1 ? 6 : 0],\n\t\t\t\t\tv[i2][t1 ? 7 : 1]));\n\t\t\tif (t2 != null) {\n\t\t\t\tvar pair = i1 ? [t1, t2] : [t2, t1];\n\t\t\t\tif (!pairs.length ||\n\t\t\t\t\tabs(pair[0] - pairs[0][0]) > timeEpsilon &&\n\t\t\t\t\tabs(pair[1] - pairs[0][1]) > timeEpsilon) {\n\t\t\t\t\tpairs.push(pair);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (i > 2 && !pairs.length)\n\t\t\t\tbreak;\n\t\t}\n\t\tif (pairs.length !== 2) {\n\t\t\tpairs = null;\n\t\t} else if (!straightBoth) {\n\t\t\tvar o1 = Curve.getPart(v1, pairs[0][0], pairs[1][0]),\n\t\t\t\to2 = Curve.getPart(v2, pairs[0][1], pairs[1][1]);\n\t\t\tif (abs(o2[2] - o1[2]) > geomEpsilon ||\n\t\t\t\tabs(o2[3] - o1[3]) > geomEpsilon ||\n\t\t\t\tabs(o2[4] - o1[4]) > geomEpsilon ||\n\t\t\t\tabs(o2[5] - o1[5]) > geomEpsilon)\n\t\t\t\tpairs = null;\n\t\t}\n\t\treturn pairs;\n\t}\n\n\tfunction getTimesWithTangent(v, tangent) {\n\t\tvar x0 = v[0], y0 = v[1],\n\t\t\tx1 = v[2], y1 = v[3],\n\t\t\tx2 = v[4], y2 = v[5],\n\t\t\tx3 = v[6], y3 = v[7],\n\t\t\tnormalized = tangent.normalize(),\n\t\t\ttx = normalized.x,\n\t\t\tty = normalized.y,\n\t\t\tax = 3 * x3 - 9 * x2 + 9 * x1 - 3 * x0,\n\t\t\tay = 3 * y3 - 9 * y2 + 9 * y1 - 3 * y0,\n\t\t\tbx = 6 * x2 - 12 * x1 + 6 * x0,\n\t\t\tby = 6 * y2 - 12 * y1 + 6 * y0,\n\t\t\tcx = 3 * x1 - 3 * x0,\n\t\t\tcy = 3 * y1 - 3 * y0,\n\t\t\tden = 2 * ax * ty - 2 * ay * tx,\n\t\t\ttimes = [];\n\t\tif (Math.abs(den) < Numerical.CURVETIME_EPSILON) {\n\t\t\tvar num = ax * cy - ay * cx,\n\t\t\t\tden = ax * by - ay * bx;\n\t\t\tif (den != 0) {\n\t\t\t\tvar t = -num / den;\n\t\t\t\tif (t >= 0 && t <= 1) times.push(t);\n\t\t\t}\n\t\t} else {\n\t\t\tvar delta = (bx * bx - 4 * ax * cx) * ty * ty +\n\t\t\t\t(-2 * bx * by + 4 * ay * cx + 4 * ax * cy) * tx * ty +\n\t\t\t\t(by * by - 4 * ay * cy) * tx * tx,\n\t\t\t\tk = bx * ty - by * tx;\n\t\t\tif (delta >= 0 && den != 0) {\n\t\t\t\tvar d = Math.sqrt(delta),\n\t\t\t\t\tt0 = -(k + d) / den,\n\t\t\t\t\tt1 = (-k + d) / den;\n\t\t\t\tif (t0 >= 0 && t0 <= 1) times.push(t0);\n\t\t\t\tif (t1 >= 0 && t1 <= 1) times.push(t1);\n\t\t\t}\n\t\t}\n\t\treturn times;\n\t}\n\n\treturn {\n\t\tgetIntersections: function(curve) {\n\t\t\tvar v1 = this.getValues(),\n\t\t\t\tv2 = curve && curve !== this && curve.getValues();\n\t\t\treturn v2 ? getCurveIntersections(v1, v2, this, curve, [])\n\t\t\t\t\t : getSelfIntersection(v1, this, []);\n\t\t},\n\n\t\tstatics: {\n\t\t\tgetOverlaps: getOverlaps,\n\t\t\tgetIntersections: getIntersections,\n\t\t\tgetCurveLineIntersections: getCurveLineIntersections,\n\t\t\tgetTimesWithTangent: getTimesWithTangent\n\t\t}\n\t};\n});\n\nvar CurveLocation = Base.extend({\n\t_class: 'CurveLocation',\n\n\tinitialize: function CurveLocation(curve, time, point, _overlap, _distance) {\n\t\tif (time >= 0.99999999) {\n\t\t\tvar next = curve.getNext();\n\t\t\tif (next) {\n\t\t\t\ttime = 0;\n\t\t\t\tcurve = next;\n\t\t\t}\n\t\t}\n\t\tthis._setCurve(curve);\n\t\tthis._time = time;\n\t\tthis._point = point || curve.getPointAtTime(time);\n\t\tthis._overlap = _overlap;\n\t\tthis._distance = _distance;\n\t\tthis._intersection = this._next = this._previous = null;\n\t},\n\n\t_setPath: function(path) {\n\t\tthis._path = path;\n\t\tthis._version = path ? path._version : 0;\n\t},\n\n\t_setCurve: function(curve) {\n\t\tthis._setPath(curve._path);\n\t\tthis._curve = curve;\n\t\tthis._segment = null;\n\t\tthis._segment1 = curve._segment1;\n\t\tthis._segment2 = curve._segment2;\n\t},\n\n\t_setSegment: function(segment) {\n\t\tvar curve = segment.getCurve();\n\t\tif (curve) {\n\t\t\tthis._setCurve(curve);\n\t\t} else {\n\t\t\tthis._setPath(segment._path);\n\t\t\tthis._segment1 = segment;\n\t\t\tthis._segment2 = null;\n\t\t}\n\t\tthis._segment = segment;\n\t\tthis._time = segment === this._segment1 ? 0 : 1;\n\t\tthis._point = segment._point.clone();\n\t},\n\n\tgetSegment: function() {\n\t\tvar segment = this._segment;\n\t\tif (!segment) {\n\t\t\tvar curve = this.getCurve(),\n\t\t\t\ttime = this.getTime();\n\t\t\tif (time === 0) {\n\t\t\t\tsegment = curve._segment1;\n\t\t\t} else if (time === 1) {\n\t\t\t\tsegment = curve._segment2;\n\t\t\t} else if (time != null) {\n\t\t\t\tsegment = curve.getPartLength(0, time)\n\t\t\t\t\t< curve.getPartLength(time, 1)\n\t\t\t\t\t\t? curve._segment1\n\t\t\t\t\t\t: curve._segment2;\n\t\t\t}\n\t\t\tthis._segment = segment;\n\t\t}\n\t\treturn segment;\n\t},\n\n\tgetCurve: function() {\n\t\tvar path = this._path,\n\t\t\tthat = this;\n\t\tif (path && path._version !== this._version) {\n\t\t\tthis._time = this._offset = this._curveOffset = this._curve = null;\n\t\t}\n\n\t\tfunction trySegment(segment) {\n\t\t\tvar curve = segment && segment.getCurve();\n\t\t\tif (curve && (that._time = curve.getTimeOf(that._point)) != null) {\n\t\t\t\tthat._setCurve(curve);\n\t\t\t\treturn curve;\n\t\t\t}\n\t\t}\n\n\t\treturn this._curve\n\t\t\t|| trySegment(this._segment)\n\t\t\t|| trySegment(this._segment1)\n\t\t\t|| trySegment(this._segment2.getPrevious());\n\t},\n\n\tgetPath: function() {\n\t\tvar curve = this.getCurve();\n\t\treturn curve && curve._path;\n\t},\n\n\tgetIndex: function() {\n\t\tvar curve = this.getCurve();\n\t\treturn curve && curve.getIndex();\n\t},\n\n\tgetTime: function() {\n\t\tvar curve = this.getCurve(),\n\t\t\ttime = this._time;\n\t\treturn curve && time == null\n\t\t\t? this._time = curve.getTimeOf(this._point)\n\t\t\t: time;\n\t},\n\n\tgetParameter: '#getTime',\n\n\tgetPoint: function() {\n\t\treturn this._point;\n\t},\n\n\tgetOffset: function() {\n\t\tvar offset = this._offset;\n\t\tif (offset == null) {\n\t\t\toffset = 0;\n\t\t\tvar path = this.getPath(),\n\t\t\t\tindex = this.getIndex();\n\t\t\tif (path && index != null) {\n\t\t\t\tvar curves = path.getCurves();\n\t\t\t\tfor (var i = 0; i < index; i++)\n\t\t\t\t\toffset += curves[i].getLength();\n\t\t\t}\n\t\t\tthis._offset = offset += this.getCurveOffset();\n\t\t}\n\t\treturn offset;\n\t},\n\n\tgetCurveOffset: function() {\n\t\tvar offset = this._curveOffset;\n\t\tif (offset == null) {\n\t\t\tvar curve = this.getCurve(),\n\t\t\t\ttime = this.getTime();\n\t\t\tthis._curveOffset = offset = time != null && curve\n\t\t\t\t\t&& curve.getPartLength(0, time);\n\t\t}\n\t\treturn offset;\n\t},\n\n\tgetIntersection: function() {\n\t\treturn this._intersection;\n\t},\n\n\tgetDistance: function() {\n\t\treturn this._distance;\n\t},\n\n\tdivide: function() {\n\t\tvar curve = this.getCurve(),\n\t\t\tres = curve && curve.divideAtTime(this.getTime());\n\t\tif (res) {\n\t\t\tthis._setSegment(res._segment1);\n\t\t}\n\t\treturn res;\n\t},\n\n\tsplit: function() {\n\t\tvar curve = this.getCurve(),\n\t\t\tpath = curve._path,\n\t\t\tres = curve && curve.splitAtTime(this.getTime());\n\t\tif (res) {\n\t\t\tthis._setSegment(path.getLastSegment());\n\t\t}\n\t\treturn res;\n\t},\n\n\tequals: function(loc, _ignoreOther) {\n\t\tvar res = this === loc;\n\t\tif (!res && loc instanceof CurveLocation) {\n\t\t\tvar c1 = this.getCurve(),\n\t\t\t\tc2 = loc.getCurve(),\n\t\t\t\tp1 = c1._path,\n\t\t\t\tp2 = c2._path;\n\t\t\tif (p1 === p2) {\n\t\t\t\tvar abs = Math.abs,\n\t\t\t\t\tepsilon = 1e-7,\n\t\t\t\t\tdiff = abs(this.getOffset() - loc.getOffset()),\n\t\t\t\t\ti1 = !_ignoreOther && this._intersection,\n\t\t\t\t\ti2 = !_ignoreOther && loc._intersection;\n\t\t\t\tres = (diff < epsilon\n\t\t\t\t\t\t|| p1 && abs(p1.getLength() - diff) < epsilon)\n\t\t\t\t\t&& (!i1 && !i2 || i1 && i2 && i1.equals(i2, true));\n\t\t\t}\n\t\t}\n\t\treturn res;\n\t},\n\n\ttoString: function() {\n\t\tvar parts = [],\n\t\t\tpoint = this.getPoint(),\n\t\t\tf = Formatter.instance;\n\t\tif (point)\n\t\t\tparts.push('point: ' + point);\n\t\tvar index = this.getIndex();\n\t\tif (index != null)\n\t\t\tparts.push('index: ' + index);\n\t\tvar time = this.getTime();\n\t\tif (time != null)\n\t\t\tparts.push('time: ' + f.number(time));\n\t\tif (this._distance != null)\n\t\t\tparts.push('distance: ' + f.number(this._distance));\n\t\treturn '{ ' + parts.join(', ') + ' }';\n\t},\n\n\tisTouching: function() {\n\t\tvar inter = this._intersection;\n\t\tif (inter && this.getTangent().isCollinear(inter.getTangent())) {\n\t\t\tvar curve1 = this.getCurve(),\n\t\t\t\tcurve2 = inter.getCurve();\n\t\t\treturn !(curve1.isStraight() && curve2.isStraight()\n\t\t\t\t\t&& curve1.getLine().intersect(curve2.getLine()));\n\t\t}\n\t\treturn false;\n\t},\n\n\tisCrossing: function() {\n\t\tvar inter = this._intersection;\n\t\tif (!inter)\n\t\t\treturn false;\n\t\tvar t1 = this.getTime(),\n\t\t\tt2 = inter.getTime(),\n\t\t\ttMin = 1e-8,\n\t\t\ttMax = 1 - tMin,\n\t\t\tt1Inside = t1 >= tMin && t1 <= tMax,\n\t\t\tt2Inside = t2 >= tMin && t2 <= tMax;\n\t\tif (t1Inside && t2Inside)\n\t\t\treturn !this.isTouching();\n\t\tvar c2 = this.getCurve(),\n\t\t\tc1 = c2 && t1 < tMin ? c2.getPrevious() : c2,\n\t\t\tc4 = inter.getCurve(),\n\t\t\tc3 = c4 && t2 < tMin ? c4.getPrevious() : c4;\n\t\tif (t1 > tMax)\n\t\t\tc2 = c2.getNext();\n\t\tif (t2 > tMax)\n\t\t\tc4 = c4.getNext();\n\t\tif (!c1 || !c2 || !c3 || !c4)\n\t\t\treturn false;\n\n\t\tvar offsets = [];\n\n\t\tfunction addOffsets(curve, end) {\n\t\t\tvar v = curve.getValues(),\n\t\t\t\troots = Curve.classify(v).roots || Curve.getPeaks(v),\n\t\t\t\tcount = roots.length,\n\t\t\t\toffset = Curve.getLength(v,\n\t\t\t\t\tend && count ? roots[count - 1] : 0,\n\t\t\t\t\t!end && count ? roots[0] : 1);\n\t\t\toffsets.push(count ? offset : offset / 32);\n\t\t}\n\n\t\tfunction isInRange(angle, min, max) {\n\t\t\treturn min < max\n\t\t\t\t\t? angle > min && angle < max\n\t\t\t\t\t: angle > min || angle < max;\n\t\t}\n\n\t\tif (!t1Inside) {\n\t\t\taddOffsets(c1, true);\n\t\t\taddOffsets(c2, false);\n\t\t}\n\t\tif (!t2Inside) {\n\t\t\taddOffsets(c3, true);\n\t\t\taddOffsets(c4, false);\n\t\t}\n\t\tvar pt = this.getPoint(),\n\t\t\toffset = Math.min.apply(Math, offsets),\n\t\t\tv2 = t1Inside ? c2.getTangentAtTime(t1)\n\t\t\t\t\t: c2.getPointAt(offset).subtract(pt),\n\t\t\tv1 = t1Inside ? v2.negate()\n\t\t\t\t\t: c1.getPointAt(-offset).subtract(pt),\n\t\t\tv4 = t2Inside ? c4.getTangentAtTime(t2)\n\t\t\t\t\t: c4.getPointAt(offset).subtract(pt),\n\t\t\tv3 = t2Inside ? v4.negate()\n\t\t\t\t\t: c3.getPointAt(-offset).subtract(pt),\n\t\t\ta1 = v1.getAngle(),\n\t\t\ta2 = v2.getAngle(),\n\t\t\ta3 = v3.getAngle(),\n\t\t\ta4 = v4.getAngle();\n\t\treturn !!(t1Inside\n\t\t\t\t? (isInRange(a1, a3, a4) ^ isInRange(a2, a3, a4)) &&\n\t\t\t\t (isInRange(a1, a4, a3) ^ isInRange(a2, a4, a3))\n\t\t\t\t: (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2)) &&\n\t\t\t\t (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1)));\n\t},\n\n\thasOverlap: function() {\n\t\treturn !!this._overlap;\n\t}\n}, Base.each(Curve._evaluateMethods, function(name) {\n\tvar get = name + 'At';\n\tthis[name] = function() {\n\t\tvar curve = this.getCurve(),\n\t\t\ttime = this.getTime();\n\t\treturn time != null && curve && curve[get](time, true);\n\t};\n}, {\n\tpreserve: true\n}),\nnew function() {\n\n\tfunction insert(locations, loc, merge) {\n\t\tvar length = locations.length,\n\t\t\tl = 0,\n\t\t\tr = length - 1;\n\n\t\tfunction search(index, dir) {\n\t\t\tfor (var i = index + dir; i >= -1 && i <= length; i += dir) {\n\t\t\t\tvar loc2 = locations[((i % length) + length) % length];\n\t\t\t\tif (!loc.getPoint().isClose(loc2.getPoint(),\n\t\t\t\t\t\t1e-7))\n\t\t\t\t\tbreak;\n\t\t\t\tif (loc.equals(loc2))\n\t\t\t\t\treturn loc2;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\twhile (l <= r) {\n\t\t\tvar m = (l + r) >>> 1,\n\t\t\t\tloc2 = locations[m],\n\t\t\t\tfound;\n\t\t\tif (merge && (found = loc.equals(loc2) ? loc2\n\t\t\t\t\t: (search(m, -1) || search(m, 1)))) {\n\t\t\t\tif (loc._overlap) {\n\t\t\t\t\tfound._overlap = found._intersection._overlap = true;\n\t\t\t\t}\n\t\t\t\treturn found;\n\t\t\t}\n\t\tvar path1 = loc.getPath(),\n\t\t\tpath2 = loc2.getPath(),\n\t\t\tdiff = path1 !== path2\n\t\t\t\t? path1._id - path2._id\n\t\t\t\t: (loc.getIndex() + loc.getTime())\n\t\t\t\t- (loc2.getIndex() + loc2.getTime());\n\t\t\tif (diff < 0) {\n\t\t\t\tr = m - 1;\n\t\t\t} else {\n\t\t\t\tl = m + 1;\n\t\t\t}\n\t\t}\n\t\tlocations.splice(l, 0, loc);\n\t\treturn loc;\n\t}\n\n\treturn { statics: {\n\t\tinsert: insert,\n\n\t\texpand: function(locations) {\n\t\t\tvar expanded = locations.slice();\n\t\t\tfor (var i = locations.length - 1; i >= 0; i--) {\n\t\t\t\tinsert(expanded, locations[i]._intersection, false);\n\t\t\t}\n\t\t\treturn expanded;\n\t\t}\n\t}};\n});\n\nvar PathItem = Item.extend({\n\t_class: 'PathItem',\n\t_selectBounds: false,\n\t_canScaleStroke: true,\n\tbeans: true,\n\n\tinitialize: function PathItem() {\n\t},\n\n\tstatics: {\n\t\tcreate: function(arg) {\n\t\t\tvar data,\n\t\t\t\tsegments,\n\t\t\t\tcompound;\n\t\t\tif (Base.isPlainObject(arg)) {\n\t\t\t\tsegments = arg.segments;\n\t\t\t\tdata = arg.pathData;\n\t\t\t} else if (Array.isArray(arg)) {\n\t\t\t\tsegments = arg;\n\t\t\t} else if (typeof arg === 'string') {\n\t\t\t\tdata = arg;\n\t\t\t}\n\t\t\tif (segments) {\n\t\t\t\tvar first = segments[0];\n\t\t\t\tcompound = first && Array.isArray(first[0]);\n\t\t\t} else if (data) {\n\t\t\t\tcompound = (data.match(/m/gi) || []).length > 1\n\t\t\t\t\t\t|| /z\\s*\\S+/i.test(data);\n\t\t\t}\n\t\t\tvar ctor = compound ? CompoundPath : Path;\n\t\t\treturn new ctor(arg);\n\t\t}\n\t},\n\n\t_asPathItem: function() {\n\t\treturn this;\n\t},\n\n\tisClockwise: function() {\n\t\treturn this.getArea() >= 0;\n\t},\n\n\tsetClockwise: function(clockwise) {\n\t\tif (this.isClockwise() != (clockwise = !!clockwise))\n\t\t\tthis.reverse();\n\t},\n\n\tsetPathData: function(data) {\n\n\t\tvar parts = data && data.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig),\n\t\t\tcoords,\n\t\t\trelative = false,\n\t\t\tprevious,\n\t\t\tcontrol,\n\t\t\tcurrent = new Point(),\n\t\t\tstart = new Point();\n\n\t\tfunction getCoord(index, coord) {\n\t\t\tvar val = +coords[index];\n\t\t\tif (relative)\n\t\t\t\tval += current[coord];\n\t\t\treturn val;\n\t\t}\n\n\t\tfunction getPoint(index) {\n\t\t\treturn new Point(\n\t\t\t\tgetCoord(index, 'x'),\n\t\t\t\tgetCoord(index + 1, 'y')\n\t\t\t);\n\t\t}\n\n\t\tthis.clear();\n\n\t\tfor (var i = 0, l = parts && parts.length; i < l; i++) {\n\t\t\tvar part = parts[i],\n\t\t\t\tcommand = part[0],\n\t\t\t\tlower = command.toLowerCase();\n\t\t\tcoords = part.match(/[+-]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][+-]?\\d+)?/g);\n\t\t\tvar length = coords && coords.length;\n\t\t\trelative = command === lower;\n\t\t\tif (previous === 'z' && !/[mz]/.test(lower))\n\t\t\t\tthis.moveTo(current);\n\t\t\tswitch (lower) {\n\t\t\tcase 'm':\n\t\t\tcase 'l':\n\t\t\t\tvar move = lower === 'm';\n\t\t\t\tfor (var j = 0; j < length; j += 2) {\n\t\t\t\t\tthis[move ? 'moveTo' : 'lineTo'](current = getPoint(j));\n\t\t\t\t\tif (move) {\n\t\t\t\t\t\tstart = current;\n\t\t\t\t\t\tmove = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontrol = current;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\tcase 'v':\n\t\t\t\tvar coord = lower === 'h' ? 'x' : 'y';\n\t\t\t\tcurrent = current.clone();\n\t\t\t\tfor (var j = 0; j < length; j++) {\n\t\t\t\t\tcurrent[coord] = getCoord(j, coord);\n\t\t\t\t\tthis.lineTo(current);\n\t\t\t\t}\n\t\t\t\tcontrol = current;\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\tfor (var j = 0; j < length; j += 6) {\n\t\t\t\t\tthis.cubicCurveTo(\n\t\t\t\t\t\t\tgetPoint(j),\n\t\t\t\t\t\t\tcontrol = getPoint(j + 2),\n\t\t\t\t\t\t\tcurrent = getPoint(j + 4));\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tfor (var j = 0; j < length; j += 4) {\n\t\t\t\t\tthis.cubicCurveTo(\n\t\t\t\t\t\t\t/[cs]/.test(previous)\n\t\t\t\t\t\t\t\t\t? current.multiply(2).subtract(control)\n\t\t\t\t\t\t\t\t\t: current,\n\t\t\t\t\t\t\tcontrol = getPoint(j),\n\t\t\t\t\t\t\tcurrent = getPoint(j + 2));\n\t\t\t\t\tprevious = lower;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'q':\n\t\t\t\tfor (var j = 0; j < length; j += 4) {\n\t\t\t\t\tthis.quadraticCurveTo(\n\t\t\t\t\t\t\tcontrol = getPoint(j),\n\t\t\t\t\t\t\tcurrent = getPoint(j + 2));\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 't':\n\t\t\t\tfor (var j = 0; j < length; j += 2) {\n\t\t\t\t\tthis.quadraticCurveTo(\n\t\t\t\t\t\t\tcontrol = (/[qt]/.test(previous)\n\t\t\t\t\t\t\t\t\t? current.multiply(2).subtract(control)\n\t\t\t\t\t\t\t\t\t: current),\n\t\t\t\t\t\t\tcurrent = getPoint(j));\n\t\t\t\t\tprevious = lower;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'a':\n\t\t\t\tfor (var j = 0; j < length; j += 7) {\n\t\t\t\t\tthis.arcTo(current = getPoint(j + 5),\n\t\t\t\t\t\t\tnew Size(+coords[j], +coords[j + 1]),\n\t\t\t\t\t\t\t+coords[j + 2], +coords[j + 4], +coords[j + 3]);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'z':\n\t\t\t\tthis.closePath(1e-12);\n\t\t\t\tcurrent = start;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tprevious = lower;\n\t\t}\n\t},\n\n\t_canComposite: function() {\n\t\treturn !(this.hasFill() && this.hasStroke());\n\t},\n\n\t_contains: function(point) {\n\t\tvar winding = point.isInside(\n\t\t\t\tthis.getBounds({ internal: true, handle: true }))\n\t\t\t\t\t? this._getWinding(point)\n\t\t\t\t\t: {};\n\t\treturn winding.onPath || !!(this.getFillRule() === 'evenodd'\n\t\t\t\t? winding.windingL & 1 || winding.windingR & 1\n\t\t\t\t: winding.winding);\n\t},\n\n\tgetIntersections: function(path, include, _matrix, _returnFirst) {\n\t\tvar self = this === path || !path,\n\t\t\tmatrix1 = this._matrix._orNullIfIdentity(),\n\t\t\tmatrix2 = self ? matrix1\n\t\t\t\t: (_matrix || path._matrix)._orNullIfIdentity();\n\t\treturn self || this.getBounds(matrix1).intersects(\n\t\t\t\tpath.getBounds(matrix2), 1e-12)\n\t\t\t\t? Curve.getIntersections(\n\t\t\t\t\t\tthis.getCurves(), !self && path.getCurves(), include,\n\t\t\t\t\t\tmatrix1, matrix2, _returnFirst)\n\t\t\t\t: [];\n\t},\n\n\tgetCrossings: function(path) {\n\t\treturn this.getIntersections(path, function(inter) {\n\t\t\treturn inter.isCrossing();\n\t\t});\n\t},\n\n\tgetNearestLocation: function() {\n\t\tvar point = Point.read(arguments),\n\t\t\tcurves = this.getCurves(),\n\t\t\tminDist = Infinity,\n\t\t\tminLoc = null;\n\t\tfor (var i = 0, l = curves.length; i < l; i++) {\n\t\t\tvar loc = curves[i].getNearestLocation(point);\n\t\t\tif (loc._distance < minDist) {\n\t\t\t\tminDist = loc._distance;\n\t\t\t\tminLoc = loc;\n\t\t\t}\n\t\t}\n\t\treturn minLoc;\n\t},\n\n\tgetNearestPoint: function() {\n\t\tvar loc = this.getNearestLocation.apply(this, arguments);\n\t\treturn loc ? loc.getPoint() : loc;\n\t},\n\n\tinterpolate: function(from, to, factor) {\n\t\tvar isPath = !this._children,\n\t\t\tname = isPath ? '_segments' : '_children',\n\t\t\titemsFrom = from[name],\n\t\t\titemsTo = to[name],\n\t\t\titems = this[name];\n\t\tif (!itemsFrom || !itemsTo || itemsFrom.length !== itemsTo.length) {\n\t\t\tthrow new Error('Invalid operands in interpolate() call: ' +\n\t\t\t\t\tfrom + ', ' + to);\n\t\t}\n\t\tvar current = items.length,\n\t\t\tlength = itemsTo.length;\n\t\tif (current < length) {\n\t\t\tvar ctor = isPath ? Segment : Path;\n\t\t\tfor (var i = current; i < length; i++) {\n\t\t\t\tthis.add(new ctor());\n\t\t\t}\n\t\t} else if (current > length) {\n\t\t\tthis[isPath ? 'removeSegments' : 'removeChildren'](length, current);\n\t\t}\n\t\tfor (var i = 0; i < length; i++) {\n\t\t\titems[i].interpolate(itemsFrom[i], itemsTo[i], factor);\n\t\t}\n\t\tif (isPath) {\n\t\t\tthis.setClosed(from._closed);\n\t\t\tthis._changed(9);\n\t\t}\n\t},\n\n\tcompare: function(path) {\n\t\tvar ok = false;\n\t\tif (path) {\n\t\t\tvar paths1 = this._children || [this],\n\t\t\t\tpaths2 = path._children ? path._children.slice() : [path],\n\t\t\t\tlength1 = paths1.length,\n\t\t\t\tlength2 = paths2.length,\n\t\t\t\tmatched = [],\n\t\t\t\tcount = 0;\n\t\t\tok = true;\n\t\t\tvar boundsOverlaps = CollisionDetection.findItemBoundsCollisions(paths1, paths2, Numerical.GEOMETRIC_EPSILON);\n\t\t\tfor (var i1 = length1 - 1; i1 >= 0 && ok; i1--) {\n\t\t\t\tvar path1 = paths1[i1];\n\t\t\t\tok = false;\n\t\t\t\tvar pathBoundsOverlaps = boundsOverlaps[i1];\n\t\t\t\tif (pathBoundsOverlaps) {\n\t\t\t\t\tfor (var i2 = pathBoundsOverlaps.length - 1; i2 >= 0 && !ok; i2--) {\n\t\t\t\t\t\tif (path1.compare(paths2[pathBoundsOverlaps[i2]])) {\n\t\t\t\t\t\t\tif (!matched[pathBoundsOverlaps[i2]]) {\n\t\t\t\t\t\t\t\tmatched[pathBoundsOverlaps[i2]] = true;\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tok = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tok = ok && count === length2;\n\t\t}\n\t\treturn ok;\n\t},\n\n});\n\nvar Path = PathItem.extend({\n\t_class: 'Path',\n\t_serializeFields: {\n\t\tsegments: [],\n\t\tclosed: false\n\t},\n\n\tinitialize: function Path(arg) {\n\t\tthis._closed = false;\n\t\tthis._segments = [];\n\t\tthis._version = 0;\n\t\tvar args = arguments,\n\t\t\tsegments = Array.isArray(arg)\n\t\t\t? typeof arg[0] === 'object'\n\t\t\t\t? arg\n\t\t\t\t: args\n\t\t\t: arg && (arg.size === undefined && (arg.x !== undefined\n\t\t\t\t\t|| arg.point !== undefined))\n\t\t\t\t? args\n\t\t\t\t: null;\n\t\tif (segments && segments.length > 0) {\n\t\t\tthis.setSegments(segments);\n\t\t} else {\n\t\t\tthis._curves = undefined;\n\t\t\tthis._segmentSelection = 0;\n\t\t\tif (!segments && typeof arg === 'string') {\n\t\t\t\tthis.setPathData(arg);\n\t\t\t\targ = null;\n\t\t\t}\n\t\t}\n\t\tthis._initialize(!segments && arg);\n\t},\n\n\t_equals: function(item) {\n\t\treturn this._closed === item._closed\n\t\t\t\t&& Base.equals(this._segments, item._segments);\n\t},\n\n\tcopyContent: function(source) {\n\t\tthis.setSegments(source._segments);\n\t\tthis._closed = source._closed;\n\t},\n\n\t_changed: function _changed(flags) {\n\t\t_changed.base.call(this, flags);\n\t\tif (flags & 8) {\n\t\t\tthis._length = this._area = undefined;\n\t\t\tif (flags & 32) {\n\t\t\t\tthis._version++;\n\t\t\t} else if (this._curves) {\n\t\t\t for (var i = 0, l = this._curves.length; i < l; i++)\n\t\t\t\t\tthis._curves[i]._changed();\n\t\t\t}\n\t\t} else if (flags & 64) {\n\t\t\tthis._bounds = undefined;\n\t\t}\n\t},\n\n\tgetStyle: function() {\n\t\tvar parent = this._parent;\n\t\treturn (parent instanceof CompoundPath ? parent : this)._style;\n\t},\n\n\tgetSegments: function() {\n\t\treturn this._segments;\n\t},\n\n\tsetSegments: function(segments) {\n\t\tvar fullySelected = this.isFullySelected(),\n\t\t\tlength = segments && segments.length;\n\t\tthis._segments.length = 0;\n\t\tthis._segmentSelection = 0;\n\t\tthis._curves = undefined;\n\t\tif (length) {\n\t\t\tvar last = segments[length - 1];\n\t\t\tif (typeof last === 'boolean') {\n\t\t\t\tthis.setClosed(last);\n\t\t\t\tlength--;\n\t\t\t}\n\t\t\tthis._add(Segment.readList(segments, 0, {}, length));\n\t\t}\n\t\tif (fullySelected)\n\t\t\tthis.setFullySelected(true);\n\t},\n\n\tgetFirstSegment: function() {\n\t\treturn this._segments[0];\n\t},\n\n\tgetLastSegment: function() {\n\t\treturn this._segments[this._segments.length - 1];\n\t},\n\n\tgetCurves: function() {\n\t\tvar curves = this._curves,\n\t\t\tsegments = this._segments;\n\t\tif (!curves) {\n\t\t\tvar length = this._countCurves();\n\t\t\tcurves = this._curves = new Array(length);\n\t\t\tfor (var i = 0; i < length; i++)\n\t\t\t\tcurves[i] = new Curve(this, segments[i],\n\t\t\t\t\tsegments[i + 1] || segments[0]);\n\t\t}\n\t\treturn curves;\n\t},\n\n\tgetFirstCurve: function() {\n\t\treturn this.getCurves()[0];\n\t},\n\n\tgetLastCurve: function() {\n\t\tvar curves = this.getCurves();\n\t\treturn curves[curves.length - 1];\n\t},\n\n\tisClosed: function() {\n\t\treturn this._closed;\n\t},\n\n\tsetClosed: function(closed) {\n\t\tif (this._closed != (closed = !!closed)) {\n\t\t\tthis._closed = closed;\n\t\t\tif (this._curves) {\n\t\t\t\tvar length = this._curves.length = this._countCurves();\n\t\t\t\tif (closed)\n\t\t\t\t\tthis._curves[length - 1] = new Curve(this,\n\t\t\t\t\t\tthis._segments[length - 1], this._segments[0]);\n\t\t\t}\n\t\t\tthis._changed(41);\n\t\t}\n\t}\n}, {\n\tbeans: true,\n\n\tgetPathData: function(_matrix, _precision) {\n\t\tvar segments = this._segments,\n\t\t\tlength = segments.length,\n\t\t\tf = new Formatter(_precision),\n\t\t\tcoords = new Array(6),\n\t\t\tfirst = true,\n\t\t\tcurX, curY,\n\t\t\tprevX, prevY,\n\t\t\tinX, inY,\n\t\t\toutX, outY,\n\t\t\tparts = [];\n\n\t\tfunction addSegment(segment, skipLine) {\n\t\t\tsegment._transformCoordinates(_matrix, coords);\n\t\t\tcurX = coords[0];\n\t\t\tcurY = coords[1];\n\t\t\tif (first) {\n\t\t\t\tparts.push('M' + f.pair(curX, curY));\n\t\t\t\tfirst = false;\n\t\t\t} else {\n\t\t\t\tinX = coords[2];\n\t\t\t\tinY = coords[3];\n\t\t\t\tif (inX === curX && inY === curY\n\t\t\t\t\t\t&& outX === prevX && outY === prevY) {\n\t\t\t\t\tif (!skipLine) {\n\t\t\t\t\t\tvar dx = curX - prevX,\n\t\t\t\t\t\t\tdy = curY - prevY;\n\t\t\t\t\t\tparts.push(\n\t\t\t\t\t\t\t dx === 0 ? 'v' + f.number(dy)\n\t\t\t\t\t\t\t: dy === 0 ? 'h' + f.number(dx)\n\t\t\t\t\t\t\t: 'l' + f.pair(dx, dy));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tparts.push('c' + f.pair(outX - prevX, outY - prevY)\n\t\t\t\t\t\t\t + ' ' + f.pair( inX - prevX, inY - prevY)\n\t\t\t\t\t\t\t + ' ' + f.pair(curX - prevX, curY - prevY));\n\t\t\t\t}\n\t\t\t}\n\t\t\tprevX = curX;\n\t\t\tprevY = curY;\n\t\t\toutX = coords[4];\n\t\t\toutY = coords[5];\n\t\t}\n\n\t\tif (!length)\n\t\t\treturn '';\n\n\t\tfor (var i = 0; i < length; i++)\n\t\t\taddSegment(segments[i]);\n\t\tif (this._closed && length > 0) {\n\t\t\taddSegment(segments[0], true);\n\t\t\tparts.push('z');\n\t\t}\n\t\treturn parts.join('');\n\t},\n\n\tisEmpty: function() {\n\t\treturn !this._segments.length;\n\t},\n\n\t_transformContent: function(matrix) {\n\t\tvar segments = this._segments,\n\t\t\tcoords = new Array(6);\n\t\tfor (var i = 0, l = segments.length; i < l; i++)\n\t\t\tsegments[i]._transformCoordinates(matrix, coords, true);\n\t\treturn true;\n\t},\n\n\t_add: function(segs, index) {\n\t\tvar segments = this._segments,\n\t\t\tcurves = this._curves,\n\t\t\tamount = segs.length,\n\t\t\tappend = index == null,\n\t\t\tindex = append ? segments.length : index;\n\t\tfor (var i = 0; i < amount; i++) {\n\t\t\tvar segment = segs[i];\n\t\t\tif (segment._path)\n\t\t\t\tsegment = segs[i] = segment.clone();\n\t\t\tsegment._path = this;\n\t\t\tsegment._index = index + i;\n\t\t\tif (segment._selection)\n\t\t\t\tthis._updateSelection(segment, 0, segment._selection);\n\t\t}\n\t\tif (append) {\n\t\t\tBase.push(segments, segs);\n\t\t} else {\n\t\t\tsegments.splice.apply(segments, [index, 0].concat(segs));\n\t\t\tfor (var i = index + amount, l = segments.length; i < l; i++)\n\t\t\t\tsegments[i]._index = i;\n\t\t}\n\t\tif (curves) {\n\t\t\tvar total = this._countCurves(),\n\t\t\t\tstart = index > 0 && index + amount - 1 === total ? index - 1\n\t\t\t\t\t: index,\n\t\t\t\tinsert = start,\n\t\t\t\tend = Math.min(start + amount, total);\n\t\t\tif (segs._curves) {\n\t\t\t\tcurves.splice.apply(curves, [start, 0].concat(segs._curves));\n\t\t\t\tinsert += segs._curves.length;\n\t\t\t}\n\t\t\tfor (var i = insert; i < end; i++)\n\t\t\t\tcurves.splice(i, 0, new Curve(this, null, null));\n\t\t\tthis._adjustCurves(start, end);\n\t\t}\n\t\tthis._changed(41);\n\t\treturn segs;\n\t},\n\n\t_adjustCurves: function(start, end) {\n\t\tvar segments = this._segments,\n\t\t\tcurves = this._curves,\n\t\t\tcurve;\n\t\tfor (var i = start; i < end; i++) {\n\t\t\tcurve = curves[i];\n\t\t\tcurve._path = this;\n\t\t\tcurve._segment1 = segments[i];\n\t\t\tcurve._segment2 = segments[i + 1] || segments[0];\n\t\t\tcurve._changed();\n\t\t}\n\t\tif (curve = curves[this._closed && !start ? segments.length - 1\n\t\t\t\t: start - 1]) {\n\t\t\tcurve._segment2 = segments[start] || segments[0];\n\t\t\tcurve._changed();\n\t\t}\n\t\tif (curve = curves[end]) {\n\t\t\tcurve._segment1 = segments[end];\n\t\t\tcurve._changed();\n\t\t}\n\t},\n\n\t_countCurves: function() {\n\t\tvar length = this._segments.length;\n\t\treturn !this._closed && length > 0 ? length - 1 : length;\n\t},\n\n\tadd: function(segment1 ) {\n\t\tvar args = arguments;\n\t\treturn args.length > 1 && typeof segment1 !== 'number'\n\t\t\t? this._add(Segment.readList(args))\n\t\t\t: this._add([ Segment.read(args) ])[0];\n\t},\n\n\tinsert: function(index, segment1 ) {\n\t\tvar args = arguments;\n\t\treturn args.length > 2 && typeof segment1 !== 'number'\n\t\t\t? this._add(Segment.readList(args, 1), index)\n\t\t\t: this._add([ Segment.read(args, 1) ], index)[0];\n\t},\n\n\taddSegment: function() {\n\t\treturn this._add([ Segment.read(arguments) ])[0];\n\t},\n\n\tinsertSegment: function(index ) {\n\t\treturn this._add([ Segment.read(arguments, 1) ], index)[0];\n\t},\n\n\taddSegments: function(segments) {\n\t\treturn this._add(Segment.readList(segments));\n\t},\n\n\tinsertSegments: function(index, segments) {\n\t\treturn this._add(Segment.readList(segments), index);\n\t},\n\n\tremoveSegment: function(index) {\n\t\treturn this.removeSegments(index, index + 1)[0] || null;\n\t},\n\n\tremoveSegments: function(start, end, _includeCurves) {\n\t\tstart = start || 0;\n\t\tend = Base.pick(end, this._segments.length);\n\t\tvar segments = this._segments,\n\t\t\tcurves = this._curves,\n\t\t\tcount = segments.length,\n\t\t\tremoved = segments.splice(start, end - start),\n\t\t\tamount = removed.length;\n\t\tif (!amount)\n\t\t\treturn removed;\n\t\tfor (var i = 0; i < amount; i++) {\n\t\t\tvar segment = removed[i];\n\t\t\tif (segment._selection)\n\t\t\t\tthis._updateSelection(segment, segment._selection, 0);\n\t\t\tsegment._index = segment._path = null;\n\t\t}\n\t\tfor (var i = start, l = segments.length; i < l; i++)\n\t\t\tsegments[i]._index = i;\n\t\tif (curves) {\n\t\t\tvar index = start > 0 && end === count + (this._closed ? 1 : 0)\n\t\t\t\t\t? start - 1\n\t\t\t\t\t: start,\n\t\t\t\tcurves = curves.splice(index, amount);\n\t\t\tfor (var i = curves.length - 1; i >= 0; i--)\n\t\t\t\tcurves[i]._path = null;\n\t\t\tif (_includeCurves)\n\t\t\t\tremoved._curves = curves.slice(1);\n\t\t\tthis._adjustCurves(index, index);\n\t\t}\n\t\tthis._changed(41);\n\t\treturn removed;\n\t},\n\n\tclear: '#removeSegments',\n\n\thasHandles: function() {\n\t\tvar segments = this._segments;\n\t\tfor (var i = 0, l = segments.length; i < l; i++) {\n\t\t\tif (segments[i].hasHandles())\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n\n\tclearHandles: function() {\n\t\tvar segments = this._segments;\n\t\tfor (var i = 0, l = segments.length; i < l; i++)\n\t\t\tsegments[i].clearHandles();\n\t},\n\n\tgetLength: function() {\n\t\tif (this._length == null) {\n\t\t\tvar curves = this.getCurves(),\n\t\t\t\tlength = 0;\n\t\t\tfor (var i = 0, l = curves.length; i < l; i++)\n\t\t\t\tlength += curves[i].getLength();\n\t\t\tthis._length = length;\n\t\t}\n\t\treturn this._length;\n\t},\n\n\tgetArea: function() {\n\t\tvar area = this._area;\n\t\tif (area == null) {\n\t\t\tvar segments = this._segments,\n\t\t\t\tclosed = this._closed;\n\t\t\tarea = 0;\n\t\t\tfor (var i = 0, l = segments.length; i < l; i++) {\n\t\t\t\tvar last = i + 1 === l;\n\t\t\t\tarea += Curve.getArea(Curve.getValues(\n\t\t\t\t\t\tsegments[i], segments[last ? 0 : i + 1],\n\t\t\t\t\t\tnull, last && !closed));\n\t\t\t}\n\t\t\tthis._area = area;\n\t\t}\n\t\treturn area;\n\t},\n\n\tisFullySelected: function() {\n\t\tvar length = this._segments.length;\n\t\treturn this.isSelected() && length > 0 && this._segmentSelection\n\t\t\t\t=== length * 7;\n\t},\n\n\tsetFullySelected: function(selected) {\n\t\tif (selected)\n\t\t\tthis._selectSegments(true);\n\t\tthis.setSelected(selected);\n\t},\n\n\tsetSelection: function setSelection(selection) {\n\t\tif (!(selection & 1))\n\t\t\tthis._selectSegments(false);\n\t\tsetSelection.base.call(this, selection);\n\t},\n\n\t_selectSegments: function(selected) {\n\t\tvar segments = this._segments,\n\t\t\tlength = segments.length,\n\t\t\tselection = selected ? 7 : 0;\n\t\tthis._segmentSelection = selection * length;\n\t\tfor (var i = 0; i < length; i++)\n\t\t\tsegments[i]._selection = selection;\n\t},\n\n\t_updateSelection: function(segment, oldSelection, newSelection) {\n\t\tsegment._selection = newSelection;\n\t\tvar selection = this._segmentSelection += newSelection - oldSelection;\n\t\tif (selection > 0)\n\t\t\tthis.setSelected(true);\n\t},\n\n\tdivideAt: function(location) {\n\t\tvar loc = this.getLocationAt(location),\n\t\t\tcurve;\n\t\treturn loc && (curve = loc.getCurve().divideAt(loc.getCurveOffset()))\n\t\t\t\t? curve._segment1\n\t\t\t\t: null;\n\t},\n\n\tsplitAt: function(location) {\n\t\tvar loc = this.getLocationAt(location),\n\t\t\tindex = loc && loc.index,\n\t\t\ttime = loc && loc.time,\n\t\t\ttMin = 1e-8,\n\t\t\ttMax = 1 - tMin;\n\t\tif (time > tMax) {\n\t\t\tindex++;\n\t\t\ttime = 0;\n\t\t}\n\t\tvar curves = this.getCurves();\n\t\tif (index >= 0 && index < curves.length) {\n\t\t\tif (time >= tMin) {\n\t\t\t\tcurves[index++].divideAtTime(time);\n\t\t\t}\n\t\t\tvar segs = this.removeSegments(index, this._segments.length, true),\n\t\t\t\tpath;\n\t\t\tif (this._closed) {\n\t\t\t\tthis.setClosed(false);\n\t\t\t\tpath = this;\n\t\t\t} else {\n\t\t\t\tpath = new Path(Item.NO_INSERT);\n\t\t\t\tpath.insertAbove(this);\n\t\t\t\tpath.copyAttributes(this);\n\t\t\t}\n\t\t\tpath._add(segs, 0);\n\t\t\tthis.addSegment(segs[0]);\n\t\t\treturn path;\n\t\t}\n\t\treturn null;\n\t},\n\n\tsplit: function(index, time) {\n\t\tvar curve,\n\t\t\tlocation = time === undefined ? index\n\t\t\t\t: (curve = this.getCurves()[index])\n\t\t\t\t\t&& curve.getLocationAtTime(time);\n\t\treturn location != null ? this.splitAt(location) : null;\n\t},\n\n\tjoin: function(path, tolerance) {\n\t\tvar epsilon = tolerance || 0;\n\t\tif (path && path !== this) {\n\t\t\tvar segments = path._segments,\n\t\t\t\tlast1 = this.getLastSegment(),\n\t\t\t\tlast2 = path.getLastSegment();\n\t\t\tif (!last2)\n\t\t\t\treturn this;\n\t\t\tif (last1 && last1._point.isClose(last2._point, epsilon))\n\t\t\t\tpath.reverse();\n\t\t\tvar first2 = path.getFirstSegment();\n\t\t\tif (last1 && last1._point.isClose(first2._point, epsilon)) {\n\t\t\t\tlast1.setHandleOut(first2._handleOut);\n\t\t\t\tthis._add(segments.slice(1));\n\t\t\t} else {\n\t\t\t\tvar first1 = this.getFirstSegment();\n\t\t\t\tif (first1 && first1._point.isClose(first2._point, epsilon))\n\t\t\t\t\tpath.reverse();\n\t\t\t\tlast2 = path.getLastSegment();\n\t\t\t\tif (first1 && first1._point.isClose(last2._point, epsilon)) {\n\t\t\t\t\tfirst1.setHandleIn(last2._handleIn);\n\t\t\t\t\tthis._add(segments.slice(0, segments.length - 1), 0);\n\t\t\t\t} else {\n\t\t\t\t\tthis._add(segments.slice());\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (path._closed)\n\t\t\t\tthis._add([segments[0]]);\n\t\t\tpath.remove();\n\t\t}\n\t\tvar first = this.getFirstSegment(),\n\t\t\tlast = this.getLastSegment();\n\t\tif (first !== last && first._point.isClose(last._point, epsilon)) {\n\t\t\tfirst.setHandleIn(last._handleIn);\n\t\t\tlast.remove();\n\t\t\tthis.setClosed(true);\n\t\t}\n\t\treturn this;\n\t},\n\n\treduce: function(options) {\n\t\tvar curves = this.getCurves(),\n\t\t\tsimplify = options && options.simplify,\n\t\t\ttolerance = simplify ? 1e-7 : 0;\n\t\tfor (var i = curves.length - 1; i >= 0; i--) {\n\t\t\tvar curve = curves[i];\n\t\t\tif (!curve.hasHandles() && (!curve.hasLength(tolerance)\n\t\t\t\t\t|| simplify && curve.isCollinear(curve.getNext())))\n\t\t\t\tcurve.remove();\n\t\t}\n\t\treturn this;\n\t},\n\n\treverse: function() {\n\t\tthis._segments.reverse();\n\t\tfor (var i = 0, l = this._segments.length; i < l; i++) {\n\t\t\tvar segment = this._segments[i];\n\t\t\tvar handleIn = segment._handleIn;\n\t\t\tsegment._handleIn = segment._handleOut;\n\t\t\tsegment._handleOut = handleIn;\n\t\t\tsegment._index = i;\n\t\t}\n\t\tthis._curves = null;\n\t\tthis._changed(9);\n\t},\n\n\tflatten: function(flatness) {\n\t\tvar flattener = new PathFlattener(this, flatness || 0.25, 256, true),\n\t\t\tparts = flattener.parts,\n\t\t\tlength = parts.length,\n\t\t\tsegments = [];\n\t\tfor (var i = 0; i < length; i++) {\n\t\t\tsegments.push(new Segment(parts[i].curve.slice(0, 2)));\n\t\t}\n\t\tif (!this._closed && length > 0) {\n\t\t\tsegments.push(new Segment(parts[length - 1].curve.slice(6)));\n\t\t}\n\t\tthis.setSegments(segments);\n\t},\n\n\tsimplify: function(tolerance) {\n\t\tvar segments = new PathFitter(this).fit(tolerance || 2.5);\n\t\tif (segments)\n\t\t\tthis.setSegments(segments);\n\t\treturn !!segments;\n\t},\n\n\tsmooth: function(options) {\n\t\tvar that = this,\n\t\t\topts = options || {},\n\t\t\ttype = opts.type || 'asymmetric',\n\t\t\tsegments = this._segments,\n\t\t\tlength = segments.length,\n\t\t\tclosed = this._closed;\n\n\t\tfunction getIndex(value, _default) {\n\t\t\tvar index = value && value.index;\n\t\t\tif (index != null) {\n\t\t\t\tvar path = value.path;\n\t\t\t\tif (path && path !== that)\n\t\t\t\t\tthrow new Error(value._class + ' ' + index + ' of ' + path\n\t\t\t\t\t\t\t+ ' is not part of ' + that);\n\t\t\t\tif (_default && value instanceof Curve)\n\t\t\t\t\tindex++;\n\t\t\t} else {\n\t\t\t\tindex = typeof value === 'number' ? value : _default;\n\t\t\t}\n\t\t\treturn Math.min(index < 0 && closed\n\t\t\t\t\t? index % length\n\t\t\t\t\t: index < 0 ? index + length : index, length - 1);\n\t\t}\n\n\t\tvar loop = closed && opts.from === undefined && opts.to === undefined,\n\t\t\tfrom = getIndex(opts.from, 0),\n\t\t\tto = getIndex(opts.to, length - 1);\n\n\t\tif (from > to) {\n\t\t\tif (closed) {\n\t\t\t\tfrom -= length;\n\t\t\t} else {\n\t\t\t\tvar tmp = from;\n\t\t\t\tfrom = to;\n\t\t\t\tto = tmp;\n\t\t\t}\n\t\t}\n\t\tif (/^(?:asymmetric|continuous)$/.test(type)) {\n\t\t\tvar asymmetric = type === 'asymmetric',\n\t\t\t\tmin = Math.min,\n\t\t\t\tamount = to - from + 1,\n\t\t\t\tn = amount - 1,\n\t\t\t\tpadding = loop ? min(amount, 4) : 1,\n\t\t\t\tpaddingLeft = padding,\n\t\t\t\tpaddingRight = padding,\n\t\t\t\tknots = [];\n\t\t\tif (!closed) {\n\t\t\t\tpaddingLeft = min(1, from);\n\t\t\t\tpaddingRight = min(1, length - to - 1);\n\t\t\t}\n\t\t\tn += paddingLeft + paddingRight;\n\t\t\tif (n <= 1)\n\t\t\t\treturn;\n\t\t\tfor (var i = 0, j = from - paddingLeft; i <= n; i++, j++) {\n\t\t\t\tknots[i] = segments[(j < 0 ? j + length : j) % length]._point;\n\t\t\t}\n\n\t\t\tvar x = knots[0]._x + 2 * knots[1]._x,\n\t\t\t\ty = knots[0]._y + 2 * knots[1]._y,\n\t\t\t\tf = 2,\n\t\t\t\tn_1 = n - 1,\n\t\t\t\trx = [x],\n\t\t\t\try = [y],\n\t\t\t\trf = [f],\n\t\t\t\tpx = [],\n\t\t\t\tpy = [];\n\t\t\tfor (var i = 1; i < n; i++) {\n\t\t\t\tvar internal = i < n_1,\n\t\t\t\t\ta = internal ? 1 : asymmetric ? 1 : 2,\n\t\t\t\t\tb = internal ? 4 : asymmetric ? 2 : 7,\n\t\t\t\t\tu = internal ? 4 : asymmetric ? 3 : 8,\n\t\t\t\t\tv = internal ? 2 : asymmetric ? 0 : 1,\n\t\t\t\t\tm = a / f;\n\t\t\t\tf = rf[i] = b - m;\n\t\t\t\tx = rx[i] = u * knots[i]._x + v * knots[i + 1]._x - m * x;\n\t\t\t\ty = ry[i] = u * knots[i]._y + v * knots[i + 1]._y - m * y;\n\t\t\t}\n\n\t\t\tpx[n_1] = rx[n_1] / rf[n_1];\n\t\t\tpy[n_1] = ry[n_1] / rf[n_1];\n\t\t\tfor (var i = n - 2; i >= 0; i--) {\n\t\t\t\tpx[i] = (rx[i] - px[i + 1]) / rf[i];\n\t\t\t\tpy[i] = (ry[i] - py[i + 1]) / rf[i];\n\t\t\t}\n\t\t\tpx[n] = (3 * knots[n]._x - px[n_1]) / 2;\n\t\t\tpy[n] = (3 * knots[n]._y - py[n_1]) / 2;\n\n\t\t\tfor (var i = paddingLeft, max = n - paddingRight, j = from;\n\t\t\t\t\ti <= max; i++, j++) {\n\t\t\t\tvar segment = segments[j < 0 ? j + length : j],\n\t\t\t\t\tpt = segment._point,\n\t\t\t\t\thx = px[i] - pt._x,\n\t\t\t\t\thy = py[i] - pt._y;\n\t\t\t\tif (loop || i < max)\n\t\t\t\t\tsegment.setHandleOut(hx, hy);\n\t\t\t\tif (loop || i > paddingLeft)\n\t\t\t\t\tsegment.setHandleIn(-hx, -hy);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (var i = from; i <= to; i++) {\n\t\t\t\tsegments[i < 0 ? i + length : i].smooth(opts,\n\t\t\t\t\t\t!loop && i === from, !loop && i === to);\n\t\t\t}\n\t\t}\n\t},\n\n\ttoShape: function(insert) {\n\t\tif (!this._closed)\n\t\t\treturn null;\n\n\t\tvar segments = this._segments,\n\t\t\ttype,\n\t\t\tsize,\n\t\t\tradius,\n\t\t\ttopCenter;\n\n\t\tfunction isCollinear(i, j) {\n\t\t\tvar seg1 = segments[i],\n\t\t\t\tseg2 = seg1.getNext(),\n\t\t\t\tseg3 = segments[j],\n\t\t\t\tseg4 = seg3.getNext();\n\t\t\treturn seg1._handleOut.isZero() && seg2._handleIn.isZero()\n\t\t\t\t\t&& seg3._handleOut.isZero() && seg4._handleIn.isZero()\n\t\t\t\t\t&& seg2._point.subtract(seg1._point).isCollinear(\n\t\t\t\t\t\tseg4._point.subtract(seg3._point));\n\t\t}\n\n\t\tfunction isOrthogonal(i) {\n\t\t\tvar seg2 = segments[i],\n\t\t\t\tseg1 = seg2.getPrevious(),\n\t\t\t\tseg3 = seg2.getNext();\n\t\t\treturn seg1._handleOut.isZero() && seg2._handleIn.isZero()\n\t\t\t\t\t&& seg2._handleOut.isZero() && seg3._handleIn.isZero()\n\t\t\t\t\t&& seg2._point.subtract(seg1._point).isOrthogonal(\n\t\t\t\t\t\tseg3._point.subtract(seg2._point));\n\t\t}\n\n\t\tfunction isArc(i) {\n\t\t\tvar seg1 = segments[i],\n\t\t\t\tseg2 = seg1.getNext(),\n\t\t\t\thandle1 = seg1._handleOut,\n\t\t\t\thandle2 = seg2._handleIn,\n\t\t\t\tkappa = 0.5522847498307936;\n\t\t\tif (handle1.isOrthogonal(handle2)) {\n\t\t\t\tvar pt1 = seg1._point,\n\t\t\t\t\tpt2 = seg2._point,\n\t\t\t\t\tcorner = new Line(pt1, handle1, true).intersect(\n\t\t\t\t\t\t\tnew Line(pt2, handle2, true), true);\n\t\t\t\treturn corner && Numerical.isZero(handle1.getLength() /\n\t\t\t\t\t\tcorner.subtract(pt1).getLength() - kappa)\n\t\t\t\t\t&& Numerical.isZero(handle2.getLength() /\n\t\t\t\t\t\tcorner.subtract(pt2).getLength() - kappa);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\tfunction getDistance(i, j) {\n\t\t\treturn segments[i]._point.getDistance(segments[j]._point);\n\t\t}\n\n\t\tif (!this.hasHandles() && segments.length === 4\n\t\t\t\t&& isCollinear(0, 2) && isCollinear(1, 3) && isOrthogonal(1)) {\n\t\t\ttype = Shape.Rectangle;\n\t\t\tsize = new Size(getDistance(0, 3), getDistance(0, 1));\n\t\t\ttopCenter = segments[1]._point.add(segments[2]._point).divide(2);\n\t\t} else if (segments.length === 8 && isArc(0) && isArc(2) && isArc(4)\n\t\t\t\t&& isArc(6) && isCollinear(1, 5) && isCollinear(3, 7)) {\n\t\t\ttype = Shape.Rectangle;\n\t\t\tsize = new Size(getDistance(1, 6), getDistance(0, 3));\n\t\t\tradius = size.subtract(new Size(getDistance(0, 7),\n\t\t\t\t\tgetDistance(1, 2))).divide(2);\n\t\t\ttopCenter = segments[3]._point.add(segments[4]._point).divide(2);\n\t\t} else if (segments.length === 4\n\t\t\t\t&& isArc(0) && isArc(1) && isArc(2) && isArc(3)) {\n\t\t\tif (Numerical.isZero(getDistance(0, 2) - getDistance(1, 3))) {\n\t\t\t\ttype = Shape.Circle;\n\t\t\t\tradius = getDistance(0, 2) / 2;\n\t\t\t} else {\n\t\t\t\ttype = Shape.Ellipse;\n\t\t\t\tradius = new Size(getDistance(2, 0) / 2, getDistance(3, 1) / 2);\n\t\t\t}\n\t\t\ttopCenter = segments[1]._point;\n\t\t}\n\n\t\tif (type) {\n\t\t\tvar center = this.getPosition(true),\n\t\t\t\tshape = new type({\n\t\t\t\t\tcenter: center,\n\t\t\t\t\tsize: size,\n\t\t\t\t\tradius: radius,\n\t\t\t\t\tinsert: false\n\t\t\t\t});\n\t\t\tshape.copyAttributes(this, true);\n\t\t\tshape._matrix.prepend(this._matrix);\n\t\t\tshape.rotate(topCenter.subtract(center).getAngle() + 90);\n\t\t\tif (insert === undefined || insert)\n\t\t\t\tshape.insertAbove(this);\n\t\t\treturn shape;\n\t\t}\n\t\treturn null;\n\t},\n\n\ttoPath: '#clone',\n\n\tcompare: function compare(path) {\n\t\tif (!path || path instanceof CompoundPath)\n\t\t\treturn compare.base.call(this, path);\n\t\tvar curves1 = this.getCurves(),\n\t\t\tcurves2 = path.getCurves(),\n\t\t\tlength1 = curves1.length,\n\t\t\tlength2 = curves2.length;\n\t\tif (!length1 || !length2) {\n\t\t\treturn length1 == length2;\n\t\t}\n\t\tvar v1 = curves1[0].getValues(),\n\t\t\tvalues2 = [],\n\t\t\tpos1 = 0, pos2,\n\t\t\tend1 = 0, end2;\n\t\tfor (var i = 0; i < length2; i++) {\n\t\t\tvar v2 = curves2[i].getValues();\n\t\t\tvalues2.push(v2);\n\t\t\tvar overlaps = Curve.getOverlaps(v1, v2);\n\t\t\tif (overlaps) {\n\t\t\t\tpos2 = !i && overlaps[0][0] > 0 ? length2 - 1 : i;\n\t\t\t\tend2 = overlaps[0][1];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tvar abs = Math.abs,\n\t\t\tepsilon = 1e-8,\n\t\t\tv2 = values2[pos2],\n\t\t\tstart2;\n\t\twhile (v1 && v2) {\n\t\t\tvar overlaps = Curve.getOverlaps(v1, v2);\n\t\t\tif (overlaps) {\n\t\t\t\tvar t1 = overlaps[0][0];\n\t\t\t\tif (abs(t1 - end1) < epsilon) {\n\t\t\t\t\tend1 = overlaps[1][0];\n\t\t\t\t\tif (end1 === 1) {\n\t\t\t\t\t\tv1 = ++pos1 < length1 ? curves1[pos1].getValues() : null;\n\t\t\t\t\t\tend1 = 0;\n\t\t\t\t\t}\n\t\t\t\t\tvar t2 = overlaps[0][1];\n\t\t\t\t\tif (abs(t2 - end2) < epsilon) {\n\t\t\t\t\t\tif (!start2)\n\t\t\t\t\t\t\tstart2 = [pos2, t2];\n\t\t\t\t\t\tend2 = overlaps[1][1];\n\t\t\t\t\t\tif (end2 === 1) {\n\t\t\t\t\t\t\tif (++pos2 >= length2)\n\t\t\t\t\t\t\t\tpos2 = 0;\n\t\t\t\t\t\t\tv2 = values2[pos2] || curves2[pos2].getValues();\n\t\t\t\t\t\t\tend2 = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!v1) {\n\t\t\t\t\t\t\treturn start2[0] === pos2 && start2[1] === end2;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\treturn false;\n\t},\n\n\t_hitTestSelf: function(point, options, viewMatrix, strokeMatrix) {\n\t\tvar that = this,\n\t\t\tstyle = this.getStyle(),\n\t\t\tsegments = this._segments,\n\t\t\tnumSegments = segments.length,\n\t\t\tclosed = this._closed,\n\t\t\ttolerancePadding = options._tolerancePadding,\n\t\t\tstrokePadding = tolerancePadding,\n\t\t\tjoin, cap, miterLimit,\n\t\t\tarea, loc, res,\n\t\t\thitStroke = options.stroke && (style.hasStroke() || options.hitUnstrokedPaths),\n\t\t\thitFill = options.fill && (style.hasFill() || options.hitUnfilledPaths),\n\t\t\thitCurves = options.curves,\n\t\t\tstrokeRadius = hitStroke\n\t\t\t\t\t? style.getStrokeWidth() / 2\n\t\t\t\t\t: hitFill && options.tolerance > 0 || hitCurves\n\t\t\t\t\t\t? 0 : null;\n\t\tif (strokeRadius !== null) {\n\t\t\tif (strokeRadius > 0) {\n\t\t\t\tjoin = style.getStrokeJoin();\n\t\t\t\tcap = style.getStrokeCap();\n\t\t\t\tmiterLimit = style.getMiterLimit();\n\t\t\t\tstrokePadding = strokePadding.add(\n\t\t\t\t\tPath._getStrokePadding(strokeRadius, strokeMatrix));\n\t\t\t} else {\n\t\t\t\tjoin = cap = 'round';\n\t\t\t}\n\t\t}\n\n\t\tfunction isCloseEnough(pt, padding) {\n\t\t\treturn point.subtract(pt).divide(padding).length <= 1;\n\t\t}\n\n\t\tfunction checkSegmentPoint(seg, pt, name) {\n\t\t\tif (!options.selected || pt.isSelected()) {\n\t\t\t\tvar anchor = seg._point;\n\t\t\t\tif (pt !== anchor)\n\t\t\t\t\tpt = pt.add(anchor);\n\t\t\t\tif (isCloseEnough(pt, strokePadding)) {\n\t\t\t\t\treturn new HitResult(name, that, {\n\t\t\t\t\t\tsegment: seg,\n\t\t\t\t\t\tpoint: pt\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction checkSegmentPoints(seg, ends) {\n\t\t\treturn (ends || options.segments)\n\t\t\t\t&& checkSegmentPoint(seg, seg._point, 'segment')\n\t\t\t\t|| (!ends && options.handles) && (\n\t\t\t\t\tcheckSegmentPoint(seg, seg._handleIn, 'handle-in') ||\n\t\t\t\t\tcheckSegmentPoint(seg, seg._handleOut, 'handle-out'));\n\t\t}\n\n\t\tfunction addToArea(point) {\n\t\t\tarea.add(point);\n\t\t}\n\n\t\tfunction checkSegmentStroke(segment) {\n\t\t\tvar isJoin = closed || segment._index > 0\n\t\t\t\t\t&& segment._index < numSegments - 1;\n\t\t\tif ((isJoin ? join : cap) === 'round') {\n\t\t\t\treturn isCloseEnough(segment._point, strokePadding);\n\t\t\t} else {\n\t\t\t\tarea = new Path({ internal: true, closed: true });\n\t\t\t\tif (isJoin) {\n\t\t\t\t\tif (!segment.isSmooth()) {\n\t\t\t\t\t\tPath._addBevelJoin(segment, join, strokeRadius,\n\t\t\t\t\t\t\t miterLimit, null, strokeMatrix, addToArea, true);\n\t\t\t\t\t}\n\t\t\t\t} else if (cap === 'square') {\n\t\t\t\t\tPath._addSquareCap(segment, cap, strokeRadius, null,\n\t\t\t\t\t\t\tstrokeMatrix, addToArea, true);\n\t\t\t\t}\n\t\t\t\tif (!area.isEmpty()) {\n\t\t\t\t\tvar loc;\n\t\t\t\t\treturn area.contains(point)\n\t\t\t\t\t\t|| (loc = area.getNearestLocation(point))\n\t\t\t\t\t\t\t&& isCloseEnough(loc.getPoint(), tolerancePadding);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (options.ends && !options.segments && !closed) {\n\t\t\tif (res = checkSegmentPoints(segments[0], true)\n\t\t\t\t\t|| checkSegmentPoints(segments[numSegments - 1], true))\n\t\t\t\treturn res;\n\t\t} else if (options.segments || options.handles) {\n\t\t\tfor (var i = 0; i < numSegments; i++)\n\t\t\t\tif (res = checkSegmentPoints(segments[i]))\n\t\t\t\t\treturn res;\n\t\t}\n\t\tif (strokeRadius !== null) {\n\t\t\tloc = this.getNearestLocation(point);\n\t\t\tif (loc) {\n\t\t\t\tvar time = loc.getTime();\n\t\t\t\tif (time === 0 || time === 1 && numSegments > 1) {\n\t\t\t\t\tif (!checkSegmentStroke(loc.getSegment()))\n\t\t\t\t\t\tloc = null;\n\t\t\t\t} else if (!isCloseEnough(loc.getPoint(), strokePadding)) {\n\t\t\t\t\tloc = null;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!loc && join === 'miter' && numSegments > 1) {\n\t\t\t\tfor (var i = 0; i < numSegments; i++) {\n\t\t\t\t\tvar segment = segments[i];\n\t\t\t\t\tif (point.getDistance(segment._point)\n\t\t\t\t\t\t\t<= miterLimit * strokeRadius\n\t\t\t\t\t\t\t&& checkSegmentStroke(segment)) {\n\t\t\t\t\t\tloc = segment.getLocation();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn !loc && hitFill && this._contains(point)\n\t\t\t\t|| loc && !hitStroke && !hitCurves\n\t\t\t\t\t? new HitResult('fill', this)\n\t\t\t\t\t: loc\n\t\t\t\t\t\t? new HitResult(hitStroke ? 'stroke' : 'curve', this, {\n\t\t\t\t\t\t\tlocation: loc,\n\t\t\t\t\t\t\tpoint: loc.getPoint()\n\t\t\t\t\t\t})\n\t\t\t\t\t\t: null;\n\t}\n\n}, Base.each(Curve._evaluateMethods,\n\tfunction(name) {\n\t\tthis[name + 'At'] = function(offset) {\n\t\t\tvar loc = this.getLocationAt(offset);\n\t\t\treturn loc && loc[name]();\n\t\t};\n\t},\n{\n\tbeans: false,\n\n\tgetLocationOf: function() {\n\t\tvar point = Point.read(arguments),\n\t\t\tcurves = this.getCurves();\n\t\tfor (var i = 0, l = curves.length; i < l; i++) {\n\t\t\tvar loc = curves[i].getLocationOf(point);\n\t\t\tif (loc)\n\t\t\t\treturn loc;\n\t\t}\n\t\treturn null;\n\t},\n\n\tgetOffsetOf: function() {\n\t\tvar loc = this.getLocationOf.apply(this, arguments);\n\t\treturn loc ? loc.getOffset() : null;\n\t},\n\n\tgetLocationAt: function(offset) {\n\t\tif (typeof offset === 'number') {\n\t\t\tvar curves = this.getCurves(),\n\t\t\t\tlength = 0;\n\t\t\tfor (var i = 0, l = curves.length; i < l; i++) {\n\t\t\t\tvar start = length,\n\t\t\t\t\tcurve = curves[i];\n\t\t\t\tlength += curve.getLength();\n\t\t\t\tif (length > offset) {\n\t\t\t\t\treturn curve.getLocationAt(offset - start);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (curves.length > 0 && offset <= this.getLength()) {\n\t\t\t\treturn new CurveLocation(curves[curves.length - 1], 1);\n\t\t\t}\n\t\t} else if (offset && offset.getPath && offset.getPath() === this) {\n\t\t\treturn offset;\n\t\t}\n\t\treturn null;\n\t},\n\n\tgetOffsetsWithTangent: function() {\n\t\tvar tangent = Point.read(arguments);\n\t\tif (tangent.isZero()) {\n\t\t\treturn [];\n\t\t}\n\n\t\tvar offsets = [];\n\t\tvar curveStart = 0;\n\t\tvar curves = this.getCurves();\n\t\tfor (var i = 0, l = curves.length; i < l; i++) {\n\t\t\tvar curve = curves[i];\n\t\t\tvar curveTimes = curve.getTimesWithTangent(tangent);\n\t\t\tfor (var j = 0, m = curveTimes.length; j < m; j++) {\n\t\t\t\tvar offset = curveStart + curve.getOffsetAtTime(curveTimes[j]);\n\t\t\t\tif (offsets.indexOf(offset) < 0) {\n\t\t\t\t\toffsets.push(offset);\n\t\t\t\t}\n\t\t\t}\n\t\t\tcurveStart += curve.length;\n\t\t}\n\t\treturn offsets;\n\t}\n}),\nnew function() {\n\n\tfunction drawHandles(ctx, segments, matrix, size, isFullySelected) {\n\t\tif (size <= 0) return;\n\n\t\tvar half = size / 2,\n\t\t\tcoords = new Array(6),\n\t\t\tpX, pY;\n\n\t\tfunction drawHandle(index) {\n\t\t\tvar hX = coords[index],\n\t\t\t\thY = coords[index + 1];\n\t\t\tif (pX != hX || pY != hY) {\n\t\t\t\tctx.beginPath();\n\t\t\t\tctx.moveTo(pX, pY);\n\t\t\t\tctx.lineTo(hX, hY);\n\t\t\t\tctx.moveTo(hX - half, hY);\n\t\t\t\tctx.lineTo(hX, hY + half);\n\t\t\t\tctx.lineTo(hX + half, hY);\n\t\t\t\tctx.lineTo(hX, hY - half);\n\t\t\t\tctx.closePath();\n\t\t\t\tctx.stroke();\n\t\t\t}\n\t\t}\n\n\t\tfor (var i = 0, l = segments.length; i < l; i++) {\n\t\t\tvar segment = segments[i],\n\t\t\t\tselection = segment._selection;\n\t\t\tsegment._transformCoordinates(matrix, coords);\n\t\t\tpX = coords[0];\n\t\t\tpY = coords[1];\n\t\t\tif (selection & 2 && !isFullySelected)\n\t\t\t\tdrawHandle(2);\n\t\t\tif (selection & 4 && !isFullySelected)\n\t\t\t\tdrawHandle(4);\n\t\t\tctx.beginPath();\n\t\t\tctx.arc(pX, pY, half, 0, Math.PI * 2, true);\n\t\t\tctx.stroke();\n\t\t\tvar fillStyle = ctx.fillStyle;\n\t\t\tif (!(selection & 1)) {\n\t\t\t\tctx.fillStyle = 'rgba(255, 255, 255, 0.5)';\n\t\t\t}\n\t\t\tctx.fill();\n\t\t\tctx.fillStyle = fillStyle;\n\t\t}\n\t}\n\n\tfunction drawSegments(ctx, path, matrix) {\n\t\tvar segments = path._segments,\n\t\t\tlength = segments.length,\n\t\t\tcoords = new Array(6),\n\t\t\tfirst = true,\n\t\t\tcurX, curY,\n\t\t\tprevX, prevY,\n\t\t\tinX, inY,\n\t\t\toutX, outY;\n\n\t\tfunction drawSegment(segment) {\n\t\t\tif (matrix) {\n\t\t\t\tsegment._transformCoordinates(matrix, coords);\n\t\t\t\tcurX = coords[0];\n\t\t\t\tcurY = coords[1];\n\t\t\t} else {\n\t\t\t\tvar point = segment._point;\n\t\t\t\tcurX = point._x;\n\t\t\t\tcurY = point._y;\n\t\t\t}\n\t\t\tif (first) {\n\t\t\t\tctx.moveTo(curX, curY);\n\t\t\t\tfirst = false;\n\t\t\t} else {\n\t\t\t\tif (matrix) {\n\t\t\t\t\tinX = coords[2];\n\t\t\t\t\tinY = coords[3];\n\t\t\t\t} else {\n\t\t\t\t\tvar handle = segment._handleIn;\n\t\t\t\t\tinX = curX + handle._x;\n\t\t\t\t\tinY = curY + handle._y;\n\t\t\t\t}\n\t\t\t\tif (inX === curX && inY === curY\n\t\t\t\t\t\t&& outX === prevX && outY === prevY) {\n\t\t\t\t\tctx.lineTo(curX, curY);\n\t\t\t\t} else {\n\t\t\t\t\tctx.bezierCurveTo(outX, outY, inX, inY, curX, curY);\n\t\t\t\t}\n\t\t\t}\n\t\t\tprevX = curX;\n\t\t\tprevY = curY;\n\t\t\tif (matrix) {\n\t\t\t\toutX = coords[4];\n\t\t\t\toutY = coords[5];\n\t\t\t} else {\n\t\t\t\tvar handle = segment._handleOut;\n\t\t\t\toutX = prevX + handle._x;\n\t\t\t\toutY = prevY + handle._y;\n\t\t\t}\n\t\t}\n\n\t\tfor (var i = 0; i < length; i++)\n\t\t\tdrawSegment(segments[i]);\n\t\tif (path._closed && length > 0)\n\t\t\tdrawSegment(segments[0]);\n\t}\n\n\treturn {\n\t\t_draw: function(ctx, param, viewMatrix, strokeMatrix) {\n\t\t\tvar dontStart = param.dontStart,\n\t\t\t\tdontPaint = param.dontFinish || param.clip,\n\t\t\t\tstyle = this.getStyle(),\n\t\t\t\thasFill = style.hasFill(),\n\t\t\t\thasStroke = style.hasStroke(),\n\t\t\t\tdashArray = style.getDashArray(),\n\t\t\t\tdashLength = !paper.support.nativeDash && hasStroke\n\t\t\t\t\t\t&& dashArray && dashArray.length;\n\n\t\t\tif (!dontStart)\n\t\t\t\tctx.beginPath();\n\n\t\t\tif (hasFill || hasStroke && !dashLength || dontPaint) {\n\t\t\t\tdrawSegments(ctx, this, strokeMatrix);\n\t\t\t\tif (this._closed)\n\t\t\t\t\tctx.closePath();\n\t\t\t}\n\n\t\t\tfunction getOffset(i) {\n\t\t\t\treturn dashArray[((i % dashLength) + dashLength) % dashLength];\n\t\t\t}\n\n\t\t\tif (!dontPaint && (hasFill || hasStroke)) {\n\t\t\t\tthis._setStyles(ctx, param, viewMatrix, strokeMatrix);\n\t\t\t\tif (hasFill) {\n\t\t\t\t\tctx.fill(style.getFillRule());\n\t\t\t\t\tctx.shadowColor = 'rgba(0,0,0,0)';\n\t\t\t\t}\n\t\t\t\tif (hasStroke) {\n\t\t\t\t\tif (dashLength) {\n\t\t\t\t\t\tif (!dontStart)\n\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tvar flattener = new PathFlattener(this, 0.25, 32, false,\n\t\t\t\t\t\t\t\tstrokeMatrix),\n\t\t\t\t\t\t\tlength = flattener.length,\n\t\t\t\t\t\t\tfrom = -style.getDashOffset(), to,\n\t\t\t\t\t\t\ti = 0;\n\t\t\t\t\t\tfrom = from % length;\n\t\t\t\t\t\twhile (from > 0) {\n\t\t\t\t\t\t\tfrom -= getOffset(i--) + getOffset(i--);\n\t\t\t\t\t\t}\n\t\t\t\t\t\twhile (from < length) {\n\t\t\t\t\t\t\tto = from + getOffset(i++);\n\t\t\t\t\t\t\tif (from > 0 || to > 0)\n\t\t\t\t\t\t\t\tflattener.drawPart(ctx,\n\t\t\t\t\t\t\t\t\t\tMath.max(from, 0), Math.max(to, 0));\n\t\t\t\t\t\t\tfrom = to + getOffset(i++);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tctx.stroke();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t_drawSelected: function(ctx, matrix) {\n\t\t\tctx.beginPath();\n\t\t\tdrawSegments(ctx, this, matrix);\n\t\t\tctx.stroke();\n\t\t\tdrawHandles(ctx, this._segments, matrix, paper.settings.handleSize,\n\t\t\t\tthis.isFullySelected());\n\t\t}\n\t};\n},\nnew function() {\n\tfunction getCurrentSegment(that) {\n\t\tvar segments = that._segments;\n\t\tif (!segments.length)\n\t\t\tthrow new Error('Use a moveTo() command first');\n\t\treturn segments[segments.length - 1];\n\t}\n\n\treturn {\n\t\tmoveTo: function() {\n\t\t\tvar segments = this._segments;\n\t\t\tif (segments.length === 1)\n\t\t\t\tthis.removeSegment(0);\n\t\t\tif (!segments.length)\n\t\t\t\tthis._add([ new Segment(Point.read(arguments)) ]);\n\t\t},\n\n\t\tmoveBy: function() {\n\t\t\tthrow new Error('moveBy() is unsupported on Path items.');\n\t\t},\n\n\t\tlineTo: function() {\n\t\t\tthis._add([ new Segment(Point.read(arguments)) ]);\n\t\t},\n\n\t\tcubicCurveTo: function() {\n\t\t\tvar args = arguments,\n\t\t\t\thandle1 = Point.read(args),\n\t\t\t\thandle2 = Point.read(args),\n\t\t\t\tto = Point.read(args),\n\t\t\t\tcurrent = getCurrentSegment(this);\n\t\t\tcurrent.setHandleOut(handle1.subtract(current._point));\n\t\t\tthis._add([ new Segment(to, handle2.subtract(to)) ]);\n\t\t},\n\n\t\tquadraticCurveTo: function() {\n\t\t\tvar args = arguments,\n\t\t\t\thandle = Point.read(args),\n\t\t\t\tto = Point.read(args),\n\t\t\t\tcurrent = getCurrentSegment(this)._point;\n\t\t\tthis.cubicCurveTo(\n\t\t\t\thandle.add(current.subtract(handle).multiply(1 / 3)),\n\t\t\t\thandle.add(to.subtract(handle).multiply(1 / 3)),\n\t\t\t\tto\n\t\t\t);\n\t\t},\n\n\t\tcurveTo: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tthrough = Point.read(args),\n\t\t\t\tto = Point.read(args),\n\t\t\t\tt = Base.pick(Base.read(args), 0.5),\n\t\t\t\tt1 = 1 - t,\n\t\t\t\tcurrent = getCurrentSegment(this)._point,\n\t\t\t\thandle = through.subtract(current.multiply(t1 * t1))\n\t\t\t\t\t.subtract(to.multiply(t * t)).divide(2 * t * t1);\n\t\t\tif (handle.isNaN())\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'Cannot put a curve through points with parameter = ' + t);\n\t\t\tthis.quadraticCurveTo(handle, to);\n\t\t},\n\n\t\tarcTo: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tabs = Math.abs,\n\t\t\t\tsqrt = Math.sqrt,\n\t\t\t\tcurrent = getCurrentSegment(this),\n\t\t\t\tfrom = current._point,\n\t\t\t\tto = Point.read(args),\n\t\t\t\tthrough,\n\t\t\t\tpeek = Base.peek(args),\n\t\t\t\tclockwise = Base.pick(peek, true),\n\t\t\t\tcenter, extent, vector, matrix;\n\t\t\tif (typeof clockwise === 'boolean') {\n\t\t\t\tvar middle = from.add(to).divide(2),\n\t\t\t\tthrough = middle.add(middle.subtract(from).rotate(\n\t\t\t\t\t\tclockwise ? -90 : 90));\n\t\t\t} else if (Base.remain(args) <= 2) {\n\t\t\t\tthrough = to;\n\t\t\t\tto = Point.read(args);\n\t\t\t} else if (!from.equals(to)) {\n\t\t\t\tvar radius = Size.read(args),\n\t\t\t\t\tisZero = Numerical.isZero;\n\t\t\t\tif (isZero(radius.width) || isZero(radius.height))\n\t\t\t\t\treturn this.lineTo(to);\n\t\t\t\tvar rotation = Base.read(args),\n\t\t\t\t\tclockwise = !!Base.read(args),\n\t\t\t\t\tlarge = !!Base.read(args),\n\t\t\t\t\tmiddle = from.add(to).divide(2),\n\t\t\t\t\tpt = from.subtract(middle).rotate(-rotation),\n\t\t\t\t\tx = pt.x,\n\t\t\t\t\ty = pt.y,\n\t\t\t\t\trx = abs(radius.width),\n\t\t\t\t\try = abs(radius.height),\n\t\t\t\t\trxSq = rx * rx,\n\t\t\t\t\trySq = ry * ry,\n\t\t\t\t\txSq = x * x,\n\t\t\t\t\tySq = y * y;\n\t\t\t\tvar factor = sqrt(xSq / rxSq + ySq / rySq);\n\t\t\t\tif (factor > 1) {\n\t\t\t\t\trx *= factor;\n\t\t\t\t\try *= factor;\n\t\t\t\t\trxSq = rx * rx;\n\t\t\t\t\trySq = ry * ry;\n\t\t\t\t}\n\t\t\t\tfactor = (rxSq * rySq - rxSq * ySq - rySq * xSq) /\n\t\t\t\t\t\t(rxSq * ySq + rySq * xSq);\n\t\t\t\tif (abs(factor) < 1e-12)\n\t\t\t\t\tfactor = 0;\n\t\t\t\tif (factor < 0)\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'Cannot create an arc with the given arguments');\n\t\t\t\tcenter = new Point(rx * y / ry, -ry * x / rx)\n\t\t\t\t\t\t.multiply((large === clockwise ? -1 : 1) * sqrt(factor))\n\t\t\t\t\t\t.rotate(rotation).add(middle);\n\t\t\t\tmatrix = new Matrix().translate(center).rotate(rotation)\n\t\t\t\t\t\t.scale(rx, ry);\n\t\t\t\tvector = matrix._inverseTransform(from);\n\t\t\t\textent = vector.getDirectedAngle(matrix._inverseTransform(to));\n\t\t\t\tif (!clockwise && extent > 0)\n\t\t\t\t\textent -= 360;\n\t\t\t\telse if (clockwise && extent < 0)\n\t\t\t\t\textent += 360;\n\t\t\t}\n\t\t\tif (through) {\n\t\t\t\tvar l1 = new Line(from.add(through).divide(2),\n\t\t\t\t\t\t\tthrough.subtract(from).rotate(90), true),\n\t\t\t\t\tl2 = new Line(through.add(to).divide(2),\n\t\t\t\t\t\t\tto.subtract(through).rotate(90), true),\n\t\t\t\t\tline = new Line(from, to),\n\t\t\t\t\tthroughSide = line.getSide(through);\n\t\t\t\tcenter = l1.intersect(l2, true);\n\t\t\t\tif (!center) {\n\t\t\t\t\tif (!throughSide)\n\t\t\t\t\t\treturn this.lineTo(to);\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'Cannot create an arc with the given arguments');\n\t\t\t\t}\n\t\t\t\tvector = from.subtract(center);\n\t\t\t\textent = vector.getDirectedAngle(to.subtract(center));\n\t\t\t\tvar centerSide = line.getSide(center, true);\n\t\t\t\tif (centerSide === 0) {\n\t\t\t\t\textent = throughSide * abs(extent);\n\t\t\t\t} else if (throughSide === centerSide) {\n\t\t\t\t\textent += extent < 0 ? 360 : -360;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (extent) {\n\t\t\t\tvar epsilon = 1e-7,\n\t\t\t\t\text = abs(extent),\n\t\t\t\t\tcount = ext >= 360 ? 4 : Math.ceil((ext - epsilon) / 90),\n\t\t\t\t\tinc = extent / count,\n\t\t\t\t\thalf = inc * Math.PI / 360,\n\t\t\t\t\tz = 4 / 3 * Math.sin(half) / (1 + Math.cos(half)),\n\t\t\t\t\tsegments = [];\n\t\t\t\tfor (var i = 0; i <= count; i++) {\n\t\t\t\t\tvar pt = to,\n\t\t\t\t\t\tout = null;\n\t\t\t\t\tif (i < count) {\n\t\t\t\t\t\tout = vector.rotate(90).multiply(z);\n\t\t\t\t\t\tif (matrix) {\n\t\t\t\t\t\t\tpt = matrix._transformPoint(vector);\n\t\t\t\t\t\t\tout = matrix._transformPoint(vector.add(out))\n\t\t\t\t\t\t\t\t\t.subtract(pt);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpt = center.add(vector);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!i) {\n\t\t\t\t\t\tcurrent.setHandleOut(out);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar _in = vector.rotate(-90).multiply(z);\n\t\t\t\t\t\tif (matrix) {\n\t\t\t\t\t\t\t_in = matrix._transformPoint(vector.add(_in))\n\t\t\t\t\t\t\t\t\t.subtract(pt);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsegments.push(new Segment(pt, _in, out));\n\t\t\t\t\t}\n\t\t\t\t\tvector = vector.rotate(inc);\n\t\t\t\t}\n\t\t\t\tthis._add(segments);\n\t\t\t}\n\t\t},\n\n\t\tlineBy: function() {\n\t\t\tvar to = Point.read(arguments),\n\t\t\t\tcurrent = getCurrentSegment(this)._point;\n\t\t\tthis.lineTo(current.add(to));\n\t\t},\n\n\t\tcurveBy: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tthrough = Point.read(args),\n\t\t\t\tto = Point.read(args),\n\t\t\t\tparameter = Base.read(args),\n\t\t\t\tcurrent = getCurrentSegment(this)._point;\n\t\t\tthis.curveTo(current.add(through), current.add(to), parameter);\n\t\t},\n\n\t\tcubicCurveBy: function() {\n\t\t\tvar args = arguments,\n\t\t\t\thandle1 = Point.read(args),\n\t\t\t\thandle2 = Point.read(args),\n\t\t\t\tto = Point.read(args),\n\t\t\t\tcurrent = getCurrentSegment(this)._point;\n\t\t\tthis.cubicCurveTo(current.add(handle1), current.add(handle2),\n\t\t\t\t\tcurrent.add(to));\n\t\t},\n\n\t\tquadraticCurveBy: function() {\n\t\t\tvar args = arguments,\n\t\t\t\thandle = Point.read(args),\n\t\t\t\tto = Point.read(args),\n\t\t\t\tcurrent = getCurrentSegment(this)._point;\n\t\t\tthis.quadraticCurveTo(current.add(handle), current.add(to));\n\t\t},\n\n\t\tarcBy: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tcurrent = getCurrentSegment(this)._point,\n\t\t\t\tpoint = current.add(Point.read(args)),\n\t\t\t\tclockwise = Base.pick(Base.peek(args), true);\n\t\t\tif (typeof clockwise === 'boolean') {\n\t\t\t\tthis.arcTo(point, clockwise);\n\t\t\t} else {\n\t\t\t\tthis.arcTo(point, current.add(Point.read(args)));\n\t\t\t}\n\t\t},\n\n\t\tclosePath: function(tolerance) {\n\t\t\tthis.setClosed(true);\n\t\t\tthis.join(this, tolerance);\n\t\t}\n\t};\n}, {\n\n\t_getBounds: function(matrix, options) {\n\t\tvar method = options.handle\n\t\t\t\t? 'getHandleBounds'\n\t\t\t\t: options.stroke\n\t\t\t\t? 'getStrokeBounds'\n\t\t\t\t: 'getBounds';\n\t\treturn Path[method](this._segments, this._closed, this, matrix, options);\n\t},\n\nstatics: {\n\tgetBounds: function(segments, closed, path, matrix, options, strokePadding) {\n\t\tvar first = segments[0];\n\t\tif (!first)\n\t\t\treturn new Rectangle();\n\t\tvar coords = new Array(6),\n\t\t\tprevCoords = first._transformCoordinates(matrix, new Array(6)),\n\t\t\tmin = prevCoords.slice(0, 2),\n\t\t\tmax = min.slice(),\n\t\t\troots = new Array(2);\n\n\t\tfunction processSegment(segment) {\n\t\t\tsegment._transformCoordinates(matrix, coords);\n\t\t\tfor (var i = 0; i < 2; i++) {\n\t\t\t\tCurve._addBounds(\n\t\t\t\t\tprevCoords[i],\n\t\t\t\t\tprevCoords[i + 4],\n\t\t\t\t\tcoords[i + 2],\n\t\t\t\t\tcoords[i],\n\t\t\t\t\ti, strokePadding ? strokePadding[i] : 0, min, max, roots);\n\t\t\t}\n\t\t\tvar tmp = prevCoords;\n\t\t\tprevCoords = coords;\n\t\t\tcoords = tmp;\n\t\t}\n\n\t\tfor (var i = 1, l = segments.length; i < l; i++)\n\t\t\tprocessSegment(segments[i]);\n\t\tif (closed)\n\t\t\tprocessSegment(first);\n\t\treturn new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);\n\t},\n\n\tgetStrokeBounds: function(segments, closed, path, matrix, options) {\n\t\tvar style = path.getStyle(),\n\t\t\tstroke = style.hasStroke(),\n\t\t\tstrokeWidth = style.getStrokeWidth(),\n\t\t\tstrokeMatrix = stroke && path._getStrokeMatrix(matrix, options),\n\t\t\tstrokePadding = stroke && Path._getStrokePadding(strokeWidth,\n\t\t\t\tstrokeMatrix),\n\t\t\tbounds = Path.getBounds(segments, closed, path, matrix, options,\n\t\t\t\tstrokePadding);\n\t\tif (!stroke)\n\t\t\treturn bounds;\n\t\tvar strokeRadius = strokeWidth / 2,\n\t\t\tjoin = style.getStrokeJoin(),\n\t\t\tcap = style.getStrokeCap(),\n\t\t\tmiterLimit = style.getMiterLimit(),\n\t\t\tjoinBounds = new Rectangle(new Size(strokePadding));\n\n\t\tfunction addPoint(point) {\n\t\t\tbounds = bounds.include(point);\n\t\t}\n\n\t\tfunction addRound(segment) {\n\t\t\tbounds = bounds.unite(\n\t\t\t\t\tjoinBounds.setCenter(segment._point.transform(matrix)));\n\t\t}\n\n\t\tfunction addJoin(segment, join) {\n\t\t\tif (join === 'round' || segment.isSmooth()) {\n\t\t\t\taddRound(segment);\n\t\t\t} else {\n\t\t\t\tPath._addBevelJoin(segment, join, strokeRadius, miterLimit,\n\t\t\t\t\t\tmatrix, strokeMatrix, addPoint);\n\t\t\t}\n\t\t}\n\n\t\tfunction addCap(segment, cap) {\n\t\t\tif (cap === 'round') {\n\t\t\t\taddRound(segment);\n\t\t\t} else {\n\t\t\t\tPath._addSquareCap(segment, cap, strokeRadius, matrix,\n\t\t\t\t\t\tstrokeMatrix, addPoint);\n\t\t\t}\n\t\t}\n\n\t\tvar length = segments.length - (closed ? 0 : 1);\n\t\tif (length > 0) {\n\t\t\tfor (var i = 1; i < length; i++) {\n\t\t\t\taddJoin(segments[i], join);\n\t\t\t}\n\t\t\tif (closed) {\n\t\t\t\taddJoin(segments[0], join);\n\t\t\t} else {\n\t\t\t\taddCap(segments[0], cap);\n\t\t\t\taddCap(segments[segments.length - 1], cap);\n\t\t\t}\n\t\t}\n\t\treturn bounds;\n\t},\n\n\t_getStrokePadding: function(radius, matrix) {\n\t\tif (!matrix)\n\t\t\treturn [radius, radius];\n\t\tvar hor = new Point(radius, 0).transform(matrix),\n\t\t\tver = new Point(0, radius).transform(matrix),\n\t\t\tphi = hor.getAngleInRadians(),\n\t\t\ta = hor.getLength(),\n\t\t\tb = ver.getLength();\n\t\tvar sin = Math.sin(phi),\n\t\t\tcos = Math.cos(phi),\n\t\t\ttan = Math.tan(phi),\n\t\t\ttx = Math.atan2(b * tan, a),\n\t\t\tty = Math.atan2(b, tan * a);\n\t\treturn [Math.abs(a * Math.cos(tx) * cos + b * Math.sin(tx) * sin),\n\t\t\t\tMath.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];\n\t},\n\n\t_addBevelJoin: function(segment, join, radius, miterLimit, matrix,\n\t\t\tstrokeMatrix, addPoint, isArea) {\n\t\tvar curve2 = segment.getCurve(),\n\t\t\tcurve1 = curve2.getPrevious(),\n\t\t\tpoint = curve2.getPoint1().transform(matrix),\n\t\t\tnormal1 = curve1.getNormalAtTime(1).multiply(radius)\n\t\t\t\t.transform(strokeMatrix),\n\t\t\tnormal2 = curve2.getNormalAtTime(0).multiply(radius)\n\t\t\t\t.transform(strokeMatrix),\n\t\t\t\tangle = normal1.getDirectedAngle(normal2);\n\t\tif (angle < 0 || angle >= 180) {\n\t\t\tnormal1 = normal1.negate();\n\t\t\tnormal2 = normal2.negate();\n\t\t}\n\t\tif (isArea)\n\t\t\taddPoint(point);\n\t\taddPoint(point.add(normal1));\n\t\tif (join === 'miter') {\n\t\t\tvar corner = new Line(point.add(normal1),\n\t\t\t\t\tnew Point(-normal1.y, normal1.x), true\n\t\t\t\t).intersect(new Line(point.add(normal2),\n\t\t\t\t\tnew Point(-normal2.y, normal2.x), true\n\t\t\t\t), true);\n\t\t\tif (corner && point.getDistance(corner) <= miterLimit * radius) {\n\t\t\t\taddPoint(corner);\n\t\t\t}\n\t\t}\n\t\taddPoint(point.add(normal2));\n\t},\n\n\t_addSquareCap: function(segment, cap, radius, matrix, strokeMatrix,\n\t\t\taddPoint, isArea) {\n\t\tvar point = segment._point.transform(matrix),\n\t\t\tloc = segment.getLocation(),\n\t\t\tnormal = loc.getNormal()\n\t\t\t\t\t.multiply(loc.getTime() === 0 ? radius : -radius)\n\t\t\t\t\t.transform(strokeMatrix);\n\t\tif (cap === 'square') {\n\t\t\tif (isArea) {\n\t\t\t\taddPoint(point.subtract(normal));\n\t\t\t\taddPoint(point.add(normal));\n\t\t\t}\n\t\t\tpoint = point.add(normal.rotate(-90));\n\t\t}\n\t\taddPoint(point.add(normal));\n\t\taddPoint(point.subtract(normal));\n\t},\n\n\tgetHandleBounds: function(segments, closed, path, matrix, options) {\n\t\tvar style = path.getStyle(),\n\t\t\tstroke = options.stroke && style.hasStroke(),\n\t\t\tstrokePadding,\n\t\t\tjoinPadding;\n\t\tif (stroke) {\n\t\t\tvar strokeMatrix = path._getStrokeMatrix(matrix, options),\n\t\t\t\tstrokeRadius = style.getStrokeWidth() / 2,\n\t\t\t\tjoinRadius = strokeRadius;\n\t\t\tif (style.getStrokeJoin() === 'miter')\n\t\t\t\tjoinRadius = strokeRadius * style.getMiterLimit();\n\t\t\tif (style.getStrokeCap() === 'square')\n\t\t\t\tjoinRadius = Math.max(joinRadius, strokeRadius * Math.SQRT2);\n\t\t\tstrokePadding = Path._getStrokePadding(strokeRadius, strokeMatrix);\n\t\t\tjoinPadding = Path._getStrokePadding(joinRadius, strokeMatrix);\n\t\t}\n\t\tvar coords = new Array(6),\n\t\t\tx1 = Infinity,\n\t\t\tx2 = -x1,\n\t\t\ty1 = x1,\n\t\t\ty2 = x2;\n\t\tfor (var i = 0, l = segments.length; i < l; i++) {\n\t\t\tvar segment = segments[i];\n\t\t\tsegment._transformCoordinates(matrix, coords);\n\t\t\tfor (var j = 0; j < 6; j += 2) {\n\t\t\t\tvar padding = !j ? joinPadding : strokePadding,\n\t\t\t\t\tpaddingX = padding ? padding[0] : 0,\n\t\t\t\t\tpaddingY = padding ? padding[1] : 0,\n\t\t\t\t\tx = coords[j],\n\t\t\t\t\ty = coords[j + 1],\n\t\t\t\t\txn = x - paddingX,\n\t\t\t\t\txx = x + paddingX,\n\t\t\t\t\tyn = y - paddingY,\n\t\t\t\t\tyx = y + paddingY;\n\t\t\t\tif (xn < x1) x1 = xn;\n\t\t\t\tif (xx > x2) x2 = xx;\n\t\t\t\tif (yn < y1) y1 = yn;\n\t\t\t\tif (yx > y2) y2 = yx;\n\t\t\t}\n\t\t}\n\t\treturn new Rectangle(x1, y1, x2 - x1, y2 - y1);\n\t}\n}});\n\nPath.inject({ statics: new function() {\n\n\tvar kappa = 0.5522847498307936,\n\t\tellipseSegments = [\n\t\t\tnew Segment([-1, 0], [0, kappa ], [0, -kappa]),\n\t\t\tnew Segment([0, -1], [-kappa, 0], [kappa, 0 ]),\n\t\t\tnew Segment([1, 0], [0, -kappa], [0, kappa ]),\n\t\t\tnew Segment([0, 1], [kappa, 0 ], [-kappa, 0])\n\t\t];\n\n\tfunction createPath(segments, closed, args) {\n\t\tvar props = Base.getNamed(args),\n\t\t\tpath = new Path(props && props.insert == false && Item.NO_INSERT);\n\t\tpath._add(segments);\n\t\tpath._closed = closed;\n\t\treturn path.set(props, { insert: true });\n\t}\n\n\tfunction createEllipse(center, radius, args) {\n\t\tvar segments = new Array(4);\n\t\tfor (var i = 0; i < 4; i++) {\n\t\t\tvar segment = ellipseSegments[i];\n\t\t\tsegments[i] = new Segment(\n\t\t\t\tsegment._point.multiply(radius).add(center),\n\t\t\t\tsegment._handleIn.multiply(radius),\n\t\t\t\tsegment._handleOut.multiply(radius)\n\t\t\t);\n\t\t}\n\t\treturn createPath(segments, true, args);\n\t}\n\n\treturn {\n\t\tLine: function() {\n\t\t\tvar args = arguments;\n\t\t\treturn createPath([\n\t\t\t\tnew Segment(Point.readNamed(args, 'from')),\n\t\t\t\tnew Segment(Point.readNamed(args, 'to'))\n\t\t\t], false, args);\n\t\t},\n\n\t\tCircle: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tcenter = Point.readNamed(args, 'center'),\n\t\t\t\tradius = Base.readNamed(args, 'radius');\n\t\t\treturn createEllipse(center, new Size(radius), args);\n\t\t},\n\n\t\tRectangle: function() {\n\t\t\tvar args = arguments,\n\t\t\t\trect = Rectangle.readNamed(args, 'rectangle'),\n\t\t\t\tradius = Size.readNamed(args, 'radius', 0,\n\t\t\t\t\t\t{ readNull: true }),\n\t\t\t\tbl = rect.getBottomLeft(true),\n\t\t\t\ttl = rect.getTopLeft(true),\n\t\t\t\ttr = rect.getTopRight(true),\n\t\t\t\tbr = rect.getBottomRight(true),\n\t\t\t\tsegments;\n\t\t\tif (!radius || radius.isZero()) {\n\t\t\t\tsegments = [\n\t\t\t\t\tnew Segment(bl),\n\t\t\t\t\tnew Segment(tl),\n\t\t\t\t\tnew Segment(tr),\n\t\t\t\t\tnew Segment(br)\n\t\t\t\t];\n\t\t\t} else {\n\t\t\t\tradius = Size.min(radius, rect.getSize(true).divide(2));\n\t\t\t\tvar rx = radius.width,\n\t\t\t\t\try = radius.height,\n\t\t\t\t\thx = rx * kappa,\n\t\t\t\t\thy = ry * kappa;\n\t\t\t\tsegments = [\n\t\t\t\t\tnew Segment(bl.add(rx, 0), null, [-hx, 0]),\n\t\t\t\t\tnew Segment(bl.subtract(0, ry), [0, hy]),\n\t\t\t\t\tnew Segment(tl.add(0, ry), null, [0, -hy]),\n\t\t\t\t\tnew Segment(tl.add(rx, 0), [-hx, 0], null),\n\t\t\t\t\tnew Segment(tr.subtract(rx, 0), null, [hx, 0]),\n\t\t\t\t\tnew Segment(tr.add(0, ry), [0, -hy], null),\n\t\t\t\t\tnew Segment(br.subtract(0, ry), null, [0, hy]),\n\t\t\t\t\tnew Segment(br.subtract(rx, 0), [hx, 0])\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn createPath(segments, true, args);\n\t\t},\n\n\t\tRoundRectangle: '#Rectangle',\n\n\t\tEllipse: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tellipse = Shape._readEllipse(args);\n\t\t\treturn createEllipse(ellipse.center, ellipse.radius, args);\n\t\t},\n\n\t\tOval: '#Ellipse',\n\n\t\tArc: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tfrom = Point.readNamed(args, 'from'),\n\t\t\t\tthrough = Point.readNamed(args, 'through'),\n\t\t\t\tto = Point.readNamed(args, 'to'),\n\t\t\t\tprops = Base.getNamed(args),\n\t\t\t\tpath = new Path(props && props.insert == false\n\t\t\t\t\t\t&& Item.NO_INSERT);\n\t\t\tpath.moveTo(from);\n\t\t\tpath.arcTo(through, to);\n\t\t\treturn path.set(props);\n\t\t},\n\n\t\tRegularPolygon: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tcenter = Point.readNamed(args, 'center'),\n\t\t\t\tsides = Base.readNamed(args, 'sides'),\n\t\t\t\tradius = Base.readNamed(args, 'radius'),\n\t\t\t\tstep = 360 / sides,\n\t\t\t\tthree = sides % 3 === 0,\n\t\t\t\tvector = new Point(0, three ? -radius : radius),\n\t\t\t\toffset = three ? -1 : 0.5,\n\t\t\t\tsegments = new Array(sides);\n\t\t\tfor (var i = 0; i < sides; i++)\n\t\t\t\tsegments[i] = new Segment(center.add(\n\t\t\t\t\tvector.rotate((i + offset) * step)));\n\t\t\treturn createPath(segments, true, args);\n\t\t},\n\n\t\tStar: function() {\n\t\t\tvar args = arguments,\n\t\t\t\tcenter = Point.readNamed(args, 'center'),\n\t\t\t\tpoints = Base.readNamed(args, 'points') * 2,\n\t\t\t\tradius1 = Base.readNamed(args, 'radius1'),\n\t\t\t\tradius2 = Base.readNamed(args, 'radius2'),\n\t\t\t\tstep = 360 / points,\n\t\t\t\tvector = new Point(0, -1),\n\t\t\t\tsegments = new Array(points);\n\t\t\tfor (var i = 0; i < points; i++)\n\t\t\t\tsegments[i] = new Segment(center.add(vector.rotate(step * i)\n\t\t\t\t\t\t.multiply(i % 2 ? radius2 : radius1)));\n\t\t\treturn createPath(segments, true, args);\n\t\t}\n\t};\n}});\n\nvar CompoundPath = PathItem.extend({\n\t_class: 'CompoundPath',\n\t_serializeFields: {\n\t\tchildren: []\n\t},\n\tbeans: true,\n\n\tinitialize: function CompoundPath(arg) {\n\t\tthis._children = [];\n\t\tthis._namedChildren = {};\n\t\tif (!this._initialize(arg)) {\n\t\t\tif (typeof arg === 'string') {\n\t\t\t\tthis.setPathData(arg);\n\t\t\t} else {\n\t\t\t\tthis.addChildren(Array.isArray(arg) ? arg : arguments);\n\t\t\t}\n\t\t}\n\t},\n\n\tinsertChildren: function insertChildren(index, items) {\n\t\tvar list = items,\n\t\t\tfirst = list[0];\n\t\tif (first && typeof first[0] === 'number')\n\t\t\tlist = [list];\n\t\tfor (var i = items.length - 1; i >= 0; i--) {\n\t\t\tvar item = list[i];\n\t\t\tif (list === items && !(item instanceof Path))\n\t\t\t\tlist = Base.slice(list);\n\t\t\tif (Array.isArray(item)) {\n\t\t\t\tlist[i] = new Path({ segments: item, insert: false });\n\t\t\t} else if (item instanceof CompoundPath) {\n\t\t\t\tlist.splice.apply(list, [i, 1].concat(item.removeChildren()));\n\t\t\t\titem.remove();\n\t\t\t}\n\t\t}\n\t\treturn insertChildren.base.call(this, index, list);\n\t},\n\n\treduce: function reduce(options) {\n\t\tvar children = this._children;\n\t\tfor (var i = children.length - 1; i >= 0; i--) {\n\t\t\tvar path = children[i].reduce(options);\n\t\t\tif (path.isEmpty())\n\t\t\t\tpath.remove();\n\t\t}\n\t\tif (!children.length) {\n\t\t\tvar path = new Path(Item.NO_INSERT);\n\t\t\tpath.copyAttributes(this);\n\t\t\tpath.insertAbove(this);\n\t\t\tthis.remove();\n\t\t\treturn path;\n\t\t}\n\t\treturn reduce.base.call(this);\n\t},\n\n\tisClosed: function() {\n\t\tvar children = this._children;\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tif (!children[i]._closed)\n\t\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\tsetClosed: function(closed) {\n\t\tvar children = this._children;\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tchildren[i].setClosed(closed);\n\t\t}\n\t},\n\n\tgetFirstSegment: function() {\n\t\tvar first = this.getFirstChild();\n\t\treturn first && first.getFirstSegment();\n\t},\n\n\tgetLastSegment: function() {\n\t\tvar last = this.getLastChild();\n\t\treturn last && last.getLastSegment();\n\t},\n\n\tgetCurves: function() {\n\t\tvar children = this._children,\n\t\t\tcurves = [];\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tBase.push(curves, children[i].getCurves());\n\t\t}\n\t\treturn curves;\n\t},\n\n\tgetFirstCurve: function() {\n\t\tvar first = this.getFirstChild();\n\t\treturn first && first.getFirstCurve();\n\t},\n\n\tgetLastCurve: function() {\n\t\tvar last = this.getLastChild();\n\t\treturn last && last.getLastCurve();\n\t},\n\n\tgetArea: function() {\n\t\tvar children = this._children,\n\t\t\tarea = 0;\n\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\tarea += children[i].getArea();\n\t\treturn area;\n\t},\n\n\tgetLength: function() {\n\t\tvar children = this._children,\n\t\t\tlength = 0;\n\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\tlength += children[i].getLength();\n\t\treturn length;\n\t},\n\n\tgetPathData: function(_matrix, _precision) {\n\t\tvar children = this._children,\n\t\t\tpaths = [];\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tvar child = children[i],\n\t\t\t\tmx = child._matrix;\n\t\t\tpaths.push(child.getPathData(_matrix && !mx.isIdentity()\n\t\t\t\t\t? _matrix.appended(mx) : _matrix, _precision));\n\t\t}\n\t\treturn paths.join('');\n\t},\n\n\t_hitTestChildren: function _hitTestChildren(point, options, viewMatrix) {\n\t\treturn _hitTestChildren.base.call(this, point,\n\t\t\t\toptions.class === Path || options.type === 'path' || options.hitUnfilledPaths ? options\n\t\t\t\t\t: Base.set({}, options, { fill: false }),\n\t\t\t\tviewMatrix);\n\t},\n\n\t_draw: function(ctx, param, viewMatrix, strokeMatrix) {\n\t\tvar children = this._children;\n\t\tif (!children.length)\n\t\t\treturn;\n\n\t\tparam = param.extend({ dontStart: true, dontFinish: true });\n\t\tctx.beginPath();\n\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\tchildren[i].draw(ctx, param, strokeMatrix);\n\n\t\tif (!param.clip) {\n\t\t\tthis._setStyles(ctx, param, viewMatrix, strokeMatrix);\n\t\t\tvar style = this._style;\n\t\t\tif (style.hasFill()) {\n\t\t\t\tctx.fill(style.getFillRule());\n\t\t\t\tctx.shadowColor = 'rgba(0,0,0,0)';\n\t\t\t}\n\t\t\tif (style.hasStroke())\n\t\t\t\tctx.stroke();\n\t\t}\n\t},\n\n\t_drawSelected: function(ctx, matrix, selectionItems) {\n\t\tvar children = this._children;\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tvar child = children[i],\n\t\t\t\tmx = child._matrix;\n\t\t\tif (!selectionItems[child._id]) {\n\t\t\t\tchild._drawSelected(ctx, mx.isIdentity() ? matrix\n\t\t\t\t\t\t: matrix.appended(mx));\n\t\t\t}\n\t\t}\n\t}\n},\nnew function() {\n\tfunction getCurrentPath(that, check) {\n\t\tvar children = that._children;\n\t\tif (check && !children.length)\n\t\t\tthrow new Error('Use a moveTo() command first');\n\t\treturn children[children.length - 1];\n\t}\n\n\treturn Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo',\n\t\t\t'arcTo', 'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy',\n\t\t\t'arcBy'],\n\t\tfunction(key) {\n\t\t\tthis[key] = function() {\n\t\t\t\tvar path = getCurrentPath(this, true);\n\t\t\t\tpath[key].apply(path, arguments);\n\t\t\t};\n\t\t}, {\n\t\t\tmoveTo: function() {\n\t\t\t\tvar current = getCurrentPath(this),\n\t\t\t\t\tpath = current && current.isEmpty() ? current\n\t\t\t\t\t\t\t: new Path(Item.NO_INSERT);\n\t\t\t\tif (path !== current)\n\t\t\t\t\tthis.addChild(path);\n\t\t\t\tpath.moveTo.apply(path, arguments);\n\t\t\t},\n\n\t\t\tmoveBy: function() {\n\t\t\t\tvar current = getCurrentPath(this, true),\n\t\t\t\t\tlast = current && current.getLastSegment(),\n\t\t\t\t\tpoint = Point.read(arguments);\n\t\t\t\tthis.moveTo(last ? point.add(last._point) : point);\n\t\t\t},\n\n\t\t\tclosePath: function(tolerance) {\n\t\t\t\tgetCurrentPath(this, true).closePath(tolerance);\n\t\t\t}\n\t\t}\n\t);\n}, Base.each(['reverse', 'flatten', 'simplify', 'smooth'], function(key) {\n\tthis[key] = function(param) {\n\t\tvar children = this._children,\n\t\t\tres;\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tres = children[i][key](param) || res;\n\t\t}\n\t\treturn res;\n\t};\n}, {}));\n\nPathItem.inject(new function() {\n\tvar min = Math.min,\n\t\tmax = Math.max,\n\t\tabs = Math.abs,\n\t\toperators = {\n\t\t\tunite: { '1': true, '2': true },\n\t\t\tintersect: { '2': true },\n\t\t\tsubtract: { '1': true },\n\t\t\texclude: { '1': true, '-1': true }\n\t\t};\n\n\tfunction getPaths(path) {\n\t\treturn path._children || [path];\n\t}\n\n\tfunction preparePath(path, resolve) {\n\t\tvar res = path\n\t\t\t.clone(false)\n\t\t\t.reduce({ simplify: true })\n\t\t\t.transform(null, true, true);\n\t\tif (resolve) {\n\t\t\tvar paths = getPaths(res);\n\t\t\tfor (var i = 0, l = paths.length; i < l; i++) {\n\t\t\t\tvar path = paths[i];\n\t\t\t\tif (!path._closed && !path.isEmpty()) {\n\t\t\t\t\tpath.closePath(1e-12);\n\t\t\t\t\tpath.getFirstSegment().setHandleIn(0, 0);\n\t\t\t\t\tpath.getLastSegment().setHandleOut(0, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t\tres = res\n\t\t\t\t.resolveCrossings()\n\t\t\t\t.reorient(res.getFillRule() === 'nonzero', true);\n\t\t}\n\t\treturn res;\n\t}\n\n\tfunction createResult(paths, simplify, path1, path2, options) {\n\t\tvar result = new CompoundPath(Item.NO_INSERT);\n\t\tresult.addChildren(paths, true);\n\t\tresult = result.reduce({ simplify: simplify });\n\t\tif (!(options && options.insert == false)) {\n\t\t\tresult.insertAbove(path2 && path1.isSibling(path2)\n\t\t\t\t\t&& path1.getIndex() < path2.getIndex() ? path2 : path1);\n\t\t}\n\t\tresult.copyAttributes(path1, true);\n\t\treturn result;\n\t}\n\n\tfunction filterIntersection(inter) {\n\t\treturn inter.hasOverlap() || inter.isCrossing();\n\t}\n\n\tfunction traceBoolean(path1, path2, operation, options) {\n\t\tif (options && (options.trace == false || options.stroke) &&\n\t\t\t\t/^(subtract|intersect)$/.test(operation))\n\t\t\treturn splitBoolean(path1, path2, operation);\n\t\tvar _path1 = preparePath(path1, true),\n\t\t\t_path2 = path2 && path1 !== path2 && preparePath(path2, true),\n\t\t\toperator = operators[operation];\n\t\toperator[operation] = true;\n\t\tif (_path2 && (operator.subtract || operator.exclude)\n\t\t\t\t^ (_path2.isClockwise() ^ _path1.isClockwise()))\n\t\t\t_path2.reverse();\n\t\tvar crossings = divideLocations(CurveLocation.expand(\n\t\t\t\t_path1.getIntersections(_path2, filterIntersection))),\n\t\t\tpaths1 = getPaths(_path1),\n\t\t\tpaths2 = _path2 && getPaths(_path2),\n\t\t\tsegments = [],\n\t\t\tcurves = [],\n\t\t\tpaths;\n\n\t\tfunction collectPaths(paths) {\n\t\t\tfor (var i = 0, l = paths.length; i < l; i++) {\n\t\t\t\tvar path = paths[i];\n\t\t\t\tBase.push(segments, path._segments);\n\t\t\t\tBase.push(curves, path.getCurves());\n\t\t\t\tpath._overlapsOnly = true;\n\t\t\t}\n\t\t}\n\n\t\tfunction getCurves(indices) {\n\t\t\tvar list = [];\n\t\t\tfor (var i = 0, l = indices && indices.length; i < l; i++) {\n\t\t\t\tlist.push(curves[indices[i]]);\n\t\t\t}\n\t\t\treturn list;\n\t\t}\n\n\t\tif (crossings.length) {\n\t\t\tcollectPaths(paths1);\n\t\t\tif (paths2)\n\t\t\t\tcollectPaths(paths2);\n\n\t\t\tvar curvesValues = new Array(curves.length);\n\t\t\tfor (var i = 0, l = curves.length; i < l; i++) {\n\t\t\t\tcurvesValues[i] = curves[i].getValues();\n\t\t\t}\n\t\t\tvar curveCollisions = CollisionDetection.findCurveBoundsCollisions(\n\t\t\t\t\tcurvesValues, curvesValues, 0, true);\n\t\t\tvar curveCollisionsMap = {};\n\t\t\tfor (var i = 0; i < curves.length; i++) {\n\t\t\t\tvar curve = curves[i],\n\t\t\t\t\tid = curve._path._id,\n\t\t\t\t\tmap = curveCollisionsMap[id] = curveCollisionsMap[id] || {};\n\t\t\t\tmap[curve.getIndex()] = {\n\t\t\t\t\thor: getCurves(curveCollisions[i].hor),\n\t\t\t\t\tver: getCurves(curveCollisions[i].ver)\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tfor (var i = 0, l = crossings.length; i < l; i++) {\n\t\t\t\tpropagateWinding(crossings[i]._segment, _path1, _path2,\n\t\t\t\t\t\tcurveCollisionsMap, operator);\n\t\t\t}\n\t\t\tfor (var i = 0, l = segments.length; i < l; i++) {\n\t\t\t\tvar segment = segments[i],\n\t\t\t\t\tinter = segment._intersection;\n\t\t\t\tif (!segment._winding) {\n\t\t\t\t\tpropagateWinding(segment, _path1, _path2,\n\t\t\t\t\t\t\tcurveCollisionsMap, operator);\n\t\t\t\t}\n\t\t\t\tif (!(inter && inter._overlap))\n\t\t\t\t\tsegment._path._overlapsOnly = false;\n\t\t\t}\n\t\t\tpaths = tracePaths(segments, operator);\n\t\t} else {\n\t\t\tpaths = reorientPaths(\n\t\t\t\t\tpaths2 ? paths1.concat(paths2) : paths1.slice(),\n\t\t\t\t\tfunction(w) {\n\t\t\t\t\t\treturn !!operator[w];\n\t\t\t\t\t});\n\t\t}\n\t\treturn createResult(paths, true, path1, path2, options);\n\t}\n\n\tfunction splitBoolean(path1, path2, operation) {\n\t\tvar _path1 = preparePath(path1),\n\t\t\t_path2 = preparePath(path2),\n\t\t\tcrossings = _path1.getIntersections(_path2, filterIntersection),\n\t\t\tsubtract = operation === 'subtract',\n\t\t\tdivide = operation === 'divide',\n\t\t\tadded = {},\n\t\t\tpaths = [];\n\n\t\tfunction addPath(path) {\n\t\t\tif (!added[path._id] && (divide ||\n\t\t\t\t\t_path2.contains(path.getPointAt(path.getLength() / 2))\n\t\t\t\t\t\t^ subtract)) {\n\t\t\t\tpaths.unshift(path);\n\t\t\t\treturn added[path._id] = true;\n\t\t\t}\n\t\t}\n\n\t\tfor (var i = crossings.length - 1; i >= 0; i--) {\n\t\t\tvar path = crossings[i].split();\n\t\t\tif (path) {\n\t\t\t\tif (addPath(path))\n\t\t\t\t\tpath.getFirstSegment().setHandleIn(0, 0);\n\t\t\t\t_path1.getLastSegment().setHandleOut(0, 0);\n\t\t\t}\n\t\t}\n\t\taddPath(_path1);\n\t\treturn createResult(paths, false, path1, path2);\n\t}\n\n\tfunction linkIntersections(from, to) {\n\t\tvar prev = from;\n\t\twhile (prev) {\n\t\t\tif (prev === to)\n\t\t\t\treturn;\n\t\t\tprev = prev._previous;\n\t\t}\n\t\twhile (from._next && from._next !== to)\n\t\t\tfrom = from._next;\n\t\tif (!from._next) {\n\t\t\twhile (to._previous)\n\t\t\t\tto = to._previous;\n\t\t\tfrom._next = to;\n\t\t\tto._previous = from;\n\t\t}\n\t}\n\n\tfunction clearCurveHandles(curves) {\n\t\tfor (var i = curves.length - 1; i >= 0; i--)\n\t\t\tcurves[i].clearHandles();\n\t}\n\n\tfunction reorientPaths(paths, isInside, clockwise) {\n\t\tvar length = paths && paths.length;\n\t\tif (length) {\n\t\t\tvar lookup = Base.each(paths, function (path, i) {\n\t\t\t\t\tthis[path._id] = {\n\t\t\t\t\t\tcontainer: null,\n\t\t\t\t\t\twinding: path.isClockwise() ? 1 : -1,\n\t\t\t\t\t\tindex: i\n\t\t\t\t\t};\n\t\t\t\t}, {}),\n\t\t\t\tsorted = paths.slice().sort(function (a, b) {\n\t\t\t\t\treturn abs(b.getArea()) - abs(a.getArea());\n\t\t\t\t}),\n\t\t\t\tfirst = sorted[0];\n\t\t\tvar collisions = CollisionDetection.findItemBoundsCollisions(sorted,\n\t\t\t\t\tnull, Numerical.GEOMETRIC_EPSILON);\n\t\t\tif (clockwise == null)\n\t\t\t\tclockwise = first.isClockwise();\n\t\t\tfor (var i = 0; i < length; i++) {\n\t\t\t\tvar path1 = sorted[i],\n\t\t\t\t\tentry1 = lookup[path1._id],\n\t\t\t\t\tcontainerWinding = 0,\n\t\t\t\t\tindices = collisions[i];\n\t\t\t\tif (indices) {\n\t\t\t\t\tvar point = null;\n\t\t\t\t\tfor (var j = indices.length - 1; j >= 0; j--) {\n\t\t\t\t\t\tif (indices[j] < i) {\n\t\t\t\t\t\t\tpoint = point || path1.getInteriorPoint();\n\t\t\t\t\t\t\tvar path2 = sorted[indices[j]];\n\t\t\t\t\t\t\tif (path2.contains(point)) {\n\t\t\t\t\t\t\t\tvar entry2 = lookup[path2._id];\n\t\t\t\t\t\t\t\tcontainerWinding = entry2.winding;\n\t\t\t\t\t\t\t\tentry1.winding += containerWinding;\n\t\t\t\t\t\t\t\tentry1.container = entry2.exclude\n\t\t\t\t\t\t\t\t\t? entry2.container : path2;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (isInside(entry1.winding) === isInside(containerWinding)) {\n\t\t\t\t\tentry1.exclude = true;\n\t\t\t\t\tpaths[entry1.index] = null;\n\t\t\t\t} else {\n\t\t\t\t\tvar container = entry1.container;\n\t\t\t\t\tpath1.setClockwise(\n\t\t\t\t\t\t\tcontainer ? !container.isClockwise() : clockwise);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn paths;\n\t}\n\n\tfunction divideLocations(locations, include, clearLater) {\n\t\tvar results = include && [],\n\t\t\ttMin = 1e-8,\n\t\t\ttMax = 1 - tMin,\n\t\t\tclearHandles = false,\n\t\t\tclearCurves = clearLater || [],\n\t\t\tclearLookup = clearLater && {},\n\t\t\trenormalizeLocs,\n\t\t\tprevCurve,\n\t\t\tprevTime;\n\n\t\tfunction getId(curve) {\n\t\t\treturn curve._path._id + '.' + curve._segment1._index;\n\t\t}\n\n\t\tfor (var i = (clearLater && clearLater.length) - 1; i >= 0; i--) {\n\t\t\tvar curve = clearLater[i];\n\t\t\tif (curve._path)\n\t\t\t\tclearLookup[getId(curve)] = true;\n\t\t}\n\n\t\tfor (var i = locations.length - 1; i >= 0; i--) {\n\t\t\tvar loc = locations[i],\n\t\t\t\ttime = loc._time,\n\t\t\t\torigTime = time,\n\t\t\t\texclude = include && !include(loc),\n\t\t\t\tcurve = loc._curve,\n\t\t\t\tsegment;\n\t\t\tif (curve) {\n\t\t\t\tif (curve !== prevCurve) {\n\t\t\t\t\tclearHandles = !curve.hasHandles()\n\t\t\t\t\t\t\t|| clearLookup && clearLookup[getId(curve)];\n\t\t\t\t\trenormalizeLocs = [];\n\t\t\t\t\tprevTime = null;\n\t\t\t\t\tprevCurve = curve;\n\t\t\t\t} else if (prevTime >= tMin) {\n\t\t\t\t\ttime /= prevTime;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (exclude) {\n\t\t\t\tif (renormalizeLocs)\n\t\t\t\t\trenormalizeLocs.push(loc);\n\t\t\t\tcontinue;\n\t\t\t} else if (include) {\n\t\t\t\tresults.unshift(loc);\n\t\t\t}\n\t\t\tprevTime = origTime;\n\t\t\tif (time < tMin) {\n\t\t\t\tsegment = curve._segment1;\n\t\t\t} else if (time > tMax) {\n\t\t\t\tsegment = curve._segment2;\n\t\t\t} else {\n\t\t\t\tvar newCurve = curve.divideAtTime(time, true);\n\t\t\t\tif (clearHandles)\n\t\t\t\t\tclearCurves.push(curve, newCurve);\n\t\t\t\tsegment = newCurve._segment1;\n\t\t\t\tfor (var j = renormalizeLocs.length - 1; j >= 0; j--) {\n\t\t\t\t\tvar l = renormalizeLocs[j];\n\t\t\t\t\tl._time = (l._time - time) / (1 - time);\n\t\t\t\t}\n\t\t\t}\n\t\t\tloc._setSegment(segment);\n\t\t\tvar inter = segment._intersection,\n\t\t\t\tdest = loc._intersection;\n\t\t\tif (inter) {\n\t\t\t\tlinkIntersections(inter, dest);\n\t\t\t\tvar other = inter;\n\t\t\t\twhile (other) {\n\t\t\t\t\tlinkIntersections(other._intersection, inter);\n\t\t\t\t\tother = other._next;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsegment._intersection = dest;\n\t\t\t}\n\t\t}\n\t\tif (!clearLater)\n\t\t\tclearCurveHandles(clearCurves);\n\t\treturn results || locations;\n\t}\n\n\tfunction getWinding(point, curves, dir, closed, dontFlip) {\n\t\tvar curvesList = Array.isArray(curves)\n\t\t\t? curves\n\t\t\t: curves[dir ? 'hor' : 'ver'];\n\t\tvar ia = dir ? 1 : 0,\n\t\t\tio = ia ^ 1,\n\t\t\tpv = [point.x, point.y],\n\t\t\tpa = pv[ia],\n\t\t\tpo = pv[io],\n\t\t\twindingEpsilon = 1e-9,\n\t\t\tqualityEpsilon = 1e-6,\n\t\t\tpaL = pa - windingEpsilon,\n\t\t\tpaR = pa + windingEpsilon,\n\t\t\twindingL = 0,\n\t\t\twindingR = 0,\n\t\t\tpathWindingL = 0,\n\t\t\tpathWindingR = 0,\n\t\t\tonPath = false,\n\t\t\tonAnyPath = false,\n\t\t\tquality = 1,\n\t\t\troots = [],\n\t\t\tvPrev,\n\t\t\tvClose;\n\n\t\tfunction addWinding(v) {\n\t\t\tvar o0 = v[io + 0],\n\t\t\t\to3 = v[io + 6];\n\t\t\tif (po < min(o0, o3) || po > max(o0, o3)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar a0 = v[ia + 0],\n\t\t\t\ta1 = v[ia + 2],\n\t\t\t\ta2 = v[ia + 4],\n\t\t\t\ta3 = v[ia + 6];\n\t\t\tif (o0 === o3) {\n\t\t\t\tif (a0 < paR && a3 > paL || a3 < paR && a0 > paL) {\n\t\t\t\t\tonPath = true;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar t = po === o0 ? 0\n\t\t\t\t\t: po === o3 ? 1\n\t\t\t\t\t: paL > max(a0, a1, a2, a3) || paR < min(a0, a1, a2, a3)\n\t\t\t\t\t? 1\n\t\t\t\t\t: Curve.solveCubic(v, io, po, roots, 0, 1) > 0\n\t\t\t\t\t\t? roots[0]\n\t\t\t\t\t\t: 1,\n\t\t\t\ta = t === 0 ? a0\n\t\t\t\t\t: t === 1 ? a3\n\t\t\t\t\t: Curve.getPoint(v, t)[dir ? 'y' : 'x'],\n\t\t\t\twinding = o0 > o3 ? 1 : -1,\n\t\t\t\twindingPrev = vPrev[io] > vPrev[io + 6] ? 1 : -1,\n\t\t\t\ta3Prev = vPrev[ia + 6];\n\t\t\tif (po !== o0) {\n\t\t\t\tif (a < paL) {\n\t\t\t\t\tpathWindingL += winding;\n\t\t\t\t} else if (a > paR) {\n\t\t\t\t\tpathWindingR += winding;\n\t\t\t\t} else {\n\t\t\t\t\tonPath = true;\n\t\t\t\t}\n\t\t\t\tif (a > pa - qualityEpsilon && a < pa + qualityEpsilon)\n\t\t\t\t\tquality /= 2;\n\t\t\t} else {\n\t\t\t\tif (winding !== windingPrev) {\n\t\t\t\t\tif (a0 < paL) {\n\t\t\t\t\t\tpathWindingL += winding;\n\t\t\t\t\t} else if (a0 > paR) {\n\t\t\t\t\t\tpathWindingR += winding;\n\t\t\t\t\t}\n\t\t\t\t} else if (a0 != a3Prev) {\n\t\t\t\t\tif (a3Prev < paR && a > paR) {\n\t\t\t\t\t\tpathWindingR += winding;\n\t\t\t\t\t\tonPath = true;\n\t\t\t\t\t} else if (a3Prev > paL && a < paL) {\n\t\t\t\t\t\tpathWindingL += winding;\n\t\t\t\t\t\tonPath = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tquality /= 4;\n\t\t\t}\n\t\t\tvPrev = v;\n\t\t\treturn !dontFlip && a > paL && a < paR\n\t\t\t\t\t&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0\n\t\t\t\t\t&& getWinding(point, curves, !dir, closed, true);\n\t\t}\n\n\t\tfunction handleCurve(v) {\n\t\t\tvar o0 = v[io + 0],\n\t\t\t\to1 = v[io + 2],\n\t\t\t\to2 = v[io + 4],\n\t\t\t\to3 = v[io + 6];\n\t\t\tif (po <= max(o0, o1, o2, o3) && po >= min(o0, o1, o2, o3)) {\n\t\t\t\tvar a0 = v[ia + 0],\n\t\t\t\t\ta1 = v[ia + 2],\n\t\t\t\t\ta2 = v[ia + 4],\n\t\t\t\t\ta3 = v[ia + 6],\n\t\t\t\t\tmonoCurves = paL > max(a0, a1, a2, a3) ||\n\t\t\t\t\t\t\t\t paR < min(a0, a1, a2, a3)\n\t\t\t\t\t\t\t? [v] : Curve.getMonoCurves(v, dir),\n\t\t\t\t\tres;\n\t\t\t\tfor (var i = 0, l = monoCurves.length; i < l; i++) {\n\t\t\t\t\tif (res = addWinding(monoCurves[i]))\n\t\t\t\t\t\treturn res;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (var i = 0, l = curvesList.length; i < l; i++) {\n\t\t\tvar curve = curvesList[i],\n\t\t\t\tpath = curve._path,\n\t\t\t\tv = curve.getValues(),\n\t\t\t\tres;\n\t\t\tif (!i || curvesList[i - 1]._path !== path) {\n\t\t\t\tvPrev = null;\n\t\t\t\tif (!path._closed) {\n\t\t\t\t\tvClose = Curve.getValues(\n\t\t\t\t\t\t\tpath.getLastCurve().getSegment2(),\n\t\t\t\t\t\t\tcurve.getSegment1(),\n\t\t\t\t\t\t\tnull, !closed);\n\t\t\t\t\tif (vClose[io] !== vClose[io + 6]) {\n\t\t\t\t\t\tvPrev = vClose;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!vPrev) {\n\t\t\t\t\tvPrev = v;\n\t\t\t\t\tvar prev = path.getLastCurve();\n\t\t\t\t\twhile (prev && prev !== curve) {\n\t\t\t\t\t\tvar v2 = prev.getValues();\n\t\t\t\t\t\tif (v2[io] !== v2[io + 6]) {\n\t\t\t\t\t\t\tvPrev = v2;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprev = prev.getPrevious();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (res = handleCurve(v))\n\t\t\t\treturn res;\n\n\t\t\tif (i + 1 === l || curvesList[i + 1]._path !== path) {\n\t\t\t\tif (vClose && (res = handleCurve(vClose)))\n\t\t\t\t\treturn res;\n\t\t\t\tif (onPath && !pathWindingL && !pathWindingR) {\n\t\t\t\t\tpathWindingL = pathWindingR = path.isClockwise(closed) ^ dir\n\t\t\t\t\t\t\t? 1 : -1;\n\t\t\t\t}\n\t\t\t\twindingL += pathWindingL;\n\t\t\t\twindingR += pathWindingR;\n\t\t\t\tpathWindingL = pathWindingR = 0;\n\t\t\t\tif (onPath) {\n\t\t\t\t\tonAnyPath = true;\n\t\t\t\t\tonPath = false;\n\t\t\t\t}\n\t\t\t\tvClose = null;\n\t\t\t}\n\t\t}\n\t\twindingL = abs(windingL);\n\t\twindingR = abs(windingR);\n\t\treturn {\n\t\t\twinding: max(windingL, windingR),\n\t\t\twindingL: windingL,\n\t\t\twindingR: windingR,\n\t\t\tquality: quality,\n\t\t\tonPath: onAnyPath\n\t\t};\n\t}\n\n\tfunction propagateWinding(segment, path1, path2, curveCollisionsMap,\n\t\t\toperator) {\n\t\tvar chain = [],\n\t\t\tstart = segment,\n\t\t\ttotalLength = 0,\n\t\t\twinding;\n\t\tdo {\n\t\t\tvar curve = segment.getCurve();\n\t\t\tif (curve) {\n\t\t\t\tvar length = curve.getLength();\n\t\t\t\tchain.push({ segment: segment, curve: curve, length: length });\n\t\t\t\ttotalLength += length;\n\t\t\t}\n\t\t\tsegment = segment.getNext();\n\t\t} while (segment && !segment._intersection && segment !== start);\n\t\tvar offsets = [0.5, 0.25, 0.75],\n\t\t\twinding = { winding: 0, quality: -1 },\n\t\t\ttMin = 1e-3,\n\t\t\ttMax = 1 - tMin;\n\t\tfor (var i = 0; i < offsets.length && winding.quality < 0.5; i++) {\n\t\t\tvar length = totalLength * offsets[i];\n\t\t\tfor (var j = 0, l = chain.length; j < l; j++) {\n\t\t\t\tvar entry = chain[j],\n\t\t\t\t\tcurveLength = entry.length;\n\t\t\t\tif (length <= curveLength) {\n\t\t\t\t\tvar curve = entry.curve,\n\t\t\t\t\t\tpath = curve._path,\n\t\t\t\t\t\tparent = path._parent,\n\t\t\t\t\t\toperand = parent instanceof CompoundPath ? parent : path,\n\t\t\t\t\t\tt = Numerical.clamp(curve.getTimeAt(length), tMin, tMax),\n\t\t\t\t\t\tpt = curve.getPointAtTime(t),\n\t\t\t\t\t\tdir = abs(curve.getTangentAtTime(t).y) < Math.SQRT1_2;\n\t\t\t\t\tvar wind = null;\n\t\t\t\t\tif (operator.subtract && path2) {\n\t\t\t\t\t\tvar otherPath = operand === path1 ? path2 : path1,\n\t\t\t\t\t\t\tpathWinding = otherPath._getWinding(pt, dir, true);\n\t\t\t\t\t\tif (operand === path1 && pathWinding.winding ||\n\t\t\t\t\t\t\toperand === path2 && !pathWinding.winding) {\n\t\t\t\t\t\t\tif (pathWinding.quality < 1) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\twind = { winding: 0, quality: 1 };\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\twind = wind || getWinding(\n\t\t\t\t\t\t\tpt, curveCollisionsMap[path._id][curve.getIndex()],\n\t\t\t\t\t\t\tdir, true);\n\t\t\t\t\tif (wind.quality > winding.quality)\n\t\t\t\t\t\twinding = wind;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tlength -= curveLength;\n\t\t\t}\n\t\t}\n\t\tfor (var j = chain.length - 1; j >= 0; j--) {\n\t\t\tchain[j].segment._winding = winding;\n\t\t}\n\t}\n\n\tfunction tracePaths(segments, operator) {\n\t\tvar paths = [],\n\t\t\tstarts;\n\n\t\tfunction isValid(seg) {\n\t\t\tvar winding;\n\t\t\treturn !!(seg && !seg._visited && (!operator\n\t\t\t\t\t|| operator[(winding = seg._winding || {}).winding]\n\t\t\t\t\t\t&& !(operator.unite && winding.winding === 2\n\t\t\t\t\t\t\t&& winding.windingL && winding.windingR)));\n\t\t}\n\n\t\tfunction isStart(seg) {\n\t\t\tif (seg) {\n\t\t\t\tfor (var i = 0, l = starts.length; i < l; i++) {\n\t\t\t\t\tif (seg === starts[i])\n\t\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\tfunction visitPath(path) {\n\t\t\tvar segments = path._segments;\n\t\t\tfor (var i = 0, l = segments.length; i < l; i++) {\n\t\t\t\tsegments[i]._visited = true;\n\t\t\t}\n\t\t}\n\n\t\tfunction getCrossingSegments(segment, collectStarts) {\n\t\t\tvar inter = segment._intersection,\n\t\t\t\tstart = inter,\n\t\t\t\tcrossings = [];\n\t\t\tif (collectStarts)\n\t\t\t\tstarts = [segment];\n\n\t\t\tfunction collect(inter, end) {\n\t\t\t\twhile (inter && inter !== end) {\n\t\t\t\t\tvar other = inter._segment,\n\t\t\t\t\t\tpath = other && other._path;\n\t\t\t\t\tif (path) {\n\t\t\t\t\t\tvar next = other.getNext() || path.getFirstSegment(),\n\t\t\t\t\t\t\tnextInter = next._intersection;\n\t\t\t\t\t\tif (other !== segment && (isStart(other)\n\t\t\t\t\t\t\t|| isStart(next)\n\t\t\t\t\t\t\t|| next && (isValid(other) && (isValid(next)\n\t\t\t\t\t\t\t\t|| nextInter && isValid(nextInter._segment))))\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tcrossings.push(other);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (collectStarts)\n\t\t\t\t\t\t\tstarts.push(other);\n\t\t\t\t\t}\n\t\t\t\t\tinter = inter._next;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (inter) {\n\t\t\t\tcollect(inter);\n\t\t\t\twhile (inter && inter._previous)\n\t\t\t\t\tinter = inter._previous;\n\t\t\t\tcollect(inter, start);\n\t\t\t}\n\t\t\treturn crossings;\n\t\t}\n\n\t\tsegments.sort(function(seg1, seg2) {\n\t\t\tvar inter1 = seg1._intersection,\n\t\t\t\tinter2 = seg2._intersection,\n\t\t\t\tover1 = !!(inter1 && inter1._overlap),\n\t\t\t\tover2 = !!(inter2 && inter2._overlap),\n\t\t\t\tpath1 = seg1._path,\n\t\t\t\tpath2 = seg2._path;\n\t\t\treturn over1 ^ over2\n\t\t\t\t\t? over1 ? 1 : -1\n\t\t\t\t\t: !inter1 ^ !inter2\n\t\t\t\t\t\t? inter1 ? 1 : -1\n\t\t\t\t\t\t: path1 !== path2\n\t\t\t\t\t\t\t? path1._id - path2._id\n\t\t\t\t\t\t\t: seg1._index - seg2._index;\n\t\t});\n\n\t\tfor (var i = 0, l = segments.length; i < l; i++) {\n\t\t\tvar seg = segments[i],\n\t\t\t\tvalid = isValid(seg),\n\t\t\t\tpath = null,\n\t\t\t\tfinished = false,\n\t\t\t\tclosed = true,\n\t\t\t\tbranches = [],\n\t\t\t\tbranch,\n\t\t\t\tvisited,\n\t\t\t\thandleIn;\n\t\t\tif (valid && seg._path._overlapsOnly) {\n\t\t\t\tvar path1 = seg._path,\n\t\t\t\t\tpath2 = seg._intersection._segment._path;\n\t\t\t\tif (path1.compare(path2)) {\n\t\t\t\t\tif (path1.getArea())\n\t\t\t\t\t\tpaths.push(path1.clone(false));\n\t\t\t\t\tvisitPath(path1);\n\t\t\t\t\tvisitPath(path2);\n\t\t\t\t\tvalid = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile (valid) {\n\t\t\t\tvar first = !path,\n\t\t\t\t\tcrossings = getCrossingSegments(seg, first),\n\t\t\t\t\tother = crossings.shift(),\n\t\t\t\t\tfinished = !first && (isStart(seg) || isStart(other)),\n\t\t\t\t\tcross = !finished && other;\n\t\t\t\tif (first) {\n\t\t\t\t\tpath = new Path(Item.NO_INSERT);\n\t\t\t\t\tbranch = null;\n\t\t\t\t}\n\t\t\t\tif (finished) {\n\t\t\t\t\tif (seg.isFirst() || seg.isLast())\n\t\t\t\t\t\tclosed = seg._path._closed;\n\t\t\t\t\tseg._visited = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (cross && branch) {\n\t\t\t\t\tbranches.push(branch);\n\t\t\t\t\tbranch = null;\n\t\t\t\t}\n\t\t\t\tif (!branch) {\n\t\t\t\t\tif (cross)\n\t\t\t\t\t\tcrossings.push(seg);\n\t\t\t\t\tbranch = {\n\t\t\t\t\t\tstart: path._segments.length,\n\t\t\t\t\t\tcrossings: crossings,\n\t\t\t\t\t\tvisited: visited = [],\n\t\t\t\t\t\thandleIn: handleIn\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (cross)\n\t\t\t\t\tseg = other;\n\t\t\t\tif (!isValid(seg)) {\n\t\t\t\t\tpath.removeSegments(branch.start);\n\t\t\t\t\tfor (var j = 0, k = visited.length; j < k; j++) {\n\t\t\t\t\t\tvisited[j]._visited = false;\n\t\t\t\t\t}\n\t\t\t\t\tvisited.length = 0;\n\t\t\t\t\tdo {\n\t\t\t\t\t\tseg = branch && branch.crossings.shift();\n\t\t\t\t\t\tif (!seg || !seg._path) {\n\t\t\t\t\t\t\tseg = null;\n\t\t\t\t\t\t\tbranch = branches.pop();\n\t\t\t\t\t\t\tif (branch) {\n\t\t\t\t\t\t\t\tvisited = branch.visited;\n\t\t\t\t\t\t\t\thandleIn = branch.handleIn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} while (branch && !isValid(seg));\n\t\t\t\t\tif (!seg)\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tvar next = seg.getNext();\n\t\t\t\tpath.add(new Segment(seg._point, handleIn,\n\t\t\t\t\t\tnext && seg._handleOut));\n\t\t\t\tseg._visited = true;\n\t\t\t\tvisited.push(seg);\n\t\t\t\tseg = next || seg._path.getFirstSegment();\n\t\t\t\thandleIn = next && next._handleIn;\n\t\t\t}\n\t\t\tif (finished) {\n\t\t\t\tif (closed) {\n\t\t\t\t\tpath.getFirstSegment().setHandleIn(handleIn);\n\t\t\t\t\tpath.setClosed(closed);\n\t\t\t\t}\n\t\t\t\tif (path.getArea() !== 0) {\n\t\t\t\t\tpaths.push(path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn paths;\n\t}\n\n\treturn {\n\t\t_getWinding: function(point, dir, closed) {\n\t\t\treturn getWinding(point, this.getCurves(), dir, closed);\n\t\t},\n\n\t\tunite: function(path, options) {\n\t\t\treturn traceBoolean(this, path, 'unite', options);\n\t\t},\n\n\t\tintersect: function(path, options) {\n\t\t\treturn traceBoolean(this, path, 'intersect', options);\n\t\t},\n\n\t\tsubtract: function(path, options) {\n\t\t\treturn traceBoolean(this, path, 'subtract', options);\n\t\t},\n\n\t\texclude: function(path, options) {\n\t\t\treturn traceBoolean(this, path, 'exclude', options);\n\t\t},\n\n\t\tdivide: function(path, options) {\n\t\t\treturn options && (options.trace == false || options.stroke)\n\t\t\t\t\t? splitBoolean(this, path, 'divide')\n\t\t\t\t\t: createResult([\n\t\t\t\t\t\tthis.subtract(path, options),\n\t\t\t\t\t\tthis.intersect(path, options)\n\t\t\t\t\t], true, this, path, options);\n\t\t},\n\n\t\tresolveCrossings: function() {\n\t\t\tvar children = this._children,\n\t\t\t\tpaths = children || [this];\n\n\t\t\tfunction hasOverlap(seg, path) {\n\t\t\t\tvar inter = seg && seg._intersection;\n\t\t\t\treturn inter && inter._overlap && inter._path === path;\n\t\t\t}\n\n\t\t\tvar hasOverlaps = false,\n\t\t\t\thasCrossings = false,\n\t\t\t\tintersections = this.getIntersections(null, function(inter) {\n\t\t\t\t\treturn inter.hasOverlap() && (hasOverlaps = true) ||\n\t\t\t\t\t\t\tinter.isCrossing() && (hasCrossings = true);\n\t\t\t\t}),\n\t\t\t\tclearCurves = hasOverlaps && hasCrossings && [];\n\t\t\tintersections = CurveLocation.expand(intersections);\n\t\t\tif (hasOverlaps) {\n\t\t\t\tvar overlaps = divideLocations(intersections, function(inter) {\n\t\t\t\t\treturn inter.hasOverlap();\n\t\t\t\t}, clearCurves);\n\t\t\t\tfor (var i = overlaps.length - 1; i >= 0; i--) {\n\t\t\t\t\tvar overlap = overlaps[i],\n\t\t\t\t\t\tpath = overlap._path,\n\t\t\t\t\t\tseg = overlap._segment,\n\t\t\t\t\t\tprev = seg.getPrevious(),\n\t\t\t\t\t\tnext = seg.getNext();\n\t\t\t\t\tif (hasOverlap(prev, path) && hasOverlap(next, path)) {\n\t\t\t\t\t\tseg.remove();\n\t\t\t\t\t\tprev._handleOut._set(0, 0);\n\t\t\t\t\t\tnext._handleIn._set(0, 0);\n\t\t\t\t\t\tif (prev !== seg && !prev.getCurve().hasLength()) {\n\t\t\t\t\t\t\tnext._handleIn.set(prev._handleIn);\n\t\t\t\t\t\t\tprev.remove();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (hasCrossings) {\n\t\t\t\tdivideLocations(intersections, hasOverlaps && function(inter) {\n\t\t\t\t\tvar curve1 = inter.getCurve(),\n\t\t\t\t\t\tseg1 = inter.getSegment(),\n\t\t\t\t\t\tother = inter._intersection,\n\t\t\t\t\t\tcurve2 = other._curve,\n\t\t\t\t\t\tseg2 = other._segment;\n\t\t\t\t\tif (curve1 && curve2 && curve1._path && curve2._path)\n\t\t\t\t\t\treturn true;\n\t\t\t\t\tif (seg1)\n\t\t\t\t\t\tseg1._intersection = null;\n\t\t\t\t\tif (seg2)\n\t\t\t\t\t\tseg2._intersection = null;\n\t\t\t\t}, clearCurves);\n\t\t\t\tif (clearCurves)\n\t\t\t\t\tclearCurveHandles(clearCurves);\n\t\t\t\tpaths = tracePaths(Base.each(paths, function(path) {\n\t\t\t\t\tBase.push(this, path._segments);\n\t\t\t\t}, []));\n\t\t\t}\n\t\t\tvar length = paths.length,\n\t\t\t\titem;\n\t\t\tif (length > 1 && children) {\n\t\t\t\tif (paths !== children)\n\t\t\t\t\tthis.setChildren(paths);\n\t\t\t\titem = this;\n\t\t\t} else if (length === 1 && !children) {\n\t\t\t\tif (paths[0] !== this)\n\t\t\t\t\tthis.setSegments(paths[0].removeSegments());\n\t\t\t\titem = this;\n\t\t\t}\n\t\t\tif (!item) {\n\t\t\t\titem = new CompoundPath(Item.NO_INSERT);\n\t\t\t\titem.addChildren(paths);\n\t\t\t\titem = item.reduce();\n\t\t\t\titem.copyAttributes(this);\n\t\t\t\tthis.replaceWith(item);\n\t\t\t}\n\t\t\treturn item;\n\t\t},\n\n\t\treorient: function(nonZero, clockwise) {\n\t\t\tvar children = this._children;\n\t\t\tif (children && children.length) {\n\t\t\t\tthis.setChildren(reorientPaths(this.removeChildren(),\n\t\t\t\t\t\tfunction(w) {\n\t\t\t\t\t\t\treturn !!(nonZero ? w : w & 1);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tclockwise));\n\t\t\t} else if (clockwise !== undefined) {\n\t\t\t\tthis.setClockwise(clockwise);\n\t\t\t}\n\t\t\treturn this;\n\t\t},\n\n\t\tgetInteriorPoint: function() {\n\t\t\tvar bounds = this.getBounds(),\n\t\t\t\tpoint = bounds.getCenter(true);\n\t\t\tif (!this.contains(point)) {\n\t\t\t\tvar curves = this.getCurves(),\n\t\t\t\t\ty = point.y,\n\t\t\t\t\tintercepts = [],\n\t\t\t\t\troots = [];\n\t\t\t\tfor (var i = 0, l = curves.length; i < l; i++) {\n\t\t\t\t\tvar v = curves[i].getValues(),\n\t\t\t\t\t\to0 = v[1],\n\t\t\t\t\t\to1 = v[3],\n\t\t\t\t\t\to2 = v[5],\n\t\t\t\t\t\to3 = v[7];\n\t\t\t\t\tif (y >= min(o0, o1, o2, o3) && y <= max(o0, o1, o2, o3)) {\n\t\t\t\t\t\tvar monoCurves = Curve.getMonoCurves(v);\n\t\t\t\t\t\tfor (var j = 0, m = monoCurves.length; j < m; j++) {\n\t\t\t\t\t\t\tvar mv = monoCurves[j],\n\t\t\t\t\t\t\t\tmo0 = mv[1],\n\t\t\t\t\t\t\t\tmo3 = mv[7];\n\t\t\t\t\t\t\tif ((mo0 !== mo3) &&\n\t\t\t\t\t\t\t\t(y >= mo0 && y <= mo3 || y >= mo3 && y <= mo0)){\n\t\t\t\t\t\t\t\tvar x = y === mo0 ? mv[0]\n\t\t\t\t\t\t\t\t\t: y === mo3 ? mv[6]\n\t\t\t\t\t\t\t\t\t: Curve.solveCubic(mv, 1, y, roots, 0, 1)\n\t\t\t\t\t\t\t\t\t\t=== 1\n\t\t\t\t\t\t\t\t\t\t? Curve.getPoint(mv, roots[0]).x\n\t\t\t\t\t\t\t\t\t\t: (mv[0] + mv[6]) / 2;\n\t\t\t\t\t\t\t\tintercepts.push(x);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (intercepts.length > 1) {\n\t\t\t\t\tintercepts.sort(function(a, b) { return a - b; });\n\t\t\t\t\tpoint.x = (intercepts[0] + intercepts[1]) / 2;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn point;\n\t\t}\n\t};\n});\n\nvar PathFlattener = Base.extend({\n\t_class: 'PathFlattener',\n\n\tinitialize: function(path, flatness, maxRecursion, ignoreStraight, matrix) {\n\t\tvar curves = [],\n\t\t\tparts = [],\n\t\t\tlength = 0,\n\t\t\tminSpan = 1 / (maxRecursion || 32),\n\t\t\tsegments = path._segments,\n\t\t\tsegment1 = segments[0],\n\t\t\tsegment2;\n\n\t\tfunction addCurve(segment1, segment2) {\n\t\t\tvar curve = Curve.getValues(segment1, segment2, matrix);\n\t\t\tcurves.push(curve);\n\t\t\tcomputeParts(curve, segment1._index, 0, 1);\n\t\t}\n\n\t\tfunction computeParts(curve, index, t1, t2) {\n\t\t\tif ((t2 - t1) > minSpan\n\t\t\t\t\t&& !(ignoreStraight && Curve.isStraight(curve))\n\t\t\t\t\t&& !Curve.isFlatEnough(curve, flatness || 0.25)) {\n\t\t\t\tvar halves = Curve.subdivide(curve, 0.5),\n\t\t\t\t\ttMid = (t1 + t2) / 2;\n\t\t\t\tcomputeParts(halves[0], index, t1, tMid);\n\t\t\t\tcomputeParts(halves[1], index, tMid, t2);\n\t\t\t} else {\n\t\t\t\tvar dx = curve[6] - curve[0],\n\t\t\t\t\tdy = curve[7] - curve[1],\n\t\t\t\t\tdist = Math.sqrt(dx * dx + dy * dy);\n\t\t\t\tif (dist > 0) {\n\t\t\t\t\tlength += dist;\n\t\t\t\t\tparts.push({\n\t\t\t\t\t\toffset: length,\n\t\t\t\t\t\tcurve: curve,\n\t\t\t\t\t\tindex: index,\n\t\t\t\t\t\ttime: t2,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (var i = 1, l = segments.length; i < l; i++) {\n\t\t\tsegment2 = segments[i];\n\t\t\taddCurve(segment1, segment2);\n\t\t\tsegment1 = segment2;\n\t\t}\n\t\tif (path._closed)\n\t\t\taddCurve(segment2 || segment1, segments[0]);\n\t\tthis.curves = curves;\n\t\tthis.parts = parts;\n\t\tthis.length = length;\n\t\tthis.index = 0;\n\t},\n\n\t_get: function(offset) {\n\t\tvar parts = this.parts,\n\t\t\tlength = parts.length,\n\t\t\tstart,\n\t\t\ti, j = this.index;\n\t\tfor (;;) {\n\t\t\ti = j;\n\t\t\tif (!j || parts[--j].offset < offset)\n\t\t\t\tbreak;\n\t\t}\n\t\tfor (; i < length; i++) {\n\t\t\tvar part = parts[i];\n\t\t\tif (part.offset >= offset) {\n\t\t\t\tthis.index = i;\n\t\t\t\tvar prev = parts[i - 1],\n\t\t\t\t\tprevTime = prev && prev.index === part.index ? prev.time : 0,\n\t\t\t\t\tprevOffset = prev ? prev.offset : 0;\n\t\t\t\treturn {\n\t\t\t\t\tindex: part.index,\n\t\t\t\t\ttime: prevTime + (part.time - prevTime)\n\t\t\t\t\t\t* (offset - prevOffset) / (part.offset - prevOffset)\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tindex: parts[length - 1].index,\n\t\t\ttime: 1\n\t\t};\n\t},\n\n\tdrawPart: function(ctx, from, to) {\n\t\tvar start = this._get(from),\n\t\t\tend = this._get(to);\n\t\tfor (var i = start.index, l = end.index; i <= l; i++) {\n\t\t\tvar curve = Curve.getPart(this.curves[i],\n\t\t\t\t\ti === start.index ? start.time : 0,\n\t\t\t\t\ti === end.index ? end.time : 1);\n\t\t\tif (i === start.index)\n\t\t\t\tctx.moveTo(curve[0], curve[1]);\n\t\t\tctx.bezierCurveTo.apply(ctx, curve.slice(2));\n\t\t}\n\t}\n}, Base.each(Curve._evaluateMethods,\n\tfunction(name) {\n\t\tthis[name + 'At'] = function(offset) {\n\t\t\tvar param = this._get(offset);\n\t\t\treturn Curve[name](this.curves[param.index], param.time);\n\t\t};\n\t}, {})\n);\n\nvar PathFitter = Base.extend({\n\tinitialize: function(path) {\n\t\tvar points = this.points = [],\n\t\t\tsegments = path._segments,\n\t\t\tclosed = path._closed;\n\t\tfor (var i = 0, prev, l = segments.length; i < l; i++) {\n\t\t\tvar point = segments[i].point;\n\t\t\tif (!prev || !prev.equals(point)) {\n\t\t\t\tpoints.push(prev = point.clone());\n\t\t\t}\n\t\t}\n\t\tif (closed) {\n\t\t\tpoints.unshift(points[points.length - 1]);\n\t\t\tpoints.push(points[1]);\n\t\t}\n\t\tthis.closed = closed;\n\t},\n\n\tfit: function(error) {\n\t\tvar points = this.points,\n\t\t\tlength = points.length,\n\t\t\tsegments = null;\n\t\tif (length > 0) {\n\t\t\tsegments = [new Segment(points[0])];\n\t\t\tif (length > 1) {\n\t\t\t\tthis.fitCubic(segments, error, 0, length - 1,\n\t\t\t\t\t\tpoints[1].subtract(points[0]),\n\t\t\t\t\t\tpoints[length - 2].subtract(points[length - 1]));\n\t\t\t\tif (this.closed) {\n\t\t\t\t\tsegments.shift();\n\t\t\t\t\tsegments.pop();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn segments;\n\t},\n\n\tfitCubic: function(segments, error, first, last, tan1, tan2) {\n\t\tvar points = this.points;\n\t\tif (last - first === 1) {\n\t\t\tvar pt1 = points[first],\n\t\t\t\tpt2 = points[last],\n\t\t\t\tdist = pt1.getDistance(pt2) / 3;\n\t\t\tthis.addCurve(segments, [pt1, pt1.add(tan1.normalize(dist)),\n\t\t\t\t\tpt2.add(tan2.normalize(dist)), pt2]);\n\t\t\treturn;\n\t\t}\n\t\tvar uPrime = this.chordLengthParameterize(first, last),\n\t\t\tmaxError = Math.max(error, error * error),\n\t\t\tsplit,\n\t\t\tparametersInOrder = true;\n\t\tfor (var i = 0; i <= 4; i++) {\n\t\t\tvar curve = this.generateBezier(first, last, uPrime, tan1, tan2);\n\t\t\tvar max = this.findMaxError(first, last, curve, uPrime);\n\t\t\tif (max.error < error && parametersInOrder) {\n\t\t\t\tthis.addCurve(segments, curve);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsplit = max.index;\n\t\t\tif (max.error >= maxError)\n\t\t\t\tbreak;\n\t\t\tparametersInOrder = this.reparameterize(first, last, uPrime, curve);\n\t\t\tmaxError = max.error;\n\t\t}\n\t\tvar tanCenter = points[split - 1].subtract(points[split + 1]);\n\t\tthis.fitCubic(segments, error, first, split, tan1, tanCenter);\n\t\tthis.fitCubic(segments, error, split, last, tanCenter.negate(), tan2);\n\t},\n\n\taddCurve: function(segments, curve) {\n\t\tvar prev = segments[segments.length - 1];\n\t\tprev.setHandleOut(curve[1].subtract(curve[0]));\n\t\tsegments.push(new Segment(curve[3], curve[2].subtract(curve[3])));\n\t},\n\n\tgenerateBezier: function(first, last, uPrime, tan1, tan2) {\n\t\tvar epsilon = 1e-12,\n\t\t\tabs = Math.abs,\n\t\t\tpoints = this.points,\n\t\t\tpt1 = points[first],\n\t\t\tpt2 = points[last],\n\t\t\tC = [[0, 0], [0, 0]],\n\t\t\tX = [0, 0];\n\n\t\tfor (var i = 0, l = last - first + 1; i < l; i++) {\n\t\t\tvar u = uPrime[i],\n\t\t\t\tt = 1 - u,\n\t\t\t\tb = 3 * u * t,\n\t\t\t\tb0 = t * t * t,\n\t\t\t\tb1 = b * t,\n\t\t\t\tb2 = b * u,\n\t\t\t\tb3 = u * u * u,\n\t\t\t\ta1 = tan1.normalize(b1),\n\t\t\t\ta2 = tan2.normalize(b2),\n\t\t\t\ttmp = points[first + i]\n\t\t\t\t\t.subtract(pt1.multiply(b0 + b1))\n\t\t\t\t\t.subtract(pt2.multiply(b2 + b3));\n\t\t\tC[0][0] += a1.dot(a1);\n\t\t\tC[0][1] += a1.dot(a2);\n\t\t\tC[1][0] = C[0][1];\n\t\t\tC[1][1] += a2.dot(a2);\n\t\t\tX[0] += a1.dot(tmp);\n\t\t\tX[1] += a2.dot(tmp);\n\t\t}\n\n\t\tvar detC0C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1],\n\t\t\talpha1,\n\t\t\talpha2;\n\t\tif (abs(detC0C1) > epsilon) {\n\t\t\tvar detC0X = C[0][0] * X[1] - C[1][0] * X[0],\n\t\t\t\tdetXC1 = X[0] * C[1][1] - X[1] * C[0][1];\n\t\t\talpha1 = detXC1 / detC0C1;\n\t\t\talpha2 = detC0X / detC0C1;\n\t\t} else {\n\t\t\tvar c0 = C[0][0] + C[0][1],\n\t\t\t\tc1 = C[1][0] + C[1][1];\n\t\t\talpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0\n\t\t\t\t\t\t\t: abs(c1) > epsilon ? X[1] / c1\n\t\t\t\t\t\t\t: 0;\n\t\t}\n\n\t\tvar segLength = pt2.getDistance(pt1),\n\t\t\teps = epsilon * segLength,\n\t\t\thandle1,\n\t\t\thandle2;\n\t\tif (alpha1 < eps || alpha2 < eps) {\n\t\t\talpha1 = alpha2 = segLength / 3;\n\t\t} else {\n\t\t\tvar line = pt2.subtract(pt1);\n\t\t\thandle1 = tan1.normalize(alpha1);\n\t\t\thandle2 = tan2.normalize(alpha2);\n\t\t\tif (handle1.dot(line) - handle2.dot(line) > segLength * segLength) {\n\t\t\t\talpha1 = alpha2 = segLength / 3;\n\t\t\t\thandle1 = handle2 = null;\n\t\t\t}\n\t\t}\n\n\t\treturn [pt1,\n\t\t\t\tpt1.add(handle1 || tan1.normalize(alpha1)),\n\t\t\t\tpt2.add(handle2 || tan2.normalize(alpha2)),\n\t\t\t\tpt2];\n\t},\n\n\treparameterize: function(first, last, u, curve) {\n\t\tfor (var i = first; i <= last; i++) {\n\t\t\tu[i - first] = this.findRoot(curve, this.points[i], u[i - first]);\n\t\t}\n\t\tfor (var i = 1, l = u.length; i < l; i++) {\n\t\t\tif (u[i] <= u[i - 1])\n\t\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\tfindRoot: function(curve, point, u) {\n\t\tvar curve1 = [],\n\t\t\tcurve2 = [];\n\t\tfor (var i = 0; i <= 2; i++) {\n\t\t\tcurve1[i] = curve[i + 1].subtract(curve[i]).multiply(3);\n\t\t}\n\t\tfor (var i = 0; i <= 1; i++) {\n\t\t\tcurve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2);\n\t\t}\n\t\tvar pt = this.evaluate(3, curve, u),\n\t\t\tpt1 = this.evaluate(2, curve1, u),\n\t\t\tpt2 = this.evaluate(1, curve2, u),\n\t\t\tdiff = pt.subtract(point),\n\t\t\tdf = pt1.dot(pt1) + diff.dot(pt2);\n\t\treturn Numerical.isMachineZero(df) ? u : u - diff.dot(pt1) / df;\n\t},\n\n\tevaluate: function(degree, curve, t) {\n\t\tvar tmp = curve.slice();\n\t\tfor (var i = 1; i <= degree; i++) {\n\t\t\tfor (var j = 0; j <= degree - i; j++) {\n\t\t\t\ttmp[j] = tmp[j].multiply(1 - t).add(tmp[j + 1].multiply(t));\n\t\t\t}\n\t\t}\n\t\treturn tmp[0];\n\t},\n\n\tchordLengthParameterize: function(first, last) {\n\t\tvar u = [0];\n\t\tfor (var i = first + 1; i <= last; i++) {\n\t\t\tu[i - first] = u[i - first - 1]\n\t\t\t\t\t+ this.points[i].getDistance(this.points[i - 1]);\n\t\t}\n\t\tfor (var i = 1, m = last - first; i <= m; i++) {\n\t\t\tu[i] /= u[m];\n\t\t}\n\t\treturn u;\n\t},\n\n\tfindMaxError: function(first, last, curve, u) {\n\t\tvar index = Math.floor((last - first + 1) / 2),\n\t\t\tmaxDist = 0;\n\t\tfor (var i = first + 1; i < last; i++) {\n\t\t\tvar P = this.evaluate(3, curve, u[i - first]);\n\t\t\tvar v = P.subtract(this.points[i]);\n\t\t\tvar dist = v.x * v.x + v.y * v.y;\n\t\t\tif (dist >= maxDist) {\n\t\t\t\tmaxDist = dist;\n\t\t\t\tindex = i;\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\terror: maxDist,\n\t\t\tindex: index\n\t\t};\n\t}\n});\n\nvar TextItem = Item.extend({\n\t_class: 'TextItem',\n\t_applyMatrix: false,\n\t_canApplyMatrix: false,\n\t_serializeFields: {\n\t\tcontent: null\n\t},\n\t_boundsOptions: { stroke: false, handle: false },\n\n\tinitialize: function TextItem(arg) {\n\t\tthis._content = '';\n\t\tthis._lines = [];\n\t\tvar hasProps = arg && Base.isPlainObject(arg)\n\t\t\t\t&& arg.x === undefined && arg.y === undefined;\n\t\tthis._initialize(hasProps && arg, !hasProps && Point.read(arguments));\n\t},\n\n\t_equals: function(item) {\n\t\treturn this._content === item._content;\n\t},\n\n\tcopyContent: function(source) {\n\t\tthis.setContent(source._content);\n\t},\n\n\tgetContent: function() {\n\t\treturn this._content;\n\t},\n\n\tsetContent: function(content) {\n\t\tthis._content = '' + content;\n\t\tthis._lines = this._content.split(/\\r\\n|\\n|\\r/mg);\n\t\tthis._changed(521);\n\t},\n\n\tisEmpty: function() {\n\t\treturn !this._content;\n\t},\n\n\tgetCharacterStyle: '#getStyle',\n\tsetCharacterStyle: '#setStyle',\n\n\tgetParagraphStyle: '#getStyle',\n\tsetParagraphStyle: '#setStyle'\n});\n\nvar PointText = TextItem.extend({\n\t_class: 'PointText',\n\n\tinitialize: function PointText() {\n\t\tTextItem.apply(this, arguments);\n\t},\n\n\tgetPoint: function() {\n\t\tvar point = this._matrix.getTranslation();\n\t\treturn new LinkedPoint(point.x, point.y, this, 'setPoint');\n\t},\n\n\tsetPoint: function() {\n\t\tvar point = Point.read(arguments);\n\t\tthis.translate(point.subtract(this._matrix.getTranslation()));\n\t},\n\n\t_draw: function(ctx, param, viewMatrix) {\n\t\tif (!this._content)\n\t\t\treturn;\n\t\tthis._setStyles(ctx, param, viewMatrix);\n\t\tvar lines = this._lines,\n\t\t\tstyle = this._style,\n\t\t\thasFill = style.hasFill(),\n\t\t\thasStroke = style.hasStroke(),\n\t\t\tleading = style.getLeading(),\n\t\t\tshadowColor = ctx.shadowColor;\n\t\tctx.font = style.getFontStyle();\n\t\tctx.textAlign = style.getJustification();\n\t\tfor (var i = 0, l = lines.length; i < l; i++) {\n\t\t\tctx.shadowColor = shadowColor;\n\t\t\tvar line = lines[i];\n\t\t\tif (hasFill) {\n\t\t\t\tctx.fillText(line, 0, 0);\n\t\t\t\tctx.shadowColor = 'rgba(0,0,0,0)';\n\t\t\t}\n\t\t\tif (hasStroke)\n\t\t\t\tctx.strokeText(line, 0, 0);\n\t\t\tctx.translate(0, leading);\n\t\t}\n\t},\n\n\t_getBounds: function(matrix, options) {\n\t\tvar rect = options.drawnTextBounds ? this._getDrawnTextSize() : this._getMeasuredTextSize();\n\t\treturn matrix ? matrix._transformBounds(rect, rect) : rect;\n\t},\n\n\t_getMeasuredTextSize: function() {\n\t\tvar style = this._style,\n\t\t\tlines = this._lines,\n\t\t\tnumLines = lines.length,\n\t\t\tjustification = style.getJustification(),\n\t\t\tleading = style.getLeading(),\n\t\t\twidth = this.getView().getTextWidth(style.getFontStyle(), lines),\n\t\t\tx = 0;\n\t\tif (justification !== 'left')\n\t\t\tx -= width / (justification === 'center' ? 2: 1);\n\t\treturn new Rectangle(x,\n\t\t\t\t\tnumLines ? - 0.75 * leading : 0,\n\t\t\t\t\twidth, numLines * leading);\n\t},\n\n\t_getDrawnTextSize: function() {\n\t\tvar style = this._style;\n\t\tvar lines = this._lines;\n\t\tvar numLines = lines.length;\n\t\tvar leading = style.getLeading();\n\t\tvar justification = style.getJustification();\n\n\t\tvar svg = SvgElement.create('svg', {\n\t\t\t\t\tversion: '1.1',\n\t\t\t\t\txmlns: SvgElement.svg\n\t\t\t\t});\n\t\tvar node = SvgElement.create('text');\n\t\tnode.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');\n\t\tsvg.appendChild(node);\n\t\tfor (var i = 0; i < numLines; i++) {\n\t\t\tvar tspanNode = SvgElement.create('tspan', {\n\t\t\t\tx: '0',\n\t\t\t\tdy: i === 0 ? '0' : leading + 'px'\n\t\t\t});\n\t\t\ttspanNode.textContent = this._lines[i];\n\t\t\tnode.appendChild(tspanNode);\n\t\t}\n\n\t\tvar element = document.createElement('span');\n\t\telement.style.visibility = ('hidden');\n\t\telement.style.whiteSpace = 'pre';\n\t\telement.style.fontSize = this.fontSize + 'px';\n\t\telement.style.fontFamily = this.font;\n\t\telement.style.lineHeight = this.leading / this.fontSize;\n\n\t\tvar bbox;\n\t\ttry {\n\t\t\telement.appendChild(svg);\n\t\t\tdocument.body.appendChild(element);\n\t\t\tbbox = svg.getBBox();\n\t\t} finally {\n\t\t\tdocument.body.removeChild(element);\n\t\t}\n\n\t\tvar halfStrokeWidth = this.strokeWidth / 2;\n\t\tvar width = bbox.width + (halfStrokeWidth * 2);\n\t\tvar height = bbox.height + (halfStrokeWidth * 2);\n\t\tvar x = bbox.x - halfStrokeWidth;\n\t\tvar y = bbox.y - halfStrokeWidth;\n\n\t\tif (justification !== 'left') {\n\t\t\tvar eltWidth = this.getView().getTextWidth(style.getFontStyle(), lines);\n\t\t\tx -= eltWidth / (justification === 'center' ? 2: 1);\n\t\t}\n\n\t\treturn new Rectangle(x, y, width + 1, Math.max(height, numLines * leading));\n\t},\n\n\t_hitTestSelf: function(point, options) {\n\t\tif (options.fill && (this.hasFill() || options.hitUnfilledPaths) && this._contains(point))\n\t\t\treturn new HitResult('fill', this);\n\t}\n});\n\nvar Color = Base.extend(new function() {\n\tvar types = {\n\t\tgray: ['gray'],\n\t\trgb: ['red', 'green', 'blue'],\n\t\thsb: ['hue', 'saturation', 'brightness'],\n\t\thsl: ['hue', 'saturation', 'lightness'],\n\t\tgradient: ['gradient', 'origin', 'destination', 'highlight']\n\t};\n\n\tvar componentParsers = {},\n\t\tnamedColors = {\n\t\t\ttransparent: [0, 0, 0, 0]\n\t\t},\n\t\tcolorCtx;\n\n\tfunction fromCSS(string) {\n\t\tvar match = string.match(\n\t\t\t\t/^#([\\da-f]{2})([\\da-f]{2})([\\da-f]{2})([\\da-f]{2})?$/i\n\t\t\t) || string.match(\n\t\t\t\t/^#([\\da-f])([\\da-f])([\\da-f])([\\da-f])?$/i\n\t\t\t),\n\t\t\ttype = 'rgb',\n\t\t\tcomponents;\n\t\tif (match) {\n\t\t\tvar amount = match[4] ? 4 : 3;\n\t\t\tcomponents = new Array(amount);\n\t\t\tfor (var i = 0; i < amount; i++) {\n\t\t\t\tvar value = match[i + 1];\n\t\t\t\tcomponents[i] = parseInt(value.length == 1\n\t\t\t\t\t\t? value + value : value, 16) / 255;\n\t\t\t}\n\t\t} else if (match = string.match(/^(rgb|hsl)a?\\((.*)\\)$/)) {\n\t\t\ttype = match[1];\n\t\t\tcomponents = match[2].trim().split(/[,\\s]+/g);\n\t\t\tvar isHSL = type === 'hsl';\n\t\t\tfor (var i = 0, l = Math.min(components.length, 4); i < l; i++) {\n\t\t\t\tvar component = components[i];\n\t\t\t\tvar value = parseFloat(component);\n\t\t\t\tif (isHSL) {\n\t\t\t\t\tif (i === 0) {\n\t\t\t\t\t\tvar unit = component.match(/([a-z]*)$/)[1];\n\t\t\t\t\t\tvalue *= ({\n\t\t\t\t\t\t\tturn: 360,\n\t\t\t\t\t\t\trad: 180 / Math.PI,\n\t\t\t\t\t\t\tgrad: 0.9\n\t\t\t\t\t\t}[unit] || 1);\n\t\t\t\t\t} else if (i < 3) {\n\t\t\t\t\t\tvalue /= 100;\n\t\t\t\t\t}\n\t\t\t\t} else if (i < 3) {\n\t\t\t\t\tvalue /= /%$/.test(component) ? 100 : 255;\n\t\t\t\t}\n\t\t\t\tcomponents[i] = value;\n\t\t\t}\n\t\t} else {\n\t\t\tvar color = namedColors[string];\n\t\t\tif (!color) {\n\t\t\t\tif (window) {\n\t\t\t\t\tif (!colorCtx) {\n\t\t\t\t\t\tcolorCtx = CanvasProvider.getContext(1, 1);\n\t\t\t\t\t\tcolorCtx.globalCompositeOperation = 'copy';\n\t\t\t\t\t}\n\t\t\t\t\tcolorCtx.fillStyle = 'rgba(0,0,0,0)';\n\t\t\t\t\tcolorCtx.fillStyle = string;\n\t\t\t\t\tcolorCtx.fillRect(0, 0, 1, 1);\n\t\t\t\t\tvar data = colorCtx.getImageData(0, 0, 1, 1).data;\n\t\t\t\t\tcolor = namedColors[string] = [\n\t\t\t\t\t\tdata[0] / 255,\n\t\t\t\t\t\tdata[1] / 255,\n\t\t\t\t\t\tdata[2] / 255\n\t\t\t\t\t];\n\t\t\t\t} else {\n\t\t\t\t\tcolor = [0, 0, 0];\n\t\t\t\t}\n\t\t\t}\n\t\t\tcomponents = color.slice();\n\t\t}\n\t\treturn [type, components];\n\t}\n\n\tvar hsbIndices = [\n\t\t[0, 3, 1],\n\t\t[2, 0, 1],\n\t\t[1, 0, 3],\n\t\t[1, 2, 0],\n\t\t[3, 1, 0],\n\t\t[0, 1, 2]\n\t];\n\n\tvar converters = {\n\t\t'rgb-hsb': function(r, g, b) {\n\t\t\tvar max = Math.max(r, g, b),\n\t\t\t\tmin = Math.min(r, g, b),\n\t\t\t\tdelta = max - min,\n\t\t\t\th = delta === 0 ? 0\n\t\t\t\t\t: ( max == r ? (g - b) / delta + (g < b ? 6 : 0)\n\t\t\t\t\t\t: max == g ? (b - r) / delta + 2\n\t\t\t\t\t\t: (r - g) / delta + 4) * 60;\n\t\t\treturn [h, max === 0 ? 0 : delta / max, max];\n\t\t},\n\n\t\t'hsb-rgb': function(h, s, b) {\n\t\t\th = (((h / 60) % 6) + 6) % 6;\n\t\t\tvar i = Math.floor(h),\n\t\t\t\tf = h - i,\n\t\t\t\ti = hsbIndices[i],\n\t\t\t\tv = [\n\t\t\t\t\tb,\n\t\t\t\t\tb * (1 - s),\n\t\t\t\t\tb * (1 - s * f),\n\t\t\t\t\tb * (1 - s * (1 - f))\n\t\t\t\t];\n\t\t\treturn [v[i[0]], v[i[1]], v[i[2]]];\n\t\t},\n\n\t\t'rgb-hsl': function(r, g, b) {\n\t\t\tvar max = Math.max(r, g, b),\n\t\t\t\tmin = Math.min(r, g, b),\n\t\t\t\tdelta = max - min,\n\t\t\t\tachromatic = delta === 0,\n\t\t\t\th = achromatic ? 0\n\t\t\t\t\t: ( max == r ? (g - b) / delta + (g < b ? 6 : 0)\n\t\t\t\t\t\t: max == g ? (b - r) / delta + 2\n\t\t\t\t\t\t: (r - g) / delta + 4) * 60,\n\t\t\t\tl = (max + min) / 2,\n\t\t\t\ts = achromatic ? 0 : l < 0.5\n\t\t\t\t\t\t? delta / (max + min)\n\t\t\t\t\t\t: delta / (2 - max - min);\n\t\t\treturn [h, s, l];\n\t\t},\n\n\t\t'hsl-rgb': function(h, s, l) {\n\t\t\th = (((h / 360) % 1) + 1) % 1;\n\t\t\tif (s === 0)\n\t\t\t\treturn [l, l, l];\n\t\t\tvar t3s = [ h + 1 / 3, h, h - 1 / 3 ],\n\t\t\t\tt2 = l < 0.5 ? l * (1 + s) : l + s - l * s,\n\t\t\t\tt1 = 2 * l - t2,\n\t\t\t\tc = [];\n\t\t\tfor (var i = 0; i < 3; i++) {\n\t\t\t\tvar t3 = t3s[i];\n\t\t\t\tif (t3 < 0) t3 += 1;\n\t\t\t\tif (t3 > 1) t3 -= 1;\n\t\t\t\tc[i] = 6 * t3 < 1\n\t\t\t\t\t? t1 + (t2 - t1) * 6 * t3\n\t\t\t\t\t: 2 * t3 < 1\n\t\t\t\t\t\t? t2\n\t\t\t\t\t\t: 3 * t3 < 2\n\t\t\t\t\t\t\t? t1 + (t2 - t1) * ((2 / 3) - t3) * 6\n\t\t\t\t\t\t\t: t1;\n\t\t\t}\n\t\t\treturn c;\n\t\t},\n\n\t\t'rgb-gray': function(r, g, b) {\n\t\t\treturn [r * 0.2989 + g * 0.587 + b * 0.114];\n\t\t},\n\n\t\t'gray-rgb': function(g) {\n\t\t\treturn [g, g, g];\n\t\t},\n\n\t\t'gray-hsb': function(g) {\n\t\t\treturn [0, 0, g];\n\t\t},\n\n\t\t'gray-hsl': function(g) {\n\t\t\treturn [0, 0, g];\n\t\t},\n\n\t\t'gradient-rgb': function() {\n\t\t\treturn [];\n\t\t},\n\n\t\t'rgb-gradient': function() {\n\t\t\treturn [];\n\t\t}\n\n\t};\n\n\treturn Base.each(types, function(properties, type) {\n\t\tcomponentParsers[type] = [];\n\t\tBase.each(properties, function(name, index) {\n\t\t\tvar part = Base.capitalize(name),\n\t\t\t\thasOverlap = /^(hue|saturation)$/.test(name),\n\t\t\t\tparser = componentParsers[type][index] = type === 'gradient'\n\t\t\t\t\t? name === 'gradient'\n\t\t\t\t\t\t? function(value) {\n\t\t\t\t\t\t\tvar current = this._components[0];\n\t\t\t\t\t\t\tvalue = Gradient.read(\n\t\t\t\t\t\t\t\tArray.isArray(value)\n\t\t\t\t\t\t\t\t\t? value\n\t\t\t\t\t\t\t\t\t: arguments, 0, { readNull: true }\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (current !== value) {\n\t\t\t\t\t\t\t\tif (current)\n\t\t\t\t\t\t\t\t\tcurrent._removeOwner(this);\n\t\t\t\t\t\t\t\tif (value)\n\t\t\t\t\t\t\t\t\tvalue._addOwner(this);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t: function() {\n\t\t\t\t\t\t\treturn Point.read(arguments, 0, {\n\t\t\t\t\t\t\t\t\treadNull: name === 'highlight',\n\t\t\t\t\t\t\t\t\tclone: true\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t: function(value) {\n\t\t\t\t\t\treturn value == null || isNaN(value) ? 0 : +value;\n\t\t\t\t\t};\n\t\t\tthis['get' + part] = function() {\n\t\t\t\treturn this._type === type\n\t\t\t\t\t|| hasOverlap && /^hs[bl]$/.test(this._type)\n\t\t\t\t\t\t? this._components[index]\n\t\t\t\t\t\t: this._convert(type)[index];\n\t\t\t};\n\n\t\t\tthis['set' + part] = function(value) {\n\t\t\t\tif (this._type !== type\n\t\t\t\t\t\t&& !(hasOverlap && /^hs[bl]$/.test(this._type))) {\n\t\t\t\t\tthis._components = this._convert(type);\n\t\t\t\t\tthis._properties = types[type];\n\t\t\t\t\tthis._type = type;\n\t\t\t\t}\n\t\t\t\tthis._components[index] = parser.call(this, value);\n\t\t\t\tthis._changed();\n\t\t\t};\n\t\t}, this);\n\t}, {\n\t\t_class: 'Color',\n\t\t_readIndex: true,\n\n\t\tinitialize: function Color(arg) {\n\t\t\tvar args = arguments,\n\t\t\t\treading = this.__read,\n\t\t\t\tread = 0,\n\t\t\t\ttype,\n\t\t\t\tcomponents,\n\t\t\t\talpha,\n\t\t\t\tvalues;\n\t\t\tif (Array.isArray(arg)) {\n\t\t\t\targs = arg;\n\t\t\t\targ = args[0];\n\t\t\t}\n\t\t\tvar argType = arg != null && typeof arg;\n\t\t\tif (argType === 'string' && arg in types) {\n\t\t\t\ttype = arg;\n\t\t\t\targ = args[1];\n\t\t\t\tif (Array.isArray(arg)) {\n\t\t\t\t\tcomponents = arg;\n\t\t\t\t\talpha = args[2];\n\t\t\t\t} else {\n\t\t\t\t\tif (reading)\n\t\t\t\t\t\tread = 1;\n\t\t\t\t\targs = Base.slice(args, 1);\n\t\t\t\t\targType = typeof arg;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!components) {\n\t\t\t\tvalues = argType === 'number'\n\t\t\t\t\t\t? args\n\t\t\t\t\t\t: argType === 'object' && arg.length != null\n\t\t\t\t\t\t\t? arg\n\t\t\t\t\t\t\t: null;\n\t\t\t\tif (values) {\n\t\t\t\t\tif (!type)\n\t\t\t\t\t\ttype = values.length >= 3\n\t\t\t\t\t\t\t\t? 'rgb'\n\t\t\t\t\t\t\t\t: 'gray';\n\t\t\t\t\tvar length = types[type].length;\n\t\t\t\t\talpha = values[length];\n\t\t\t\t\tif (reading) {\n\t\t\t\t\t\tread += values === arguments\n\t\t\t\t\t\t\t? length + (alpha != null ? 1 : 0)\n\t\t\t\t\t\t\t: 1;\n\t\t\t\t\t}\n\t\t\t\t\tif (values.length > length)\n\t\t\t\t\t\tvalues = Base.slice(values, 0, length);\n\t\t\t\t} else if (argType === 'string') {\n\t\t\t\t\tvar converted = fromCSS(arg);\n\t\t\t\t\ttype = converted[0];\n\t\t\t\t\tcomponents = converted[1];\n\t\t\t\t\tif (components.length === 4) {\n\t\t\t\t\t\talpha = components[3];\n\t\t\t\t\t\tcomponents.length--;\n\t\t\t\t\t}\n\t\t\t\t} else if (argType === 'object') {\n\t\t\t\t\tif (arg.constructor === Color) {\n\t\t\t\t\t\ttype = arg._type;\n\t\t\t\t\t\tcomponents = arg._components.slice();\n\t\t\t\t\t\talpha = arg._alpha;\n\t\t\t\t\t\tif (type === 'gradient') {\n\t\t\t\t\t\t\tfor (var i = 1, l = components.length; i < l; i++) {\n\t\t\t\t\t\t\t\tvar point = components[i];\n\t\t\t\t\t\t\t\tif (point)\n\t\t\t\t\t\t\t\t\tcomponents[i] = point.clone();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (arg.constructor === Gradient) {\n\t\t\t\t\t\ttype = 'gradient';\n\t\t\t\t\t\tvalues = args;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttype = 'hue' in arg\n\t\t\t\t\t\t\t? 'lightness' in arg\n\t\t\t\t\t\t\t\t? 'hsl'\n\t\t\t\t\t\t\t\t: 'hsb'\n\t\t\t\t\t\t\t: 'gradient' in arg || 'stops' in arg\n\t\t\t\t\t\t\t\t\t|| 'radial' in arg\n\t\t\t\t\t\t\t\t? 'gradient'\n\t\t\t\t\t\t\t\t: 'gray' in arg\n\t\t\t\t\t\t\t\t\t? 'gray'\n\t\t\t\t\t\t\t\t\t: 'rgb';\n\t\t\t\t\t\tvar properties = types[type],\n\t\t\t\t\t\t\tparsers = componentParsers[type];\n\t\t\t\t\t\tthis._components = components = [];\n\t\t\t\t\t\tfor (var i = 0, l = properties.length; i < l; i++) {\n\t\t\t\t\t\t\tvar value = arg[properties[i]];\n\t\t\t\t\t\t\tif (value == null && !i && type === 'gradient'\n\t\t\t\t\t\t\t\t\t&& 'stops' in arg) {\n\t\t\t\t\t\t\t\tvalue = {\n\t\t\t\t\t\t\t\t\tstops: arg.stops,\n\t\t\t\t\t\t\t\t\tradial: arg.radial\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvalue = parsers[i].call(this, value);\n\t\t\t\t\t\t\tif (value != null)\n\t\t\t\t\t\t\t\tcomponents[i] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t\talpha = arg.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (reading && type)\n\t\t\t\t\tread = 1;\n\t\t\t}\n\t\t\tthis._type = type || 'rgb';\n\t\t\tif (!components) {\n\t\t\t\tthis._components = components = [];\n\t\t\t\tvar parsers = componentParsers[this._type];\n\t\t\t\tfor (var i = 0, l = parsers.length; i < l; i++) {\n\t\t\t\t\tvar value = parsers[i].call(this, values && values[i]);\n\t\t\t\t\tif (value != null)\n\t\t\t\t\t\tcomponents[i] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._components = components;\n\t\t\tthis._properties = types[this._type];\n\t\t\tthis._alpha = alpha;\n\t\t\tif (reading)\n\t\t\t\tthis.__read = read;\n\t\t\treturn this;\n\t\t},\n\n\t\tset: '#initialize',\n\n\t\t_serialize: function(options, dictionary) {\n\t\t\tvar components = this.getComponents();\n\t\t\treturn Base.serialize(\n\t\t\t\t\t/^(gray|rgb)$/.test(this._type)\n\t\t\t\t\t\t? components\n\t\t\t\t\t\t: [this._type].concat(components),\n\t\t\t\t\toptions, true, dictionary);\n\t\t},\n\n\t\t_changed: function() {\n\t\t\tthis._canvasStyle = null;\n\t\t\tif (this._owner) {\n\t\t\t\tif (this._setter) {\n\t\t\t\t\tthis._owner[this._setter](this);\n\t\t\t\t} else {\n\t\t\t\t\tthis._owner._changed(129);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t_convert: function(type) {\n\t\t\tvar converter;\n\t\t\treturn this._type === type\n\t\t\t\t\t? this._components.slice()\n\t\t\t\t\t: (converter = converters[this._type + '-' + type])\n\t\t\t\t\t\t? converter.apply(this, this._components)\n\t\t\t\t\t\t: converters['rgb-' + type].apply(this,\n\t\t\t\t\t\t\tconverters[this._type + '-rgb'].apply(this,\n\t\t\t\t\t\t\t\tthis._components));\n\t\t},\n\n\t\tconvert: function(type) {\n\t\t\treturn new Color(type, this._convert(type), this._alpha);\n\t\t},\n\n\t\tgetType: function() {\n\t\t\treturn this._type;\n\t\t},\n\n\t\tsetType: function(type) {\n\t\t\tthis._components = this._convert(type);\n\t\t\tthis._properties = types[type];\n\t\t\tthis._type = type;\n\t\t},\n\n\t\tgetComponents: function() {\n\t\t\tvar components = this._components.slice();\n\t\t\tif (this._alpha != null)\n\t\t\t\tcomponents.push(this._alpha);\n\t\t\treturn components;\n\t\t},\n\n\t\tgetAlpha: function() {\n\t\t\treturn this._alpha != null ? this._alpha : 1;\n\t\t},\n\n\t\tsetAlpha: function(alpha) {\n\t\t\tthis._alpha = alpha == null ? null : Math.min(Math.max(alpha, 0), 1);\n\t\t\tthis._changed();\n\t\t},\n\n\t\thasAlpha: function() {\n\t\t\treturn this._alpha != null;\n\t\t},\n\n\t\tequals: function(color) {\n\t\t\tvar col = Base.isPlainValue(color, true)\n\t\t\t\t\t? Color.read(arguments)\n\t\t\t\t\t: color;\n\t\t\treturn col === this || col && this._class === col._class\n\t\t\t\t\t&& this._type === col._type\n\t\t\t\t\t&& this.getAlpha() === col.getAlpha()\n\t\t\t\t\t&& Base.equals(this._components, col._components)\n\t\t\t\t\t|| false;\n\t\t},\n\n\t\ttoString: function() {\n\t\t\tvar properties = this._properties,\n\t\t\t\tparts = [],\n\t\t\t\tisGradient = this._type === 'gradient',\n\t\t\t\tf = Formatter.instance;\n\t\t\tfor (var i = 0, l = properties.length; i < l; i++) {\n\t\t\t\tvar value = this._components[i];\n\t\t\t\tif (value != null)\n\t\t\t\t\tparts.push(properties[i] + ': '\n\t\t\t\t\t\t\t+ (isGradient ? value : f.number(value)));\n\t\t\t}\n\t\t\tif (this._alpha != null)\n\t\t\t\tparts.push('alpha: ' + f.number(this._alpha));\n\t\t\treturn '{ ' + parts.join(', ') + ' }';\n\t\t},\n\n\t\ttoCSS: function(hex) {\n\t\t\tvar components = this._convert('rgb'),\n\t\t\t\talpha = hex || this._alpha == null ? 1 : this._alpha;\n\t\t\tfunction convert(val) {\n\t\t\t\treturn Math.round((val < 0 ? 0 : val > 1 ? 1 : val) * 255);\n\t\t\t}\n\t\t\tcomponents = [\n\t\t\t\tconvert(components[0]),\n\t\t\t\tconvert(components[1]),\n\t\t\t\tconvert(components[2])\n\t\t\t];\n\t\t\tif (alpha < 1)\n\t\t\t\tcomponents.push(alpha < 0 ? 0 : alpha);\n\t\t\treturn hex\n\t\t\t\t\t? '#' + ((1 << 24) + (components[0] << 16)\n\t\t\t\t\t\t+ (components[1] << 8)\n\t\t\t\t\t\t+ components[2]).toString(16).slice(1)\n\t\t\t\t\t: (components.length == 4 ? 'rgba(' : 'rgb(')\n\t\t\t\t\t\t+ components.join(',') + ')';\n\t\t},\n\n\t\ttoCanvasStyle: function(ctx, matrix, strokeMatrix) {\n\t\t\tvar strokeMayChange = this._type === 'gradient' && strokeMatrix;\n\t\t\tif (this._canvasStyle && !strokeMayChange)\n\t\t\t\treturn this._canvasStyle;\n\t\t\tif (this._type !== 'gradient')\n\t\t\t\treturn this._canvasStyle = this.toCSS();\n\t\t\tvar components = this._components,\n\t\t\t\tgradient = components[0],\n\t\t\t\tstops = gradient._stops,\n\t\t\t\torigin = components[1],\n\t\t\t\tdestination = components[2],\n\t\t\t\thighlight = components[3],\n\t\t\t\tinverse = matrix && matrix.inverted(),\n\t\t\t\tcanvasGradient;\n\t\t\tif (inverse) {\n\t\t\t\torigin = inverse._transformPoint(origin);\n\t\t\t\tdestination = inverse._transformPoint(destination);\n\t\t\t\tif (highlight)\n\t\t\t\t\thighlight = inverse._transformPoint(highlight);\n\t\t\t}\n\t\t\tif (strokeMatrix) {\n\t\t\t\torigin = strokeMatrix._transformPoint(origin);\n\t\t\t\tdestination = strokeMatrix._transformPoint(destination);\n\t\t\t\tif (highlight)\n\t\t\t\t\thighlight = strokeMatrix._transformPoint(highlight);\n\t\t\t}\n\t\t\tif (gradient._radial) {\n\t\t\t\tvar radius = destination.getDistance(origin);\n\t\t\t\tif (highlight) {\n\t\t\t\t\tvar vector = highlight.subtract(origin);\n\t\t\t\t\tif (vector.getLength() > radius)\n\t\t\t\t\t\thighlight = origin.add(vector.normalize(radius - 0.1));\n\t\t\t\t}\n\t\t\t\tvar start = highlight || origin;\n\t\t\t\tcanvasGradient = ctx.createRadialGradient(start.x, start.y,\n\t\t\t\t\t\t0, origin.x, origin.y, radius);\n\t\t\t} else {\n\t\t\t\tcanvasGradient = ctx.createLinearGradient(origin.x, origin.y,\n\t\t\t\t\t\tdestination.x, destination.y);\n\t\t\t}\n\t\t\tfor (var i = 0, l = stops.length; i < l; i++) {\n\t\t\t\tvar stop = stops[i],\n\t\t\t\t\toffset = stop._offset;\n\t\t\t\tcanvasGradient.addColorStop(\n\t\t\t\t\t\toffset == null ? i / (l - 1) : offset,\n\t\t\t\t\t\tstop._color.toCanvasStyle());\n\t\t\t}\n\t\t\tif (!strokeMayChange) this._canvasStyle = canvasGradient;\n\t\t\treturn canvasGradient;\n\t\t},\n\n\t\ttransform: function(matrix) {\n\t\t\tif (this._type === 'gradient') {\n\t\t\t\tvar components = this._components;\n\t\t\t\tfor (var i = 1, l = components.length; i < l; i++) {\n\t\t\t\t\tvar point = components[i];\n\t\t\t\t\tmatrix._transformPoint(point, point, true);\n\t\t\t\t}\n\t\t\t\tthis._changed();\n\t\t\t}\n\t\t},\n\n\t\tstatics: {\n\t\t\t_types: types,\n\n\t\t\trandom: function() {\n\t\t\t\tvar random = Math.random;\n\t\t\t\treturn new Color(random(), random(), random());\n\t\t\t},\n\n\t\t\t_setOwner: function(color, owner, setter) {\n\t\t\t\tif (color) {\n\t\t\t\t\tif (color._owner && owner && color._owner !== owner) {\n\t\t\t\t\t\tcolor = color.clone();\n\t\t\t\t\t}\n\t\t\t\t\tif (!color._owner ^ !owner) {\n\t\t\t\t\t\tcolor._owner = owner || null;\n\t\t\t\t\t\tcolor._setter = setter || null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn color;\n\t\t\t}\n\t\t}\n\t});\n},\nnew function() {\n\tvar operators = {\n\t\tadd: function(a, b) {\n\t\t\treturn a + b;\n\t\t},\n\n\t\tsubtract: function(a, b) {\n\t\t\treturn a - b;\n\t\t},\n\n\t\tmultiply: function(a, b) {\n\t\t\treturn a * b;\n\t\t},\n\n\t\tdivide: function(a, b) {\n\t\t\treturn a / b;\n\t\t}\n\t};\n\n\treturn Base.each(operators, function(operator, name) {\n\t\tthis[name] = function(color) {\n\t\t\tcolor = Color.read(arguments);\n\t\t\tvar type = this._type,\n\t\t\t\tcomponents1 = this._components,\n\t\t\t\tcomponents2 = color._convert(type);\n\t\t\tfor (var i = 0, l = components1.length; i < l; i++)\n\t\t\t\tcomponents2[i] = operator(components1[i], components2[i]);\n\t\t\treturn new Color(type, components2,\n\t\t\t\t\tthis._alpha != null\n\t\t\t\t\t\t\t? operator(this._alpha, color.getAlpha())\n\t\t\t\t\t\t\t: null);\n\t\t};\n\t}, {\n\t});\n});\n\nvar Gradient = Base.extend({\n\t_class: 'Gradient',\n\n\tinitialize: function Gradient(stops, radial) {\n\t\tthis._id = UID.get();\n\t\tif (stops && Base.isPlainObject(stops)) {\n\t\t\tthis.set(stops);\n\t\t\tstops = radial = null;\n\t\t}\n\t\tif (this._stops == null) {\n\t\t\tthis.setStops(stops || ['white', 'black']);\n\t\t}\n\t\tif (this._radial == null) {\n\t\t\tthis.setRadial(typeof radial === 'string' && radial === 'radial'\n\t\t\t\t\t|| radial || false);\n\t\t}\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\treturn dictionary.add(this, function() {\n\t\t\treturn Base.serialize([this._stops, this._radial],\n\t\t\t\t\toptions, true, dictionary);\n\t\t});\n\t},\n\n\t_changed: function() {\n\t\tfor (var i = 0, l = this._owners && this._owners.length; i < l; i++) {\n\t\t\tthis._owners[i]._changed();\n\t\t}\n\t},\n\n\t_addOwner: function(color) {\n\t\tif (!this._owners)\n\t\t\tthis._owners = [];\n\t\tthis._owners.push(color);\n\t},\n\n\t_removeOwner: function(color) {\n\t\tvar index = this._owners ? this._owners.indexOf(color) : -1;\n\t\tif (index != -1) {\n\t\t\tthis._owners.splice(index, 1);\n\t\t\tif (!this._owners.length)\n\t\t\t\tthis._owners = undefined;\n\t\t}\n\t},\n\n\tclone: function() {\n\t\tvar stops = [];\n\t\tfor (var i = 0, l = this._stops.length; i < l; i++) {\n\t\t\tstops[i] = this._stops[i].clone();\n\t\t}\n\t\treturn new Gradient(stops, this._radial);\n\t},\n\n\tgetStops: function() {\n\t\treturn this._stops;\n\t},\n\n\tsetStops: function(stops) {\n\t\tif (stops.length < 2) {\n\t\t\tthrow new Error(\n\t\t\t\t\t'Gradient stop list needs to contain at least two stops.');\n\t\t}\n\t\tvar _stops = this._stops;\n\t\tif (_stops) {\n\t\t\tfor (var i = 0, l = _stops.length; i < l; i++)\n\t\t\t\t_stops[i]._owner = undefined;\n\t\t}\n\t\t_stops = this._stops = GradientStop.readList(stops, 0, { clone: true });\n\t\tfor (var i = 0, l = _stops.length; i < l; i++)\n\t\t\t_stops[i]._owner = this;\n\t\tthis._changed();\n\t},\n\n\tgetRadial: function() {\n\t\treturn this._radial;\n\t},\n\n\tsetRadial: function(radial) {\n\t\tthis._radial = radial;\n\t\tthis._changed();\n\t},\n\n\tequals: function(gradient) {\n\t\tif (gradient === this)\n\t\t\treturn true;\n\t\tif (gradient && this._class === gradient._class) {\n\t\t\tvar stops1 = this._stops,\n\t\t\t\tstops2 = gradient._stops,\n\t\t\t\tlength = stops1.length;\n\t\t\tif (length === stops2.length) {\n\t\t\t\tfor (var i = 0; i < length; i++) {\n\t\t\t\t\tif (!stops1[i].equals(stops2[i]))\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n});\n\nvar GradientStop = Base.extend({\n\t_class: 'GradientStop',\n\n\tinitialize: function GradientStop(arg0, arg1) {\n\t\tvar color = arg0,\n\t\t\toffset = arg1;\n\t\tif (typeof arg0 === 'object' && arg1 === undefined) {\n\t\t\tif (Array.isArray(arg0) && typeof arg0[0] !== 'number') {\n\t\t\t\tcolor = arg0[0];\n\t\t\t\toffset = arg0[1];\n\t\t\t} else if ('color' in arg0 || 'offset' in arg0\n\t\t\t\t\t|| 'rampPoint' in arg0) {\n\t\t\t\tcolor = arg0.color;\n\t\t\t\toffset = arg0.offset || arg0.rampPoint || 0;\n\t\t\t}\n\t\t}\n\t\tthis.setColor(color);\n\t\tthis.setOffset(offset);\n\t},\n\n\tclone: function() {\n\t\treturn new GradientStop(this._color.clone(), this._offset);\n\t},\n\n\t_serialize: function(options, dictionary) {\n\t\tvar color = this._color,\n\t\t\toffset = this._offset;\n\t\treturn Base.serialize(offset == null ? [color] : [color, offset],\n\t\t\t\toptions, true, dictionary);\n\t},\n\n\t_changed: function() {\n\t\tif (this._owner)\n\t\t\tthis._owner._changed(129);\n\t},\n\n\tgetOffset: function() {\n\t\treturn this._offset;\n\t},\n\n\tsetOffset: function(offset) {\n\t\tthis._offset = offset;\n\t\tthis._changed();\n\t},\n\n\tgetRampPoint: '#getOffset',\n\tsetRampPoint: '#setOffset',\n\n\tgetColor: function() {\n\t\treturn this._color;\n\t},\n\n\tsetColor: function() {\n\t\tColor._setOwner(this._color, null);\n\t\tthis._color = Color._setOwner(Color.read(arguments, 0), this,\n\t\t\t\t'setColor');\n\t\tthis._changed();\n\t},\n\n\tequals: function(stop) {\n\t\treturn stop === this || stop && this._class === stop._class\n\t\t\t\t&& this._color.equals(stop._color)\n\t\t\t\t&& this._offset == stop._offset\n\t\t\t\t|| false;\n\t}\n});\n\nvar Style = Base.extend(new function() {\n\tvar itemDefaults = {\n\t\tfillColor: null,\n\t\tfillRule: 'nonzero',\n\t\tstrokeColor: null,\n\t\tstrokeWidth: 1,\n\t\tstrokeCap: 'butt',\n\t\tstrokeJoin: 'miter',\n\t\tstrokeScaling: true,\n\t\tmiterLimit: 10,\n\t\tdashOffset: 0,\n\t\tdashArray: [],\n\t\tshadowColor: null,\n\t\tshadowBlur: 0,\n\t\tshadowOffset: new Point(),\n\t\tselectedColor: null\n\t},\n\tgroupDefaults = Base.set({}, itemDefaults, {\n\t\tfontFamily: 'sans-serif',\n\t\tfontWeight: 'normal',\n\t\tfontSize: 12,\n\t\tleading: null,\n\t\tjustification: 'left'\n\t}),\n\ttextDefaults = Base.set({}, groupDefaults, {\n\t\tfillColor: new Color()\n\t}),\n\tflags = {\n\t\tstrokeWidth: 193,\n\t\tstrokeCap: 193,\n\t\tstrokeJoin: 193,\n\t\tstrokeScaling: 201,\n\t\tmiterLimit: 193,\n\t\tfontFamily: 9,\n\t\tfontWeight: 9,\n\t\tfontSize: 9,\n\t\tfont: 9,\n\t\tleading: 9,\n\t\tjustification: 9\n\t},\n\titem = {\n\t\tbeans: true\n\t},\n\tfields = {\n\t\t_class: 'Style',\n\t\tbeans: true,\n\n\t\tinitialize: function Style(style, _owner, _project) {\n\t\t\tthis._values = {};\n\t\t\tthis._owner = _owner;\n\t\t\tthis._project = _owner && _owner._project || _project\n\t\t\t\t\t|| paper.project;\n\t\t\tthis._defaults = !_owner || _owner instanceof Group ? groupDefaults\n\t\t\t\t\t: _owner instanceof TextItem ? textDefaults\n\t\t\t\t\t: itemDefaults;\n\t\t\tif (style)\n\t\t\t\tthis.set(style);\n\t\t}\n\t};\n\n\tBase.each(groupDefaults, function(value, key) {\n\t\tvar isColor = /Color$/.test(key),\n\t\t\tisPoint = key === 'shadowOffset',\n\t\t\tpart = Base.capitalize(key),\n\t\t\tflag = flags[key],\n\t\t\tset = 'set' + part,\n\t\t\tget = 'get' + part;\n\n\t\tfields[set] = function(value) {\n\t\t\tvar owner = this._owner,\n\t\t\t\tchildren = owner && owner._children,\n\t\t\t\tapplyToChildren = children && children.length > 0\n\t\t\t\t\t&& !(owner instanceof CompoundPath);\n\t\t\tif (applyToChildren) {\n\t\t\t\tfor (var i = 0, l = children.length; i < l; i++)\n\t\t\t\t\tchildren[i]._style[set](value);\n\t\t\t}\n\t\t\tif ((key === 'selectedColor' || !applyToChildren)\n\t\t\t\t\t&& key in this._defaults) {\n\t\t\t\tvar old = this._values[key];\n\t\t\t\tif (old !== value) {\n\t\t\t\t\tif (isColor) {\n\t\t\t\t\t\tif (old) {\n\t\t\t\t\t\t\tColor._setOwner(old, null);\n\t\t\t\t\t\t\told._canvasStyle = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (value && value.constructor === Color) {\n\t\t\t\t\t\t\tvalue = Color._setOwner(value, owner,\n\t\t\t\t\t\t\t\t\tapplyToChildren && set);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis._values[key] = value;\n\t\t\t\t\tif (owner)\n\t\t\t\t\t\towner._changed(flag || 129);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tfields[get] = function(_dontMerge) {\n\t\t\tvar owner = this._owner,\n\t\t\t\tchildren = owner && owner._children,\n\t\t\t\tapplyToChildren = children && children.length > 0\n\t\t\t\t\t&& !(owner instanceof CompoundPath),\n\t\t\t\tvalue;\n\t\t\tif (applyToChildren && !_dontMerge) {\n\t\t\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\t\t\tvar childValue = children[i]._style[get]();\n\t\t\t\t\tif (!i) {\n\t\t\t\t\t\tvalue = childValue;\n\t\t\t\t\t} else if (!Base.equals(value, childValue)) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (key in this._defaults) {\n\t\t\t\tvar value = this._values[key];\n\t\t\t\tif (value === undefined) {\n\t\t\t\t\tvalue = this._defaults[key];\n\t\t\t\t\tif (value && value.clone) {\n\t\t\t\t\t\tvalue = value.clone();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvar ctor = isColor ? Color : isPoint ? Point : null;\n\t\t\t\t\tif (ctor && !(value && value.constructor === ctor)) {\n\t\t\t\t\t\tthis._values[key] = value = ctor.read([value], 0,\n\t\t\t\t\t\t\t\t{ readNull: true, clone: true });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (value && isColor) {\n\t\t\t\tvalue = Color._setOwner(value, owner, applyToChildren && set);\n\t\t\t}\n\t\t\treturn value;\n\t\t};\n\n\t\titem[get] = function(_dontMerge) {\n\t\t\treturn this._style[get](_dontMerge);\n\t\t};\n\n\t\titem[set] = function(value) {\n\t\t\tthis._style[set](value);\n\t\t};\n\t});\n\n\tBase.each({\n\t\tFont: 'FontFamily',\n\t\tWindingRule: 'FillRule'\n\t}, function(value, key) {\n\t\tvar get = 'get' + key,\n\t\t\tset = 'set' + key;\n\t\tfields[get] = item[get] = '#get' + value;\n\t\tfields[set] = item[set] = '#set' + value;\n\t});\n\n\tItem.inject(item);\n\treturn fields;\n}, {\n\tset: function(style) {\n\t\tvar isStyle = style instanceof Style,\n\t\t\tvalues = isStyle ? style._values : style;\n\t\tif (values) {\n\t\t\tfor (var key in values) {\n\t\t\t\tif (key in this._defaults) {\n\t\t\t\t\tvar value = values[key];\n\t\t\t\t\tthis[key] = value && isStyle && value.clone\n\t\t\t\t\t\t\t? value.clone() : value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tequals: function(style) {\n\t\tfunction compare(style1, style2, secondary) {\n\t\t\tvar values1 = style1._values,\n\t\t\t\tvalues2 = style2._values,\n\t\t\t\tdefaults2 = style2._defaults;\n\t\t\tfor (var key in values1) {\n\t\t\t\tvar value1 = values1[key],\n\t\t\t\t\tvalue2 = values2[key];\n\t\t\t\tif (!(secondary && key in values2) && !Base.equals(value1,\n\t\t\t\t\t\tvalue2 === undefined ? defaults2[key] : value2))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\treturn style === this || style && this._class === style._class\n\t\t\t\t&& compare(this, style)\n\t\t\t\t&& compare(style, this, true)\n\t\t\t\t|| false;\n\t},\n\n\t_dispose: function() {\n\t\tvar color;\n\t\tcolor = this.getFillColor();\n\t\tif (color) color._canvasStyle = null;\n\t\tcolor = this.getStrokeColor();\n\t\tif (color) color._canvasStyle = null;\n\t\tcolor = this.getShadowColor();\n\t\tif (color) color._canvasStyle = null;\n\t},\n\n\thasFill: function() {\n\t\tvar color = this.getFillColor();\n\t\treturn !!color && color.alpha > 0;\n\t},\n\n\thasStroke: function() {\n\t\tvar color = this.getStrokeColor();\n\t\treturn !!color && color.alpha > 0 && this.getStrokeWidth() > 0;\n\t},\n\n\thasShadow: function() {\n\t\tvar color = this.getShadowColor();\n\t\treturn !!color && color.alpha > 0 && (this.getShadowBlur() > 0\n\t\t\t\t|| !this.getShadowOffset().isZero());\n\t},\n\n\tgetView: function() {\n\t\treturn this._project._view;\n\t},\n\n\tgetFontStyle: function() {\n\t\tvar fontSize = this.getFontSize();\n\t\treturn this.getFontWeight()\n\t\t\t\t+ ' ' + fontSize + (/[a-z]/i.test(fontSize + '') ? ' ' : 'px ')\n\t\t\t\t+ this.getFontFamily();\n\t},\n\n\tgetFont: '#getFontFamily',\n\tsetFont: '#setFontFamily',\n\n\tgetLeading: function getLeading() {\n\t\tvar leading = getLeading.base.call(this),\n\t\t\tfontSize = this.getFontSize();\n\t\tif (/pt|em|%|px/.test(fontSize))\n\t\t\tfontSize = this.getView().getPixelSize(fontSize);\n\t\treturn leading != null ? leading : fontSize * 1.2;\n\t}\n\n});\n\nvar DomElement = new function() {\n\tfunction handlePrefix(el, name, set, value) {\n\t\tvar prefixes = ['', 'webkit', 'moz', 'Moz', 'ms', 'o'],\n\t\t\tsuffix = name[0].toUpperCase() + name.substring(1);\n\t\tfor (var i = 0; i < 6; i++) {\n\t\t\tvar prefix = prefixes[i],\n\t\t\t\tkey = prefix ? prefix + suffix : name;\n\t\t\tif (key in el) {\n\t\t\t\tif (set) {\n\t\t\t\t\tel[key] = value;\n\t\t\t\t} else {\n\t\t\t\t\treturn el[key];\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tgetStyles: function(el) {\n\t\t\tvar doc = el && el.nodeType !== 9 ? el.ownerDocument : el,\n\t\t\t\tview = doc && doc.defaultView;\n\t\t\treturn view && view.getComputedStyle(el, '');\n\t\t},\n\n\t\tgetBounds: function(el, viewport) {\n\t\t\tvar doc = el.ownerDocument,\n\t\t\t\tbody = doc.body,\n\t\t\t\thtml = doc.documentElement,\n\t\t\t\trect;\n\t\t\ttry {\n\t\t\t\trect = el.getBoundingClientRect();\n\t\t\t} catch (e) {\n\t\t\t\trect = { left: 0, top: 0, width: 0, height: 0 };\n\t\t\t}\n\t\t\tvar x = rect.left - (html.clientLeft || body.clientLeft || 0),\n\t\t\t\ty = rect.top - (html.clientTop || body.clientTop || 0);\n\t\t\tif (!viewport) {\n\t\t\t\tvar view = doc.defaultView;\n\t\t\t\tx += view.pageXOffset || html.scrollLeft || body.scrollLeft;\n\t\t\t\ty += view.pageYOffset || html.scrollTop || body.scrollTop;\n\t\t\t}\n\t\t\treturn new Rectangle(x, y, rect.width, rect.height);\n\t\t},\n\n\t\tgetViewportBounds: function(el) {\n\t\t\tvar doc = el.ownerDocument,\n\t\t\t\tview = doc.defaultView,\n\t\t\t\thtml = doc.documentElement;\n\t\t\treturn new Rectangle(0, 0,\n\t\t\t\tview.innerWidth || html.clientWidth,\n\t\t\t\tview.innerHeight || html.clientHeight\n\t\t\t);\n\t\t},\n\n\t\tgetOffset: function(el, viewport) {\n\t\t\treturn DomElement.getBounds(el, viewport).getPoint();\n\t\t},\n\n\t\tgetSize: function(el) {\n\t\t\treturn DomElement.getBounds(el, true).getSize();\n\t\t},\n\n\t\tisInvisible: function(el) {\n\t\t\treturn DomElement.getSize(el).equals(new Size(0, 0));\n\t\t},\n\n\t\tisInView: function(el) {\n\t\t\treturn !DomElement.isInvisible(el)\n\t\t\t\t\t&& DomElement.getViewportBounds(el).intersects(\n\t\t\t\t\t\tDomElement.getBounds(el, true));\n\t\t},\n\n\t\tisInserted: function(el) {\n\t\t\treturn document.body.contains(el);\n\t\t},\n\n\t\tgetPrefixed: function(el, name) {\n\t\t\treturn el && handlePrefix(el, name);\n\t\t},\n\n\t\tsetPrefixed: function(el, name, value) {\n\t\t\tif (typeof name === 'object') {\n\t\t\t\tfor (var key in name)\n\t\t\t\t\thandlePrefix(el, key, true, name[key]);\n\t\t\t} else {\n\t\t\t\thandlePrefix(el, name, true, value);\n\t\t\t}\n\t\t}\n\t};\n};\n\nvar DomEvent = {\n\tadd: function(el, events) {\n\t\tif (el) {\n\t\t\tfor (var type in events) {\n\t\t\t\tvar func = events[type],\n\t\t\t\t\tparts = type.split(/[\\s,]+/g);\n\t\t\t\tfor (var i = 0, l = parts.length; i < l; i++) {\n\t\t\t\t\tvar name = parts[i];\n\t\t\t\t\tvar options = (\n\t\t\t\t\t\tel === document\n\t\t\t\t\t\t&& (name === 'touchstart' || name === 'touchmove')\n\t\t\t\t\t) ? { passive: false } : false;\n\t\t\t\t\tel.addEventListener(name, func, options);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tremove: function(el, events) {\n\t\tif (el) {\n\t\t\tfor (var type in events) {\n\t\t\t\tvar func = events[type],\n\t\t\t\t\tparts = type.split(/[\\s,]+/g);\n\t\t\t\tfor (var i = 0, l = parts.length; i < l; i++)\n\t\t\t\t\tel.removeEventListener(parts[i], func, false);\n\t\t\t}\n\t\t}\n\t},\n\n\tgetPoint: function(event) {\n\t\tvar pos = event.targetTouches\n\t\t\t\t? event.targetTouches.length\n\t\t\t\t\t? event.targetTouches[0]\n\t\t\t\t\t: event.changedTouches[0]\n\t\t\t\t: event;\n\t\treturn new Point(\n\t\t\tpos.pageX || pos.clientX + document.documentElement.scrollLeft,\n\t\t\tpos.pageY || pos.clientY + document.documentElement.scrollTop\n\t\t);\n\t},\n\n\tgetTarget: function(event) {\n\t\treturn event.target || event.srcElement;\n\t},\n\n\tgetRelatedTarget: function(event) {\n\t\treturn event.relatedTarget || event.toElement;\n\t},\n\n\tgetOffset: function(event, target) {\n\t\treturn DomEvent.getPoint(event).subtract(DomElement.getOffset(\n\t\t\t\ttarget || DomEvent.getTarget(event)));\n\t}\n};\n\nDomEvent.requestAnimationFrame = new function() {\n\tvar nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'),\n\t\trequested = false,\n\t\tcallbacks = [],\n\t\ttimer;\n\n\tfunction handleCallbacks() {\n\t\tvar functions = callbacks;\n\t\tcallbacks = [];\n\t\tfor (var i = 0, l = functions.length; i < l; i++)\n\t\t\tfunctions[i]();\n\t\trequested = nativeRequest && callbacks.length;\n\t\tif (requested)\n\t\t\tnativeRequest(handleCallbacks);\n\t}\n\n\treturn function(callback) {\n\t\tcallbacks.push(callback);\n\t\tif (nativeRequest) {\n\t\t\tif (!requested) {\n\t\t\t\tnativeRequest(handleCallbacks);\n\t\t\t\trequested = true;\n\t\t\t}\n\t\t} else if (!timer) {\n\t\t\ttimer = setInterval(handleCallbacks, 1000 / 60);\n\t\t}\n\t};\n};\n\nvar View = Base.extend(Emitter, {\n\t_class: 'View',\n\n\tinitialize: function View(project, element) {\n\n\t\tfunction getSize(name) {\n\t\t\treturn element[name] || parseInt(element.getAttribute(name), 10);\n\t\t}\n\n\t\tfunction getCanvasSize() {\n\t\t\tvar size = DomElement.getSize(element);\n\t\t\treturn size.isNaN() || size.isZero()\n\t\t\t\t\t? new Size(getSize('width'), getSize('height'))\n\t\t\t\t\t: size;\n\t\t}\n\n\t\tvar size;\n\t\tif (window && element) {\n\t\t\tthis._id = element.getAttribute('id');\n\t\t\tif (this._id == null)\n\t\t\t\telement.setAttribute('id', this._id = 'paper-view-' + View._id++);\n\t\t\tDomEvent.add(element, this._viewEvents);\n\t\t\tvar none = 'none';\n\t\t\tDomElement.setPrefixed(element.style, {\n\t\t\t\tuserDrag: none,\n\t\t\t\tuserSelect: none,\n\t\t\t\ttouchCallout: none,\n\t\t\t\tcontentZooming: none,\n\t\t\t\ttapHighlightColor: 'rgba(0,0,0,0)'\n\t\t\t});\n\n\t\t\tif (PaperScope.hasAttribute(element, 'resize')) {\n\t\t\t\tvar that = this;\n\t\t\t\tDomEvent.add(window, this._windowEvents = {\n\t\t\t\t\tresize: function() {\n\t\t\t\t\t\tthat.setViewSize(getCanvasSize());\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tsize = getCanvasSize();\n\n\t\t\tif (PaperScope.hasAttribute(element, 'stats')\n\t\t\t\t\t&& typeof Stats !== 'undefined') {\n\t\t\t\tthis._stats = new Stats();\n\t\t\t\tvar stats = this._stats.domElement,\n\t\t\t\t\tstyle = stats.style,\n\t\t\t\t\toffset = DomElement.getOffset(element);\n\t\t\t\tstyle.position = 'absolute';\n\t\t\t\tstyle.left = offset.x + 'px';\n\t\t\t\tstyle.top = offset.y + 'px';\n\t\t\t\tdocument.body.appendChild(stats);\n\t\t\t}\n\t\t} else {\n\t\t\tsize = new Size(element);\n\t\t\telement = null;\n\t\t}\n\t\tthis._project = project;\n\t\tthis._scope = project._scope;\n\t\tthis._element = element;\n\t\tif (!this._pixelRatio)\n\t\t\tthis._pixelRatio = window && window.devicePixelRatio || 1;\n\t\tthis._setElementSize(size.width, size.height);\n\t\tthis._viewSize = size;\n\t\tView._views.push(this);\n\t\tView._viewsById[this._id] = this;\n\t\t(this._matrix = new Matrix())._owner = this;\n\t\tif (!View._focused)\n\t\t\tView._focused = this;\n\t\tthis._frameItems = {};\n\t\tthis._frameItemCount = 0;\n\t\tthis._itemEvents = { native: {}, virtual: {} };\n\t\tthis._autoUpdate = !paper.agent.node;\n\t\tthis._needsUpdate = false;\n\t},\n\n\tremove: function() {\n\t\tif (!this._project)\n\t\t\treturn false;\n\t\tif (View._focused === this)\n\t\t\tView._focused = null;\n\t\tView._views.splice(View._views.indexOf(this), 1);\n\t\tdelete View._viewsById[this._id];\n\t\tvar project = this._project;\n\t\tif (project._view === this)\n\t\t\tproject._view = null;\n\t\tDomEvent.remove(this._element, this._viewEvents);\n\t\tDomEvent.remove(window, this._windowEvents);\n\t\tthis._element = this._project = null;\n\t\tthis.off('frame');\n\t\tthis._animate = false;\n\t\tthis._frameItems = {};\n\t\treturn true;\n\t},\n\n\t_events: Base.each(\n\t\tItem._itemHandlers.concat(['onResize', 'onKeyDown', 'onKeyUp']),\n\t\tfunction(name) {\n\t\t\tthis[name] = {};\n\t\t}, {\n\t\t\tonFrame: {\n\t\t\t\tinstall: function() {\n\t\t\t\t\tthis.play();\n\t\t\t\t},\n\n\t\t\t\tuninstall: function() {\n\t\t\t\t\tthis.pause();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t),\n\n\t_animate: false,\n\t_time: 0,\n\t_count: 0,\n\n\tgetAutoUpdate: function() {\n\t\treturn this._autoUpdate;\n\t},\n\n\tsetAutoUpdate: function(autoUpdate) {\n\t\tthis._autoUpdate = autoUpdate;\n\t\tif (autoUpdate)\n\t\t\tthis.requestUpdate();\n\t},\n\n\tupdate: function() {\n\t},\n\n\tdraw: function() {\n\t\tthis.update();\n\t},\n\n\trequestUpdate: function() {\n\t\tif (!this._requested) {\n\t\t\tvar that = this;\n\t\t\tDomEvent.requestAnimationFrame(function() {\n\t\t\t\tthat._requested = false;\n\t\t\t\tif (that._animate) {\n\t\t\t\t\tthat.requestUpdate();\n\t\t\t\t\tvar element = that._element;\n\t\t\t\t\tif ((!DomElement.getPrefixed(document, 'hidden')\n\t\t\t\t\t\t\t|| PaperScope.getAttribute(element, 'keepalive')\n\t\t\t\t\t\t\t\t=== 'true') && DomElement.isInView(element)) {\n\t\t\t\t\t\tthat._handleFrame();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (that._autoUpdate)\n\t\t\t\t\tthat.update();\n\t\t\t});\n\t\t\tthis._requested = true;\n\t\t}\n\t},\n\n\tplay: function() {\n\t\tthis._animate = true;\n\t\tthis.requestUpdate();\n\t},\n\n\tpause: function() {\n\t\tthis._animate = false;\n\t},\n\n\t_handleFrame: function() {\n\t\tpaper = this._scope;\n\t\tvar now = Date.now() / 1000,\n\t\t\tdelta = this._last ? now - this._last : 0;\n\t\tthis._last = now;\n\t\tthis.emit('frame', new Base({\n\t\t\tdelta: delta,\n\t\t\ttime: this._time += delta,\n\t\t\tcount: this._count++\n\t\t}));\n\t\tif (this._stats)\n\t\t\tthis._stats.update();\n\t},\n\n\t_animateItem: function(item, animate) {\n\t\tvar items = this._frameItems;\n\t\tif (animate) {\n\t\t\titems[item._id] = {\n\t\t\t\titem: item,\n\t\t\t\ttime: 0,\n\t\t\t\tcount: 0\n\t\t\t};\n\t\t\tif (++this._frameItemCount === 1)\n\t\t\t\tthis.on('frame', this._handleFrameItems);\n\t\t} else {\n\t\t\tdelete items[item._id];\n\t\t\tif (--this._frameItemCount === 0) {\n\t\t\t\tthis.off('frame', this._handleFrameItems);\n\t\t\t}\n\t\t}\n\t},\n\n\t_handleFrameItems: function(event) {\n\t\tfor (var i in this._frameItems) {\n\t\t\tvar entry = this._frameItems[i];\n\t\t\tentry.item.emit('frame', new Base(event, {\n\t\t\t\ttime: entry.time += event.delta,\n\t\t\t\tcount: entry.count++\n\t\t\t}));\n\t\t}\n\t},\n\n\t_changed: function() {\n\t\tthis._project._changed(4097);\n\t\tthis._bounds = this._decomposed = undefined;\n\t},\n\n\tgetElement: function() {\n\t\treturn this._element;\n\t},\n\n\tgetPixelRatio: function() {\n\t\treturn this._pixelRatio;\n\t},\n\n\tgetResolution: function() {\n\t\treturn this._pixelRatio * 72;\n\t},\n\n\tgetViewSize: function() {\n\t\tvar size = this._viewSize;\n\t\treturn new LinkedSize(size.width, size.height, this, 'setViewSize');\n\t},\n\n\tsetViewSize: function() {\n\t\tvar size = Size.read(arguments),\n\t\t\tdelta = size.subtract(this._viewSize);\n\t\tif (delta.isZero())\n\t\t\treturn;\n\t\tthis._setElementSize(size.width, size.height);\n\t\tthis._viewSize.set(size);\n\t\tthis._changed();\n\t\tthis.emit('resize', { size: size, delta: delta });\n\t\tif (this._autoUpdate) {\n\t\t\tthis.update();\n\t\t}\n\t},\n\n\t_setElementSize: function(width, height) {\n\t\tvar element = this._element;\n\t\tif (element) {\n\t\t\tif (element.width !== width)\n\t\t\t\telement.width = width;\n\t\t\tif (element.height !== height)\n\t\t\t\telement.height = height;\n\t\t}\n\t},\n\n\tgetBounds: function() {\n\t\tif (!this._bounds)\n\t\t\tthis._bounds = this._matrix.inverted()._transformBounds(\n\t\t\t\t\tnew Rectangle(new Point(), this._viewSize));\n\t\treturn this._bounds;\n\t},\n\n\tgetSize: function() {\n\t\treturn this.getBounds().getSize();\n\t},\n\n\tisVisible: function() {\n\t\treturn DomElement.isInView(this._element);\n\t},\n\n\tisInserted: function() {\n\t\treturn DomElement.isInserted(this._element);\n\t},\n\n\tgetPixelSize: function(size) {\n\t\tvar element = this._element,\n\t\t\tpixels;\n\t\tif (element) {\n\t\t\tvar parent = element.parentNode,\n\t\t\t\ttemp = document.createElement('div');\n\t\t\ttemp.style.fontSize = size;\n\t\t\tparent.appendChild(temp);\n\t\t\tpixels = parseFloat(DomElement.getStyles(temp).fontSize);\n\t\t\tparent.removeChild(temp);\n\t\t} else {\n\t\t\tpixels = parseFloat(pixels);\n\t\t}\n\t\treturn pixels;\n\t},\n\n\tgetTextWidth: function(font, lines) {\n\t\treturn 0;\n\t}\n}, Base.each(['rotate', 'scale', 'shear', 'skew'], function(key) {\n\tvar rotate = key === 'rotate';\n\tthis[key] = function() {\n\t\tvar args = arguments,\n\t\t\tvalue = (rotate ? Base : Point).read(args),\n\t\t\tcenter = Point.read(args, 0, { readNull: true });\n\t\treturn this.transform(new Matrix()[key](value,\n\t\t\t\tcenter || this.getCenter(true)));\n\t};\n}, {\n\t_decompose: function() {\n\t\treturn this._decomposed || (this._decomposed = this._matrix.decompose());\n\t},\n\n\ttranslate: function() {\n\t\tvar mx = new Matrix();\n\t\treturn this.transform(mx.translate.apply(mx, arguments));\n\t},\n\n\tgetCenter: function() {\n\t\treturn this.getBounds().getCenter();\n\t},\n\n\tsetCenter: function() {\n\t\tvar center = Point.read(arguments);\n\t\tthis.translate(this.getCenter().subtract(center));\n\t},\n\n\tgetZoom: function() {\n\t\tvar scaling = this._decompose().scaling;\n\t\treturn (scaling.x + scaling.y) / 2;\n\t},\n\n\tsetZoom: function(zoom) {\n\t\tthis.transform(new Matrix().scale(zoom / this.getZoom(),\n\t\t\tthis.getCenter()));\n\t},\n\n\tgetRotation: function() {\n\t\treturn this._decompose().rotation;\n\t},\n\n\tsetRotation: function(rotation) {\n\t\tvar current = this.getRotation();\n\t\tif (current != null && rotation != null) {\n\t\t\tthis.rotate(rotation - current);\n\t\t}\n\t},\n\n\tgetScaling: function() {\n\t\tvar scaling = this._decompose().scaling;\n\t\treturn new LinkedPoint(scaling.x, scaling.y, this, 'setScaling');\n\t},\n\n\tsetScaling: function() {\n\t\tvar current = this.getScaling(),\n\t\t\tscaling = Point.read(arguments, 0, { clone: true, readNull: true });\n\t\tif (current && scaling) {\n\t\t\tthis.scale(scaling.x / current.x, scaling.y / current.y);\n\t\t}\n\t},\n\n\tgetMatrix: function() {\n\t\treturn this._matrix;\n\t},\n\n\tsetMatrix: function() {\n\t\tvar matrix = this._matrix;\n\t\tmatrix.initialize.apply(matrix, arguments);\n\t},\n\n\ttransform: function(matrix) {\n\t\tthis._matrix.append(matrix);\n\t},\n\n\tscrollBy: function() {\n\t\tthis.translate(Point.read(arguments).negate());\n\t}\n}), {\n\n\tprojectToView: function() {\n\t\treturn this._matrix._transformPoint(Point.read(arguments));\n\t},\n\n\tviewToProject: function() {\n\t\treturn this._matrix._inverseTransform(Point.read(arguments));\n\t},\n\n\tgetEventPoint: function(event) {\n\t\treturn this.viewToProject(DomEvent.getOffset(event, this._element));\n\t},\n\n}, {\n\tstatics: {\n\t\t_views: [],\n\t\t_viewsById: {},\n\t\t_id: 0,\n\n\t\tcreate: function(project, element) {\n\t\t\tif (document && typeof element === 'string')\n\t\t\t\telement = document.getElementById(element);\n\t\t\tvar ctor = window ? CanvasView : View;\n\t\t\treturn new ctor(project, element);\n\t\t}\n\t}\n},\nnew function() {\n\tif (!window)\n\t\treturn;\n\tvar prevFocus,\n\t\ttempFocus,\n\t\tdragging = false,\n\t\tmouseDown = false;\n\n\tfunction getView(event) {\n\t\tvar target = DomEvent.getTarget(event);\n\t\treturn target.getAttribute && View._viewsById[\n\t\t\t\ttarget.getAttribute('id')];\n\t}\n\n\tfunction updateFocus() {\n\t\tvar view = View._focused;\n\t\tif (!view || !view.isVisible()) {\n\t\t\tfor (var i = 0, l = View._views.length; i < l; i++) {\n\t\t\t\tif ((view = View._views[i]).isVisible()) {\n\t\t\t\t\tView._focused = tempFocus = view;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfunction handleMouseMove(view, event, point) {\n\t\tview._handleMouseEvent('mousemove', event, point);\n\t}\n\n\tvar navigator = window.navigator,\n\t\tmousedown, mousemove, mouseup;\n\tif (navigator.pointerEnabled || navigator.msPointerEnabled) {\n\t\tmousedown = 'pointerdown MSPointerDown';\n\t\tmousemove = 'pointermove MSPointerMove';\n\t\tmouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel';\n\t} else {\n\t\tmousedown = 'touchstart';\n\t\tmousemove = 'touchmove';\n\t\tmouseup = 'touchend touchcancel';\n\t\tif (!('ontouchstart' in window && navigator.userAgent.match(\n\t\t\t\t/mobile|tablet|ip(ad|hone|od)|android|silk/i))) {\n\t\t\tmousedown += ' mousedown';\n\t\t\tmousemove += ' mousemove';\n\t\t\tmouseup += ' mouseup';\n\t\t}\n\t}\n\n\tvar viewEvents = {},\n\t\tdocEvents = {\n\t\t\tmouseout: function(event) {\n\t\t\t\tvar view = View._focused,\n\t\t\t\t\ttarget = DomEvent.getRelatedTarget(event);\n\t\t\t\tif (view && (!target || target.nodeName === 'HTML')) {\n\t\t\t\t\tvar offset = DomEvent.getOffset(event, view._element),\n\t\t\t\t\t\tx = offset.x,\n\t\t\t\t\t\tabs = Math.abs,\n\t\t\t\t\t\tax = abs(x),\n\t\t\t\t\t\tmax = 1 << 25,\n\t\t\t\t\t\tdiff = ax - max;\n\t\t\t\t\toffset.x = abs(diff) < ax ? diff * (x < 0 ? -1 : 1) : x;\n\t\t\t\t\thandleMouseMove(view, event, view.viewToProject(offset));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tscroll: updateFocus\n\t\t};\n\n\tviewEvents[mousedown] = function(event) {\n\t\tvar view = View._focused = getView(event);\n\t\tif (!dragging) {\n\t\t\tdragging = true;\n\t\t\tview._handleMouseEvent('mousedown', event);\n\t\t}\n\t};\n\n\tdocEvents[mousemove] = function(event) {\n\t\tvar view = View._focused;\n\t\tif (!mouseDown) {\n\t\t\tvar target = getView(event);\n\t\t\tif (target) {\n\t\t\t\tif (view !== target) {\n\t\t\t\t\tif (view)\n\t\t\t\t\t\thandleMouseMove(view, event);\n\t\t\t\t\tif (!prevFocus)\n\t\t\t\t\t\tprevFocus = view;\n\t\t\t\t\tview = View._focused = tempFocus = target;\n\t\t\t\t}\n\t\t\t} else if (tempFocus && tempFocus === view) {\n\t\t\t\tif (prevFocus && !prevFocus.isInserted())\n\t\t\t\t\tprevFocus = null;\n\t\t\t\tview = View._focused = prevFocus;\n\t\t\t\tprevFocus = null;\n\t\t\t\tupdateFocus();\n\t\t\t}\n\t\t}\n\t\tif (view)\n\t\t\thandleMouseMove(view, event);\n\t};\n\n\tdocEvents[mousedown] = function() {\n\t\tmouseDown = true;\n\t};\n\n\tdocEvents[mouseup] = function(event) {\n\t\tvar view = View._focused;\n\t\tif (view && dragging)\n\t\t\tview._handleMouseEvent('mouseup', event);\n\t\tmouseDown = dragging = false;\n\t};\n\n\tDomEvent.add(document, docEvents);\n\n\tDomEvent.add(window, {\n\t\tload: updateFocus\n\t});\n\n\tvar called = false,\n\t\tprevented = false,\n\t\tfallbacks = {\n\t\t\tdoubleclick: 'click',\n\t\t\tmousedrag: 'mousemove'\n\t\t},\n\t\twasInView = false,\n\t\toverView,\n\t\tdownPoint,\n\t\tlastPoint,\n\t\tdownItem,\n\t\toverItem,\n\t\tdragItem,\n\t\tclickItem,\n\t\tclickTime,\n\t\tdblClick;\n\n\tfunction emitMouseEvent(obj, target, type, event, point, prevPoint,\n\t\t\tstopItem) {\n\t\tvar stopped = false,\n\t\t\tmouseEvent;\n\n\t\tfunction emit(obj, type) {\n\t\t\tif (obj.responds(type)) {\n\t\t\t\tif (!mouseEvent) {\n\t\t\t\t\tmouseEvent = new MouseEvent(type, event, point,\n\t\t\t\t\t\t\ttarget || obj,\n\t\t\t\t\t\t\tprevPoint ? point.subtract(prevPoint) : null);\n\t\t\t\t}\n\t\t\t\tif (obj.emit(type, mouseEvent)) {\n\t\t\t\t\tcalled = true;\n\t\t\t\t\tif (mouseEvent.prevented)\n\t\t\t\t\t\tprevented = true;\n\t\t\t\t\tif (mouseEvent.stopped)\n\t\t\t\t\t\treturn stopped = true;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar fallback = fallbacks[type];\n\t\t\t\tif (fallback)\n\t\t\t\t\treturn emit(obj, fallback);\n\t\t\t}\n\t\t}\n\n\t\twhile (obj && obj !== stopItem) {\n\t\t\tif (emit(obj, type))\n\t\t\t\tbreak;\n\t\t\tobj = obj._parent;\n\t\t}\n\t\treturn stopped;\n\t}\n\n\tfunction emitMouseEvents(view, hitItem, type, event, point, prevPoint) {\n\t\tview._project.removeOn(type);\n\t\tprevented = called = false;\n\t\treturn (dragItem && emitMouseEvent(dragItem, null, type, event,\n\t\t\t\t\tpoint, prevPoint)\n\t\t\t|| hitItem && hitItem !== dragItem\n\t\t\t\t&& !hitItem.isDescendant(dragItem)\n\t\t\t\t&& emitMouseEvent(hitItem, null, type === 'mousedrag' ?\n\t\t\t\t\t'mousemove' : type, event, point, prevPoint, dragItem)\n\t\t\t|| emitMouseEvent(view, dragItem || hitItem || view, type, event,\n\t\t\t\t\tpoint, prevPoint));\n\t}\n\n\tvar itemEventsMap = {\n\t\tmousedown: {\n\t\t\tmousedown: 1,\n\t\t\tmousedrag: 1,\n\t\t\tclick: 1,\n\t\t\tdoubleclick: 1\n\t\t},\n\t\tmouseup: {\n\t\t\tmouseup: 1,\n\t\t\tmousedrag: 1,\n\t\t\tclick: 1,\n\t\t\tdoubleclick: 1\n\t\t},\n\t\tmousemove: {\n\t\t\tmousedrag: 1,\n\t\t\tmousemove: 1,\n\t\t\tmouseenter: 1,\n\t\t\tmouseleave: 1\n\t\t}\n\t};\n\n\treturn {\n\t\t_viewEvents: viewEvents,\n\n\t\t_handleMouseEvent: function(type, event, point) {\n\t\t\tvar itemEvents = this._itemEvents,\n\t\t\t\thitItems = itemEvents.native[type],\n\t\t\t\tnativeMove = type === 'mousemove',\n\t\t\t\ttool = this._scope.tool,\n\t\t\t\tview = this;\n\n\t\t\tfunction responds(type) {\n\t\t\t\treturn itemEvents.virtual[type] || view.responds(type)\n\t\t\t\t\t\t|| tool && tool.responds(type);\n\t\t\t}\n\n\t\t\tif (nativeMove && dragging && responds('mousedrag'))\n\t\t\t\ttype = 'mousedrag';\n\t\t\tif (!point)\n\t\t\t\tpoint = this.getEventPoint(event);\n\n\t\t\tvar inView = this.getBounds().contains(point),\n\t\t\t\thit = hitItems && inView && view._project.hitTest(point, {\n\t\t\t\t\ttolerance: 0,\n\t\t\t\t\tfill: true,\n\t\t\t\t\tstroke: true\n\t\t\t\t}),\n\t\t\t\thitItem = hit && hit.item || null,\n\t\t\t\thandle = false,\n\t\t\t\tmouse = {};\n\t\t\tmouse[type.substr(5)] = true;\n\n\t\t\tif (hitItems && hitItem !== overItem) {\n\t\t\t\tif (overItem) {\n\t\t\t\t\temitMouseEvent(overItem, null, 'mouseleave', event, point);\n\t\t\t\t}\n\t\t\t\tif (hitItem) {\n\t\t\t\t\temitMouseEvent(hitItem, null, 'mouseenter', event, point);\n\t\t\t\t}\n\t\t\t\toverItem = hitItem;\n\t\t\t}\n\t\t\tif (wasInView ^ inView) {\n\t\t\t\temitMouseEvent(this, null, inView ? 'mouseenter' : 'mouseleave',\n\t\t\t\t\t\tevent, point);\n\t\t\t\toverView = inView ? this : null;\n\t\t\t\thandle = true;\n\t\t\t}\n\t\t\tif ((inView || mouse.drag) && !point.equals(lastPoint)) {\n\t\t\t\temitMouseEvents(this, hitItem, nativeMove ? type : 'mousemove',\n\t\t\t\t\t\tevent, point, lastPoint);\n\t\t\t\thandle = true;\n\t\t\t}\n\t\t\twasInView = inView;\n\t\t\tif (mouse.down && inView || mouse.up && downPoint) {\n\t\t\t\temitMouseEvents(this, hitItem, type, event, point, downPoint);\n\t\t\t\tif (mouse.down) {\n\t\t\t\t\tdblClick = hitItem === clickItem\n\t\t\t\t\t\t&& (Date.now() - clickTime < 300);\n\t\t\t\t\tdownItem = clickItem = hitItem;\n\t\t\t\t\tif (!prevented && hitItem) {\n\t\t\t\t\t\tvar item = hitItem;\n\t\t\t\t\t\twhile (item && !item.responds('mousedrag'))\n\t\t\t\t\t\t\titem = item._parent;\n\t\t\t\t\t\tif (item)\n\t\t\t\t\t\t\tdragItem = hitItem;\n\t\t\t\t\t}\n\t\t\t\t\tdownPoint = point;\n\t\t\t\t} else if (mouse.up) {\n\t\t\t\t\tif (!prevented && hitItem === downItem) {\n\t\t\t\t\t\tclickTime = Date.now();\n\t\t\t\t\t\temitMouseEvents(this, hitItem, dblClick ? 'doubleclick'\n\t\t\t\t\t\t\t\t: 'click', event, point, downPoint);\n\t\t\t\t\t\tdblClick = false;\n\t\t\t\t\t}\n\t\t\t\t\tdownItem = dragItem = null;\n\t\t\t\t}\n\t\t\t\twasInView = false;\n\t\t\t\thandle = true;\n\t\t\t}\n\t\t\tlastPoint = point;\n\t\t\tif (handle && tool) {\n\t\t\t\tcalled = tool._handleMouseEvent(type, event, point, mouse)\n\t\t\t\t\t|| called;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tevent.cancelable !== false\n\t\t\t\t&& (called && !mouse.move || mouse.down && responds('mouseup'))\n\t\t\t) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t},\n\n\t\t_handleKeyEvent: function(type, event, key, character) {\n\t\t\tvar scope = this._scope,\n\t\t\t\ttool = scope.tool,\n\t\t\t\tkeyEvent;\n\n\t\t\tfunction emit(obj) {\n\t\t\t\tif (obj.responds(type)) {\n\t\t\t\t\tpaper = scope;\n\t\t\t\t\tobj.emit(type, keyEvent = keyEvent\n\t\t\t\t\t\t\t|| new KeyEvent(type, event, key, character));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.isVisible()) {\n\t\t\t\temit(this);\n\t\t\t\tif (tool && tool.responds(type))\n\t\t\t\t\temit(tool);\n\t\t\t}\n\t\t},\n\n\t\t_countItemEvent: function(type, sign) {\n\t\t\tvar itemEvents = this._itemEvents,\n\t\t\t\tnative = itemEvents.native,\n\t\t\t\tvirtual = itemEvents.virtual;\n\t\t\tfor (var key in itemEventsMap) {\n\t\t\t\tnative[key] = (native[key] || 0)\n\t\t\t\t\t\t+ (itemEventsMap[key][type] || 0) * sign;\n\t\t\t}\n\t\t\tvirtual[type] = (virtual[type] || 0) + sign;\n\t\t},\n\n\t\tstatics: {\n\t\t\tupdateFocus: updateFocus,\n\n\t\t\t_resetState: function() {\n\t\t\t\tdragging = mouseDown = called = wasInView = false;\n\t\t\t\tprevFocus = tempFocus = overView = downPoint = lastPoint =\n\t\t\t\t\tdownItem = overItem = dragItem = clickItem = clickTime =\n\t\t\t\t\tdblClick = null;\n\t\t\t}\n\t\t}\n\t};\n});\n\nvar CanvasView = View.extend({\n\t_class: 'CanvasView',\n\n\tinitialize: function CanvasView(project, canvas) {\n\t\tif (!(canvas instanceof window.HTMLCanvasElement)) {\n\t\t\tvar size = Size.read(arguments, 1);\n\t\t\tif (size.isZero())\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'Cannot create CanvasView with the provided argument: '\n\t\t\t\t\t\t+ Base.slice(arguments, 1));\n\t\t\tcanvas = CanvasProvider.getCanvas(size);\n\t\t}\n\t\tvar ctx = this._context = canvas.getContext('2d');\n\t\tctx.save();\n\t\tthis._pixelRatio = 1;\n\t\tif (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) {\n\t\t\tvar deviceRatio = window.devicePixelRatio || 1,\n\t\t\t\tbackingStoreRatio = DomElement.getPrefixed(ctx,\n\t\t\t\t\t\t'backingStorePixelRatio') || 1;\n\t\t\tthis._pixelRatio = deviceRatio / backingStoreRatio;\n\t\t}\n\t\tView.call(this, project, canvas);\n\t\tthis._needsUpdate = true;\n\t},\n\n\tremove: function remove() {\n\t\tthis._context.restore();\n\t\treturn remove.base.call(this);\n\t},\n\n\t_setElementSize: function _setElementSize(width, height) {\n\t\tvar pixelRatio = this._pixelRatio;\n\t\t_setElementSize.base.call(this, width * pixelRatio, height * pixelRatio);\n\t\tif (pixelRatio !== 1) {\n\t\t\tvar element = this._element,\n\t\t\t\tctx = this._context;\n\t\t\tif (!PaperScope.hasAttribute(element, 'resize')) {\n\t\t\t\tvar style = element.style;\n\t\t\t\tstyle.width = width + 'px';\n\t\t\t\tstyle.height = height + 'px';\n\t\t\t}\n\t\t\tctx.restore();\n\t\t\tctx.save();\n\t\t\tctx.scale(pixelRatio, pixelRatio);\n\t\t}\n\t},\n\n\tgetContext: function() {\n\t\treturn this._context;\n\t},\n\n\tgetPixelSize: function getPixelSize(size) {\n\t\tvar agent = paper.agent,\n\t\t\tpixels;\n\t\tif (agent && agent.firefox) {\n\t\t\tpixels = getPixelSize.base.call(this, size);\n\t\t} else {\n\t\t\tvar ctx = this._context,\n\t\t\t\tprevFont = ctx.font;\n\t\t\tctx.font = size + ' serif';\n\t\t\tpixels = parseFloat(ctx.font);\n\t\t\tctx.font = prevFont;\n\t\t}\n\t\treturn pixels;\n\t},\n\n\tgetTextWidth: function(font, lines) {\n\t\tvar ctx = this._context,\n\t\t\tprevFont = ctx.font,\n\t\t\twidth = 0;\n\t\tctx.font = font;\n\t\tfor (var i = 0, l = lines.length; i < l; i++)\n\t\t\twidth = Math.max(width, ctx.measureText(lines[i]).width);\n\t\tctx.font = prevFont;\n\t\treturn width;\n\t},\n\n\tupdate: function() {\n\t\tif (!this._needsUpdate)\n\t\t\treturn false;\n\t\tvar project = this._project,\n\t\t\tctx = this._context,\n\t\t\tsize = this._viewSize;\n\t\tctx.clearRect(0, 0, size.width + 1, size.height + 1);\n\t\tif (project)\n\t\t\tproject.draw(ctx, this._matrix, this._pixelRatio);\n\t\tthis._needsUpdate = false;\n\t\treturn true;\n\t}\n});\n\nvar Event = Base.extend({\n\t_class: 'Event',\n\n\tinitialize: function Event(event) {\n\t\tthis.event = event;\n\t\tthis.type = event && event.type;\n\t},\n\n\tprevented: false,\n\tstopped: false,\n\n\tpreventDefault: function() {\n\t\tthis.prevented = true;\n\t\tthis.event.preventDefault();\n\t},\n\n\tstopPropagation: function() {\n\t\tthis.stopped = true;\n\t\tthis.event.stopPropagation();\n\t},\n\n\tstop: function() {\n\t\tthis.stopPropagation();\n\t\tthis.preventDefault();\n\t},\n\n\tgetTimeStamp: function() {\n\t\treturn this.event.timeStamp;\n\t},\n\n\tgetModifiers: function() {\n\t\treturn Key.modifiers;\n\t}\n});\n\nvar KeyEvent = Event.extend({\n\t_class: 'KeyEvent',\n\n\tinitialize: function KeyEvent(type, event, key, character) {\n\t\tthis.type = type;\n\t\tthis.event = event;\n\t\tthis.key = key;\n\t\tthis.character = character;\n\t},\n\n\ttoString: function() {\n\t\treturn \"{ type: '\" + this.type\n\t\t\t\t+ \"', key: '\" + this.key\n\t\t\t\t+ \"', character: '\" + this.character\n\t\t\t\t+ \"', modifiers: \" + this.getModifiers()\n\t\t\t\t+ \" }\";\n\t}\n});\n\nvar Key = new function() {\n\tvar keyLookup = {\n\t\t\t'\\t': 'tab',\n\t\t\t' ': 'space',\n\t\t\t'\\b': 'backspace',\n\t\t\t'\\x7f': 'delete',\n\t\t\t'Spacebar': 'space',\n\t\t\t'Del': 'delete',\n\t\t\t'Win': 'meta',\n\t\t\t'Esc': 'escape'\n\t\t},\n\n\t\tcharLookup = {\n\t\t\t'tab': '\\t',\n\t\t\t'space': ' ',\n\t\t\t'enter': '\\r'\n\t\t},\n\n\t\tkeyMap = {},\n\t\tcharMap = {},\n\t\tmetaFixMap,\n\t\tdownKey,\n\n\t\tmodifiers = new Base({\n\t\t\tshift: false,\n\t\t\tcontrol: false,\n\t\t\talt: false,\n\t\t\tmeta: false,\n\t\t\tcapsLock: false,\n\t\t\tspace: false\n\t\t}).inject({\n\t\t\toption: {\n\t\t\t\tget: function() {\n\t\t\t\t\treturn this.alt;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tcommand: {\n\t\t\t\tget: function() {\n\t\t\t\t\tvar agent = paper && paper.agent;\n\t\t\t\t\treturn agent && agent.mac ? this.meta : this.control;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\tfunction getKey(event) {\n\t\tvar key = event.key || event.keyIdentifier;\n\t\tkey = /^U\\+/.test(key)\n\t\t\t\t? String.fromCharCode(parseInt(key.substr(2), 16))\n\t\t\t\t: /^Arrow[A-Z]/.test(key) ? key.substr(5)\n\t\t\t\t: key === 'Unidentified' || key === undefined\n\t\t\t\t\t? String.fromCharCode(event.keyCode)\n\t\t\t\t\t: key;\n\t\treturn keyLookup[key] ||\n\t\t\t\t(key.length > 1 ? Base.hyphenate(key) : key.toLowerCase());\n\t}\n\n\tfunction handleKey(down, key, character, event) {\n\t\tvar type = down ? 'keydown' : 'keyup',\n\t\t\tview = View._focused,\n\t\t\tname;\n\t\tkeyMap[key] = down;\n\t\tif (down) {\n\t\t\tcharMap[key] = character;\n\t\t} else {\n\t\t\tdelete charMap[key];\n\t\t}\n\t\tif (key.length > 1 && (name = Base.camelize(key)) in modifiers) {\n\t\t\tmodifiers[name] = down;\n\t\t\tvar agent = paper && paper.agent;\n\t\t\tif (name === 'meta' && agent && agent.mac) {\n\t\t\t\tif (down) {\n\t\t\t\t\tmetaFixMap = {};\n\t\t\t\t} else {\n\t\t\t\t\tfor (var k in metaFixMap) {\n\t\t\t\t\t\tif (k in charMap)\n\t\t\t\t\t\t\thandleKey(false, k, metaFixMap[k], event);\n\t\t\t\t\t}\n\t\t\t\t\tmetaFixMap = null;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (down && metaFixMap) {\n\t\t\tmetaFixMap[key] = character;\n\t\t}\n\t\tif (view) {\n\t\t\tview._handleKeyEvent(down ? 'keydown' : 'keyup', event, key,\n\t\t\t\t\tcharacter);\n\t\t}\n\t}\n\n\tDomEvent.add(document, {\n\t\tkeydown: function(event) {\n\t\t\tvar key = getKey(event),\n\t\t\t\tagent = paper && paper.agent;\n\t\t\tif (key.length > 1 || agent && (agent.chrome && (event.altKey\n\t\t\t\t\t\t|| agent.mac && event.metaKey\n\t\t\t\t\t\t|| !agent.mac && event.ctrlKey))) {\n\t\t\t\thandleKey(true, key,\n\t\t\t\t\t\tcharLookup[key] || (key.length > 1 ? '' : key), event);\n\t\t\t} else {\n\t\t\t\tdownKey = key;\n\t\t\t}\n\t\t},\n\n\t\tkeypress: function(event) {\n\t\t\tif (downKey) {\n\t\t\t\tvar key = getKey(event),\n\t\t\t\t\tcode = event.charCode,\n\t\t\t\t\tcharacter = code >= 32 ? String.fromCharCode(code)\n\t\t\t\t\t\t: key.length > 1 ? '' : key;\n\t\t\t\tif (key !== downKey) {\n\t\t\t\t\tkey = character.toLowerCase();\n\t\t\t\t}\n\t\t\t\thandleKey(true, key, character, event);\n\t\t\t\tdownKey = null;\n\t\t\t}\n\t\t},\n\n\t\tkeyup: function(event) {\n\t\t\tvar key = getKey(event);\n\t\t\tif (key in charMap)\n\t\t\t\thandleKey(false, key, charMap[key], event);\n\t\t}\n\t});\n\n\tDomEvent.add(window, {\n\t\tblur: function(event) {\n\t\t\tfor (var key in charMap)\n\t\t\t\thandleKey(false, key, charMap[key], event);\n\t\t}\n\t});\n\n\treturn {\n\t\tmodifiers: modifiers,\n\n\t\tisDown: function(key) {\n\t\t\treturn !!keyMap[key];\n\t\t}\n\t};\n};\n\nvar MouseEvent = Event.extend({\n\t_class: 'MouseEvent',\n\n\tinitialize: function MouseEvent(type, event, point, target, delta) {\n\t\tthis.type = type;\n\t\tthis.event = event;\n\t\tthis.point = point;\n\t\tthis.target = target;\n\t\tthis.delta = delta;\n\t},\n\n\ttoString: function() {\n\t\treturn \"{ type: '\" + this.type\n\t\t\t\t+ \"', point: \" + this.point\n\t\t\t\t+ ', target: ' + this.target\n\t\t\t\t+ (this.delta ? ', delta: ' + this.delta : '')\n\t\t\t\t+ ', modifiers: ' + this.getModifiers()\n\t\t\t\t+ ' }';\n\t}\n});\n\nvar ToolEvent = Event.extend({\n\t_class: 'ToolEvent',\n\t_item: null,\n\n\tinitialize: function ToolEvent(tool, type, event) {\n\t\tthis.tool = tool;\n\t\tthis.type = type;\n\t\tthis.event = event;\n\t},\n\n\t_choosePoint: function(point, toolPoint) {\n\t\treturn point ? point : toolPoint ? toolPoint.clone() : null;\n\t},\n\n\tgetPoint: function() {\n\t\treturn this._choosePoint(this._point, this.tool._point);\n\t},\n\n\tsetPoint: function(point) {\n\t\tthis._point = point;\n\t},\n\n\tgetLastPoint: function() {\n\t\treturn this._choosePoint(this._lastPoint, this.tool._lastPoint);\n\t},\n\n\tsetLastPoint: function(lastPoint) {\n\t\tthis._lastPoint = lastPoint;\n\t},\n\n\tgetDownPoint: function() {\n\t\treturn this._choosePoint(this._downPoint, this.tool._downPoint);\n\t},\n\n\tsetDownPoint: function(downPoint) {\n\t\tthis._downPoint = downPoint;\n\t},\n\n\tgetMiddlePoint: function() {\n\t\tif (!this._middlePoint && this.tool._lastPoint) {\n\t\t\treturn this.tool._point.add(this.tool._lastPoint).divide(2);\n\t\t}\n\t\treturn this._middlePoint;\n\t},\n\n\tsetMiddlePoint: function(middlePoint) {\n\t\tthis._middlePoint = middlePoint;\n\t},\n\n\tgetDelta: function() {\n\t\treturn !this._delta && this.tool._lastPoint\n\t\t\t\t? this.tool._point.subtract(this.tool._lastPoint)\n\t\t\t\t: this._delta;\n\t},\n\n\tsetDelta: function(delta) {\n\t\tthis._delta = delta;\n\t},\n\n\tgetCount: function() {\n\t\treturn this.tool[/^mouse(down|up)$/.test(this.type)\n\t\t\t\t? '_downCount' : '_moveCount'];\n\t},\n\n\tsetCount: function(count) {\n\t\tthis.tool[/^mouse(down|up)$/.test(this.type) ? 'downCount' : 'count']\n\t\t\t= count;\n\t},\n\n\tgetItem: function() {\n\t\tif (!this._item) {\n\t\t\tvar result = this.tool._scope.project.hitTest(this.getPoint());\n\t\t\tif (result) {\n\t\t\t\tvar item = result.item,\n\t\t\t\t\tparent = item._parent;\n\t\t\t\twhile (/^(Group|CompoundPath)$/.test(parent._class)) {\n\t\t\t\t\titem = parent;\n\t\t\t\t\tparent = parent._parent;\n\t\t\t\t}\n\t\t\t\tthis._item = item;\n\t\t\t}\n\t\t}\n\t\treturn this._item;\n\t},\n\n\tsetItem: function(item) {\n\t\tthis._item = item;\n\t},\n\n\ttoString: function() {\n\t\treturn '{ type: ' + this.type\n\t\t\t\t+ ', point: ' + this.getPoint()\n\t\t\t\t+ ', count: ' + this.getCount()\n\t\t\t\t+ ', modifiers: ' + this.getModifiers()\n\t\t\t\t+ ' }';\n\t}\n});\n\nvar Tool = PaperScopeItem.extend({\n\t_class: 'Tool',\n\t_list: 'tools',\n\t_reference: 'tool',\n\t_events: ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',\n\t\t\t'onActivate', 'onDeactivate', 'onEditOptions', 'onKeyDown',\n\t\t\t'onKeyUp'],\n\n\tinitialize: function Tool(props) {\n\t\tPaperScopeItem.call(this);\n\t\tthis._moveCount = -1;\n\t\tthis._downCount = -1;\n\t\tthis.set(props);\n\t},\n\n\tgetMinDistance: function() {\n\t\treturn this._minDistance;\n\t},\n\n\tsetMinDistance: function(minDistance) {\n\t\tthis._minDistance = minDistance;\n\t\tif (minDistance != null && this._maxDistance != null\n\t\t\t\t&& minDistance > this._maxDistance) {\n\t\t\tthis._maxDistance = minDistance;\n\t\t}\n\t},\n\n\tgetMaxDistance: function() {\n\t\treturn this._maxDistance;\n\t},\n\n\tsetMaxDistance: function(maxDistance) {\n\t\tthis._maxDistance = maxDistance;\n\t\tif (this._minDistance != null && maxDistance != null\n\t\t\t\t&& maxDistance < this._minDistance) {\n\t\t\tthis._minDistance = maxDistance;\n\t\t}\n\t},\n\n\tgetFixedDistance: function() {\n\t\treturn this._minDistance == this._maxDistance\n\t\t\t? this._minDistance : null;\n\t},\n\n\tsetFixedDistance: function(distance) {\n\t\tthis._minDistance = this._maxDistance = distance;\n\t},\n\n\t_handleMouseEvent: function(type, event, point, mouse) {\n\t\tpaper = this._scope;\n\t\tif (mouse.drag && !this.responds(type))\n\t\t\ttype = 'mousemove';\n\t\tvar move = mouse.move || mouse.drag,\n\t\t\tresponds = this.responds(type),\n\t\t\tminDistance = this.minDistance,\n\t\t\tmaxDistance = this.maxDistance,\n\t\t\tcalled = false,\n\t\t\ttool = this;\n\t\tfunction update(minDistance, maxDistance) {\n\t\t\tvar pt = point,\n\t\t\t\ttoolPoint = move ? tool._point : (tool._downPoint || pt);\n\t\t\tif (move) {\n\t\t\t\tif (tool._moveCount >= 0 && pt.equals(toolPoint)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (toolPoint && (minDistance != null || maxDistance != null)) {\n\t\t\t\t\tvar vector = pt.subtract(toolPoint),\n\t\t\t\t\t\tdistance = vector.getLength();\n\t\t\t\t\tif (distance < (minDistance || 0))\n\t\t\t\t\t\treturn false;\n\t\t\t\t\tif (maxDistance) {\n\t\t\t\t\t\tpt = toolPoint.add(vector.normalize(\n\t\t\t\t\t\t\t\tMath.min(distance, maxDistance)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttool._moveCount++;\n\t\t\t}\n\t\t\ttool._point = pt;\n\t\t\ttool._lastPoint = toolPoint || pt;\n\t\t\tif (mouse.down) {\n\t\t\t\ttool._moveCount = -1;\n\t\t\t\ttool._downPoint = pt;\n\t\t\t\ttool._downCount++;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tfunction emit() {\n\t\t\tif (responds) {\n\t\t\t\tcalled = tool.emit(type, new ToolEvent(tool, type, event))\n\t\t\t\t\t\t|| called;\n\t\t\t}\n\t\t}\n\n\t\tif (mouse.down) {\n\t\t\tupdate();\n\t\t\temit();\n\t\t} else if (mouse.up) {\n\t\t\tupdate(null, maxDistance);\n\t\t\temit();\n\t\t} else if (responds) {\n\t\t\twhile (update(minDistance, maxDistance))\n\t\t\t\temit();\n\t\t}\n\t\treturn called;\n\t}\n\n});\n\nvar Tween = Base.extend(Emitter, {\n\t_class: 'Tween',\n\n\tstatics: {\n\t\teasings: {\n\t\t\tlinear: function(t) {\n\t\t\t\treturn t;\n\t\t\t},\n\n\t\t\teaseInQuad: function(t) {\n\t\t\t\treturn t * t;\n\t\t\t},\n\n\t\t\teaseOutQuad: function(t) {\n\t\t\t\treturn t * (2 - t);\n\t\t\t},\n\n\t\t\teaseInOutQuad: function(t) {\n\t\t\t\treturn t < 0.5\n\t\t\t\t\t? 2 * t * t\n\t\t\t\t\t: -1 + 2 * (2 - t) * t;\n\t\t\t},\n\n\t\t\teaseInCubic: function(t) {\n\t\t\t\treturn t * t * t;\n\t\t\t},\n\n\t\t\teaseOutCubic: function(t) {\n\t\t\t\treturn --t * t * t + 1;\n\t\t\t},\n\n\t\t\teaseInOutCubic: function(t) {\n\t\t\t\treturn t < 0.5\n\t\t\t\t\t? 4 * t * t * t\n\t\t\t\t\t: (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;\n\t\t\t},\n\n\t\t\teaseInQuart: function(t) {\n\t\t\t\treturn t * t * t * t;\n\t\t\t},\n\n\t\t\teaseOutQuart: function(t) {\n\t\t\t\treturn 1 - (--t) * t * t * t;\n\t\t\t},\n\n\t\t\teaseInOutQuart: function(t) {\n\t\t\t\treturn t < 0.5\n\t\t\t\t\t? 8 * t * t * t * t\n\t\t\t\t\t: 1 - 8 * (--t) * t * t * t;\n\t\t\t},\n\n\t\t\teaseInQuint: function(t) {\n\t\t\t\treturn t * t * t * t * t;\n\t\t\t},\n\n\t\t\teaseOutQuint: function(t) {\n\t\t\t\treturn 1 + --t * t * t * t * t;\n\t\t\t},\n\n\t\t\teaseInOutQuint: function(t) {\n\t\t\t\treturn t < 0.5\n\t\t\t\t\t? 16 * t * t * t * t * t\n\t\t\t\t\t: 1 + 16 * (--t) * t * t * t * t;\n\t\t\t}\n\t\t}\n\t},\n\n\tinitialize: function Tween(object, from, to, duration, easing, start) {\n\t\tthis.object = object;\n\t\tvar type = typeof easing;\n\t\tvar isFunction = type === 'function';\n\t\tthis.type = isFunction\n\t\t\t? type\n\t\t\t: type === 'string'\n\t\t\t\t? easing\n\t\t\t\t: 'linear';\n\t\tthis.easing = isFunction ? easing : Tween.easings[this.type];\n\t\tthis.duration = duration;\n\t\tthis.running = false;\n\n\t\tthis._then = null;\n\t\tthis._startTime = null;\n\t\tvar state = from || to;\n\t\tthis._keys = state ? Object.keys(state) : [];\n\t\tthis._parsedKeys = this._parseKeys(this._keys);\n\t\tthis._from = state && this._getState(from);\n\t\tthis._to = state && this._getState(to);\n\t\tif (start !== false) {\n\t\t\tthis.start();\n\t\t}\n\t},\n\n\tthen: function(then) {\n\t\tthis._then = then;\n\t\treturn this;\n\t},\n\n\tstart: function() {\n\t\tthis._startTime = null;\n\t\tthis.running = true;\n\t\treturn this;\n\t},\n\n\tstop: function() {\n\t\tthis.running = false;\n\t\treturn this;\n\t},\n\n\tupdate: function(progress) {\n\t\tif (this.running) {\n\t\t\tif (progress > 1) {\n\t\t\t\tprogress = 1;\n\t\t\t\tthis.running = false;\n\t\t\t}\n\n\t\t\tvar factor = this.easing(progress),\n\t\t\t\tkeys = this._keys,\n\t\t\t\tgetValue = function(value) {\n\t\t\t\t\treturn typeof value === 'function'\n\t\t\t\t\t\t? value(factor, progress)\n\t\t\t\t\t\t: value;\n\t\t\t\t};\n\t\t\tfor (var i = 0, l = keys && keys.length; i < l; i++) {\n\t\t\t\tvar key = keys[i],\n\t\t\t\t\tfrom = getValue(this._from[key]),\n\t\t\t\t\tto = getValue(this._to[key]),\n\t\t\t\t\tvalue = (from && to && from.__add && to.__add)\n\t\t\t\t\t\t? to.__subtract(from).__multiply(factor).__add(from)\n\t\t\t\t\t\t: ((to - from) * factor) + from;\n\t\t\t\tthis._setProperty(this._parsedKeys[key], value);\n\t\t\t}\n\n\t\t\tif (!this.running && this._then) {\n\t\t\t\tthis._then(this.object);\n\t\t\t}\n\t\t\tif (this.responds('update')) {\n\t\t\t\tthis.emit('update', new Base({\n\t\t\t\t\tprogress: progress,\n\t\t\t\t\tfactor: factor\n\t\t\t\t}));\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t},\n\n\t_events: {\n\t\tonUpdate: {}\n\t},\n\n\t_handleFrame: function(time) {\n\t\tvar startTime = this._startTime,\n\t\t\tprogress = startTime\n\t\t\t\t? (time - startTime) / this.duration\n\t\t\t\t: 0;\n\t\tif (!startTime) {\n\t\t\tthis._startTime = time;\n\t\t}\n\t\tthis.update(progress);\n\t},\n\n\t_getState: function(state) {\n\t\tvar keys = this._keys,\n\t\t\tresult = {};\n\t\tfor (var i = 0, l = keys.length; i < l; i++) {\n\t\t\tvar key = keys[i],\n\t\t\t\tpath = this._parsedKeys[key],\n\t\t\t\tcurrent = this._getProperty(path),\n\t\t\t\tvalue;\n\t\t\tif (state) {\n\t\t\t\tvar resolved = this._resolveValue(current, state[key]);\n\t\t\t\tthis._setProperty(path, resolved);\n\t\t\t\tvalue = this._getProperty(path);\n\t\t\t\tvalue = value && value.clone ? value.clone() : value;\n\t\t\t\tthis._setProperty(path, current);\n\t\t\t} else {\n\t\t\t\tvalue = current && current.clone ? current.clone() : current;\n\t\t\t}\n\t\t\tresult[key] = value;\n\t\t}\n\t\treturn result;\n\t},\n\n\t_resolveValue: function(current, value) {\n\t\tif (value) {\n\t\t\tif (Array.isArray(value) && value.length === 2) {\n\t\t\t\tvar operator = value[0];\n\t\t\t\treturn (\n\t\t\t\t\toperator &&\n\t\t\t\t\toperator.match &&\n\t\t\t\t\toperator.match(/^[+\\-\\*\\/]=/)\n\t\t\t\t)\n\t\t\t\t\t? this._calculate(current, operator[0], value[1])\n\t\t\t\t\t: value;\n\t\t\t} else if (typeof value === 'string') {\n\t\t\t\tvar match = value.match(/^[+\\-*/]=(.*)/);\n\t\t\t\tif (match) {\n\t\t\t\t\tvar parsed = JSON.parse(match[1].replace(\n\t\t\t\t\t\t/(['\"])?([a-zA-Z0-9_]+)(['\"])?:/g,\n\t\t\t\t\t\t'\"$2\": '\n\t\t\t\t\t));\n\t\t\t\t\treturn this._calculate(current, value[0], parsed);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t},\n\n\t_calculate: function(left, operator, right) {\n\t\treturn paper.PaperScript.calculateBinary(left, operator, right);\n\t},\n\n\t_parseKeys: function(keys) {\n\t\tvar parsed = {};\n\t\tfor (var i = 0, l = keys.length; i < l; i++) {\n\t\t\tvar key = keys[i],\n\t\t\t\tpath = key\n\t\t\t\t\t.replace(/\\.([^.]*)/g, '/$1')\n\t\t\t\t\t.replace(/\\[['\"]?([^'\"\\]]*)['\"]?\\]/g, '/$1');\n\t\t\tparsed[key] = path.split('/');\n\t\t}\n\t\treturn parsed;\n\t},\n\n\t_getProperty: function(path, offset) {\n\t\tvar obj = this.object;\n\t\tfor (var i = 0, l = path.length - (offset || 0); i < l && obj; i++) {\n\t\t\tobj = obj[path[i]];\n\t\t}\n\t\treturn obj;\n\t},\n\n\t_setProperty: function(path, value) {\n\t\tvar dest = this._getProperty(path, 1);\n\t\tif (dest) {\n\t\t\tdest[path[path.length - 1]] = value;\n\t\t}\n\t}\n});\n\nvar Http = {\n\trequest: function(options) {\n\t\tvar xhr = new self.XMLHttpRequest();\n\t\txhr.open((options.method || 'get').toUpperCase(), options.url,\n\t\t\t\tBase.pick(options.async, true));\n\t\tif (options.mimeType)\n\t\t\txhr.overrideMimeType(options.mimeType);\n\t\txhr.onload = function() {\n\t\t\tvar status = xhr.status;\n\t\t\tif (status === 0 || status === 200) {\n\t\t\t\tif (options.onLoad) {\n\t\t\t\t\toptions.onLoad.call(xhr, xhr.responseText);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txhr.onerror();\n\t\t\t}\n\t\t};\n\t\txhr.onerror = function() {\n\t\t\tvar status = xhr.status,\n\t\t\t\tmessage = 'Could not load \"' + options.url + '\" (Status: '\n\t\t\t\t\t\t+ status + ')';\n\t\t\tif (options.onError) {\n\t\t\t\toptions.onError(message, status);\n\t\t\t} else {\n\t\t\t\tthrow new Error(message);\n\t\t\t}\n\t\t};\n\t\treturn xhr.send(null);\n\t}\n};\n\nvar CanvasProvider = {\n\tcanvases: [],\n\n\tgetCanvas: function(width, height) {\n\t\tif (!window)\n\t\t\treturn null;\n\t\tvar canvas,\n\t\t\tclear = true;\n\t\tif (typeof width === 'object') {\n\t\t\theight = width.height;\n\t\t\twidth = width.width;\n\t\t}\n\t\tif (this.canvases.length) {\n\t\t\tcanvas = this.canvases.pop();\n\t\t} else {\n\t\t\tcanvas = document.createElement('canvas');\n\t\t\tclear = false;\n\t\t}\n\t\tvar ctx = canvas.getContext('2d');\n\t\tif (!ctx) {\n\t\t\tthrow new Error('Canvas ' + canvas +\n\t\t\t\t\t' is unable to provide a 2D context.');\n\t\t}\n\t\tif (canvas.width === width && canvas.height === height) {\n\t\t\tif (clear)\n\t\t\t\tctx.clearRect(0, 0, width + 1, height + 1);\n\t\t} else {\n\t\t\tcanvas.width = width;\n\t\t\tcanvas.height = height;\n\t\t}\n\t\tctx.save();\n\t\treturn canvas;\n\t},\n\n\tgetContext: function(width, height) {\n\t\tvar canvas = this.getCanvas(width, height);\n\t\treturn canvas ? canvas.getContext('2d') : null;\n\t},\n\n\trelease: function(obj) {\n\t\tvar canvas = obj && obj.canvas ? obj.canvas : obj;\n\t\tif (canvas && canvas.getContext) {\n\t\t\tcanvas.getContext('2d').restore();\n\t\t\tthis.canvases.push(canvas);\n\t\t}\n\t}\n};\n\nvar BlendMode = new function() {\n\tvar min = Math.min,\n\t\tmax = Math.max,\n\t\tabs = Math.abs,\n\t\tsr, sg, sb, sa,\n\t\tbr, bg, bb, ba,\n\t\tdr, dg, db;\n\n\tfunction getLum(r, g, b) {\n\t\treturn 0.2989 * r + 0.587 * g + 0.114 * b;\n\t}\n\n\tfunction setLum(r, g, b, l) {\n\t\tvar d = l - getLum(r, g, b);\n\t\tdr = r + d;\n\t\tdg = g + d;\n\t\tdb = b + d;\n\t\tvar l = getLum(dr, dg, db),\n\t\t\tmn = min(dr, dg, db),\n\t\t\tmx = max(dr, dg, db);\n\t\tif (mn < 0) {\n\t\t\tvar lmn = l - mn;\n\t\t\tdr = l + (dr - l) * l / lmn;\n\t\t\tdg = l + (dg - l) * l / lmn;\n\t\t\tdb = l + (db - l) * l / lmn;\n\t\t}\n\t\tif (mx > 255) {\n\t\t\tvar ln = 255 - l,\n\t\t\t\tmxl = mx - l;\n\t\t\tdr = l + (dr - l) * ln / mxl;\n\t\t\tdg = l + (dg - l) * ln / mxl;\n\t\t\tdb = l + (db - l) * ln / mxl;\n\t\t}\n\t}\n\n\tfunction getSat(r, g, b) {\n\t\treturn max(r, g, b) - min(r, g, b);\n\t}\n\n\tfunction setSat(r, g, b, s) {\n\t\tvar col = [r, g, b],\n\t\t\tmx = max(r, g, b),\n\t\t\tmn = min(r, g, b),\n\t\t\tmd;\n\t\tmn = mn === r ? 0 : mn === g ? 1 : 2;\n\t\tmx = mx === r ? 0 : mx === g ? 1 : 2;\n\t\tmd = min(mn, mx) === 0 ? max(mn, mx) === 1 ? 2 : 1 : 0;\n\t\tif (col[mx] > col[mn]) {\n\t\t\tcol[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]);\n\t\t\tcol[mx] = s;\n\t\t} else {\n\t\t\tcol[md] = col[mx] = 0;\n\t\t}\n\t\tcol[mn] = 0;\n\t\tdr = col[0];\n\t\tdg = col[1];\n\t\tdb = col[2];\n\t}\n\n\tvar modes = {\n\t\tmultiply: function() {\n\t\t\tdr = br * sr / 255;\n\t\t\tdg = bg * sg / 255;\n\t\t\tdb = bb * sb / 255;\n\t\t},\n\n\t\tscreen: function() {\n\t\t\tdr = br + sr - (br * sr / 255);\n\t\t\tdg = bg + sg - (bg * sg / 255);\n\t\t\tdb = bb + sb - (bb * sb / 255);\n\t\t},\n\n\t\toverlay: function() {\n\t\t\tdr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255;\n\t\t\tdg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255;\n\t\t\tdb = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255;\n\t\t},\n\n\t\t'soft-light': function() {\n\t\t\tvar t = sr * br / 255;\n\t\t\tdr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255;\n\t\t\tt = sg * bg / 255;\n\t\t\tdg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255;\n\t\t\tt = sb * bb / 255;\n\t\t\tdb = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255;\n\t\t},\n\n\t\t'hard-light': function() {\n\t\t\tdr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255;\n\t\t\tdg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255;\n\t\t\tdb = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255;\n\t\t},\n\n\t\t'color-dodge': function() {\n\t\t\tdr = br === 0 ? 0 : sr === 255 ? 255 : min(255, 255 * br / (255 - sr));\n\t\t\tdg = bg === 0 ? 0 : sg === 255 ? 255 : min(255, 255 * bg / (255 - sg));\n\t\t\tdb = bb === 0 ? 0 : sb === 255 ? 255 : min(255, 255 * bb / (255 - sb));\n\t\t},\n\n\t\t'color-burn': function() {\n\t\t\tdr = br === 255 ? 255 : sr === 0 ? 0 : max(0, 255 - (255 - br) * 255 / sr);\n\t\t\tdg = bg === 255 ? 255 : sg === 0 ? 0 : max(0, 255 - (255 - bg) * 255 / sg);\n\t\t\tdb = bb === 255 ? 255 : sb === 0 ? 0 : max(0, 255 - (255 - bb) * 255 / sb);\n\t\t},\n\n\t\tdarken: function() {\n\t\t\tdr = br < sr ? br : sr;\n\t\t\tdg = bg < sg ? bg : sg;\n\t\t\tdb = bb < sb ? bb : sb;\n\t\t},\n\n\t\tlighten: function() {\n\t\t\tdr = br > sr ? br : sr;\n\t\t\tdg = bg > sg ? bg : sg;\n\t\t\tdb = bb > sb ? bb : sb;\n\t\t},\n\n\t\tdifference: function() {\n\t\t\tdr = br - sr;\n\t\t\tif (dr < 0)\n\t\t\t\tdr = -dr;\n\t\t\tdg = bg - sg;\n\t\t\tif (dg < 0)\n\t\t\t\tdg = -dg;\n\t\t\tdb = bb - sb;\n\t\t\tif (db < 0)\n\t\t\t\tdb = -db;\n\t\t},\n\n\t\texclusion: function() {\n\t\t\tdr = br + sr * (255 - br - br) / 255;\n\t\t\tdg = bg + sg * (255 - bg - bg) / 255;\n\t\t\tdb = bb + sb * (255 - bb - bb) / 255;\n\t\t},\n\n\t\thue: function() {\n\t\t\tsetSat(sr, sg, sb, getSat(br, bg, bb));\n\t\t\tsetLum(dr, dg, db, getLum(br, bg, bb));\n\t\t},\n\n\t\tsaturation: function() {\n\t\t\tsetSat(br, bg, bb, getSat(sr, sg, sb));\n\t\t\tsetLum(dr, dg, db, getLum(br, bg, bb));\n\t\t},\n\n\t\tluminosity: function() {\n\t\t\tsetLum(br, bg, bb, getLum(sr, sg, sb));\n\t\t},\n\n\t\tcolor: function() {\n\t\t\tsetLum(sr, sg, sb, getLum(br, bg, bb));\n\t\t},\n\n\t\tadd: function() {\n\t\t\tdr = min(br + sr, 255);\n\t\t\tdg = min(bg + sg, 255);\n\t\t\tdb = min(bb + sb, 255);\n\t\t},\n\n\t\tsubtract: function() {\n\t\t\tdr = max(br - sr, 0);\n\t\t\tdg = max(bg - sg, 0);\n\t\t\tdb = max(bb - sb, 0);\n\t\t},\n\n\t\taverage: function() {\n\t\t\tdr = (br + sr) / 2;\n\t\t\tdg = (bg + sg) / 2;\n\t\t\tdb = (bb + sb) / 2;\n\t\t},\n\n\t\tnegation: function() {\n\t\t\tdr = 255 - abs(255 - sr - br);\n\t\t\tdg = 255 - abs(255 - sg - bg);\n\t\t\tdb = 255 - abs(255 - sb - bb);\n\t\t}\n\t};\n\n\tvar nativeModes = this.nativeModes = Base.each([\n\t\t'source-over', 'source-in', 'source-out', 'source-atop',\n\t\t'destination-over', 'destination-in', 'destination-out',\n\t\t'destination-atop', 'lighter', 'darker', 'copy', 'xor'\n\t], function(mode) {\n\t\tthis[mode] = true;\n\t}, {});\n\n\tvar ctx = CanvasProvider.getContext(1, 1);\n\tif (ctx) {\n\t\tBase.each(modes, function(func, mode) {\n\t\t\tvar darken = mode === 'darken',\n\t\t\t\tok = false;\n\t\t\tctx.save();\n\t\t\ttry {\n\t\t\t\tctx.fillStyle = darken ? '#300' : '#a00';\n\t\t\t\tctx.fillRect(0, 0, 1, 1);\n\t\t\t\tctx.globalCompositeOperation = mode;\n\t\t\t\tif (ctx.globalCompositeOperation === mode) {\n\t\t\t\t\tctx.fillStyle = darken ? '#a00' : '#300';\n\t\t\t\t\tctx.fillRect(0, 0, 1, 1);\n\t\t\t\t\tok = ctx.getImageData(0, 0, 1, 1).data[0] !== darken\n\t\t\t\t\t\t\t? 170 : 51;\n\t\t\t\t}\n\t\t\t} catch (e) {}\n\t\t\tctx.restore();\n\t\t\tnativeModes[mode] = ok;\n\t\t});\n\t\tCanvasProvider.release(ctx);\n\t}\n\n\tthis.process = function(mode, srcContext, dstContext, alpha, offset) {\n\t\tvar srcCanvas = srcContext.canvas,\n\t\t\tnormal = mode === 'normal';\n\t\tif (normal || nativeModes[mode]) {\n\t\t\tdstContext.save();\n\t\t\tdstContext.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tdstContext.globalAlpha = alpha;\n\t\t\tif (!normal)\n\t\t\t\tdstContext.globalCompositeOperation = mode;\n\t\t\tdstContext.drawImage(srcCanvas, offset.x, offset.y);\n\t\t\tdstContext.restore();\n\t\t} else {\n\t\t\tvar process = modes[mode];\n\t\t\tif (!process)\n\t\t\t\treturn;\n\t\t\tvar dstData = dstContext.getImageData(offset.x, offset.y,\n\t\t\t\t\tsrcCanvas.width, srcCanvas.height),\n\t\t\t\tdst = dstData.data,\n\t\t\t\tsrc = srcContext.getImageData(0, 0,\n\t\t\t\t\tsrcCanvas.width, srcCanvas.height).data;\n\t\t\tfor (var i = 0, l = dst.length; i < l; i += 4) {\n\t\t\t\tsr = src[i];\n\t\t\t\tbr = dst[i];\n\t\t\t\tsg = src[i + 1];\n\t\t\t\tbg = dst[i + 1];\n\t\t\t\tsb = src[i + 2];\n\t\t\t\tbb = dst[i + 2];\n\t\t\t\tsa = src[i + 3];\n\t\t\t\tba = dst[i + 3];\n\t\t\t\tprocess();\n\t\t\t\tvar a1 = sa * alpha / 255,\n\t\t\t\t\ta2 = 1 - a1;\n\t\t\t\tdst[i] = a1 * dr + a2 * br;\n\t\t\t\tdst[i + 1] = a1 * dg + a2 * bg;\n\t\t\t\tdst[i + 2] = a1 * db + a2 * bb;\n\t\t\t\tdst[i + 3] = sa * alpha + a2 * ba;\n\t\t\t}\n\t\t\tdstContext.putImageData(dstData, offset.x, offset.y);\n\t\t}\n\t};\n};\n\nvar SvgElement = new function() {\n\tvar svg = 'http://www.w3.org/2000/svg',\n\t\txmlns = 'http://www.w3.org/2000/xmlns',\n\t\txlink = 'http://www.w3.org/1999/xlink',\n\t\tattributeNamespace = {\n\t\t\thref: xlink,\n\t\t\txlink: xmlns,\n\t\t\txmlns: xmlns + '/',\n\t\t\t'xmlns:xlink': xmlns + '/'\n\t\t};\n\n\tfunction create(tag, attributes, formatter) {\n\t\treturn set(document.createElementNS(svg, tag), attributes, formatter);\n\t}\n\n\tfunction get(node, name) {\n\t\tvar namespace = attributeNamespace[name],\n\t\t\tvalue = namespace\n\t\t\t\t? node.getAttributeNS(namespace, name)\n\t\t\t\t: node.getAttribute(name);\n\t\treturn value === 'null' ? null : value;\n\t}\n\n\tfunction set(node, attributes, formatter) {\n\t\tfor (var name in attributes) {\n\t\t\tvar value = attributes[name],\n\t\t\t\tnamespace = attributeNamespace[name];\n\t\t\tif (typeof value === 'number' && formatter)\n\t\t\t\tvalue = formatter.number(value);\n\t\t\tif (namespace) {\n\t\t\t\tnode.setAttributeNS(namespace, name, value);\n\t\t\t} else {\n\t\t\t\tnode.setAttribute(name, value);\n\t\t\t}\n\t\t}\n\t\treturn node;\n\t}\n\n\treturn {\n\t\tsvg: svg,\n\t\txmlns: xmlns,\n\t\txlink: xlink,\n\n\t\tcreate: create,\n\t\tget: get,\n\t\tset: set\n\t};\n};\n\nvar SvgStyles = Base.each({\n\tfillColor: ['fill', 'color'],\n\tfillRule: ['fill-rule', 'string'],\n\tstrokeColor: ['stroke', 'color'],\n\tstrokeWidth: ['stroke-width', 'number'],\n\tstrokeCap: ['stroke-linecap', 'string'],\n\tstrokeJoin: ['stroke-linejoin', 'string'],\n\tstrokeScaling: ['vector-effect', 'lookup', {\n\t\ttrue: 'none',\n\t\tfalse: 'non-scaling-stroke'\n\t}, function(item, value) {\n\t\treturn !value\n\t\t\t\t&& (item instanceof PathItem\n\t\t\t\t\t|| item instanceof Shape\n\t\t\t\t\t|| item instanceof TextItem);\n\t}],\n\tmiterLimit: ['stroke-miterlimit', 'number'],\n\tdashArray: ['stroke-dasharray', 'array'],\n\tdashOffset: ['stroke-dashoffset', 'number'],\n\tfontFamily: ['font-family', 'string'],\n\tfontWeight: ['font-weight', 'string'],\n\tfontSize: ['font-size', 'number'],\n\tjustification: ['text-anchor', 'lookup', {\n\t\tleft: 'start',\n\t\tcenter: 'middle',\n\t\tright: 'end'\n\t}],\n\topacity: ['opacity', 'number'],\n\tblendMode: ['mix-blend-mode', 'style']\n}, function(entry, key) {\n\tvar part = Base.capitalize(key),\n\t\tlookup = entry[2];\n\tthis[key] = {\n\t\ttype: entry[1],\n\t\tproperty: key,\n\t\tattribute: entry[0],\n\t\ttoSVG: lookup,\n\t\tfromSVG: lookup && Base.each(lookup, function(value, name) {\n\t\t\tthis[value] = name;\n\t\t}, {}),\n\t\texportFilter: entry[3],\n\t\tget: 'get' + part,\n\t\tset: 'set' + part\n\t};\n}, {});\n\nnew function() {\n\tvar formatter;\n\n\tfunction getTransform(matrix, coordinates, center) {\n\t\tvar attrs = new Base(),\n\t\t\ttrans = matrix.getTranslation();\n\t\tif (coordinates) {\n\t\t\tvar point;\n\t\t\tif (matrix.isInvertible()) {\n\t\t\t\tmatrix = matrix._shiftless();\n\t\t\t\tpoint = matrix._inverseTransform(trans);\n\t\t\t\ttrans = null;\n\t\t\t} else {\n\t\t\t\tpoint = new Point();\n\t\t\t}\n\t\t\tattrs[center ? 'cx' : 'x'] = point.x;\n\t\t\tattrs[center ? 'cy' : 'y'] = point.y;\n\t\t}\n\t\tif (!matrix.isIdentity()) {\n\t\t\tvar decomposed = matrix.decompose();\n\t\t\tif (decomposed) {\n\t\t\t\tvar parts = [],\n\t\t\t\t\tangle = decomposed.rotation,\n\t\t\t\t\tscale = decomposed.scaling,\n\t\t\t\t\tskew = decomposed.skewing;\n\t\t\t\tif (trans && !trans.isZero())\n\t\t\t\t\tparts.push('translate(' + formatter.point(trans) + ')');\n\t\t\t\tif (angle)\n\t\t\t\t\tparts.push('rotate(' + formatter.number(angle) + ')');\n\t\t\t\tif (!Numerical.isZero(scale.x - 1)\n\t\t\t\t\t\t|| !Numerical.isZero(scale.y - 1))\n\t\t\t\t\tparts.push('scale(' + formatter.point(scale) +')');\n\t\t\t\tif (skew.x)\n\t\t\t\t\tparts.push('skewX(' + formatter.number(skew.x) + ')');\n\t\t\t\tif (skew.y)\n\t\t\t\t\tparts.push('skewY(' + formatter.number(skew.y) + ')');\n\t\t\t\tattrs.transform = parts.join(' ');\n\t\t\t} else {\n\t\t\t\tattrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';\n\t\t\t}\n\t\t}\n\t\treturn attrs;\n\t}\n\n\tfunction exportGroup(item, options) {\n\t\tvar attrs = getTransform(item._matrix),\n\t\t\tchildren = item._children;\n\t\tvar node = SvgElement.create('g', attrs, formatter);\n\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\tvar child = children[i];\n\t\t\tvar childNode = exportSVG(child, options);\n\t\t\tif (childNode) {\n\t\t\t\tif (child.isClipMask()) {\n\t\t\t\t\tvar clip = SvgElement.create('clipPath');\n\t\t\t\t\tclip.appendChild(childNode);\n\t\t\t\t\tsetDefinition(child, clip, 'clip');\n\t\t\t\t\tSvgElement.set(node, {\n\t\t\t\t\t\t'clip-path': 'url(#' + clip.id + ')'\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tnode.appendChild(childNode);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn node;\n\t}\n\n\tfunction exportRaster(item, options) {\n\t\tvar attrs = getTransform(item._matrix, true),\n\t\t\tsize = item.getSize(),\n\t\t\timage = item.getImage();\n\t\tattrs.x -= size.width / 2;\n\t\tattrs.y -= size.height / 2;\n\t\tattrs.width = size.width;\n\t\tattrs.height = size.height;\n\t\tattrs.href = options.embedImages == false && image && image.src\n\t\t\t\t|| item.toDataURL();\n\t\treturn SvgElement.create('image', attrs, formatter);\n\t}\n\n\tfunction exportPath(item, options) {\n\t\tvar matchShapes = options.matchShapes;\n\t\tif (matchShapes) {\n\t\t\tvar shape = item.toShape(false);\n\t\t\tif (shape)\n\t\t\t\treturn exportShape(shape, options);\n\t\t}\n\t\tvar segments = item._segments,\n\t\t\tlength = segments.length,\n\t\t\ttype,\n\t\t\tattrs = getTransform(item._matrix);\n\t\tif (matchShapes && length >= 2 && !item.hasHandles()) {\n\t\t\tif (length > 2) {\n\t\t\t\ttype = item._closed ? 'polygon' : 'polyline';\n\t\t\t\tvar parts = [];\n\t\t\t\tfor (var i = 0; i < length; i++) {\n\t\t\t\t\tparts.push(formatter.point(segments[i]._point));\n\t\t\t\t}\n\t\t\t\tattrs.points = parts.join(' ');\n\t\t\t} else {\n\t\t\t\ttype = 'line';\n\t\t\t\tvar start = segments[0]._point,\n\t\t\t\t\tend = segments[1]._point;\n\t\t\t\tattrs.set({\n\t\t\t\t\tx1: start.x,\n\t\t\t\t\ty1: start.y,\n\t\t\t\t\tx2: end.x,\n\t\t\t\t\ty2: end.y\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\ttype = 'path';\n\t\t\tattrs.d = item.getPathData(null, options.precision);\n\t\t}\n\t\treturn SvgElement.create(type, attrs, formatter);\n\t}\n\n\tfunction exportShape(item) {\n\t\tvar type = item._type,\n\t\t\tradius = item._radius,\n\t\t\tattrs = getTransform(item._matrix, true, type !== 'rectangle');\n\t\tif (type === 'rectangle') {\n\t\t\ttype = 'rect';\n\t\t\tvar size = item._size,\n\t\t\t\twidth = size.width,\n\t\t\t\theight = size.height;\n\t\t\tattrs.x -= width / 2;\n\t\t\tattrs.y -= height / 2;\n\t\t\tattrs.width = width;\n\t\t\tattrs.height = height;\n\t\t\tif (radius.isZero())\n\t\t\t\tradius = null;\n\t\t}\n\t\tif (radius) {\n\t\t\tif (type === 'circle') {\n\t\t\t\tattrs.r = radius;\n\t\t\t} else {\n\t\t\t\tattrs.rx = radius.width;\n\t\t\t\tattrs.ry = radius.height;\n\t\t\t}\n\t\t}\n\t\treturn SvgElement.create(type, attrs, formatter);\n\t}\n\n\tfunction exportCompoundPath(item, options) {\n\t\tvar attrs = getTransform(item._matrix);\n\t\tvar data = item.getPathData(null, options.precision);\n\t\tif (data)\n\t\t\tattrs.d = data;\n\t\treturn SvgElement.create('path', attrs, formatter);\n\t}\n\n\tfunction exportSymbolItem(item, options) {\n\t\tvar attrs = getTransform(item._matrix, true),\n\t\t\tdefinition = item._definition,\n\t\t\tnode = getDefinition(definition, 'symbol'),\n\t\t\tdefinitionItem = definition._item,\n\t\t\tbounds = definitionItem.getStrokeBounds();\n\t\tif (!node) {\n\t\t\tnode = SvgElement.create('symbol', {\n\t\t\t\tviewBox: formatter.rectangle(bounds)\n\t\t\t});\n\t\t\tnode.appendChild(exportSVG(definitionItem, options));\n\t\t\tsetDefinition(definition, node, 'symbol');\n\t\t}\n\t\tattrs.href = '#' + node.id;\n\t\tattrs.x += bounds.x;\n\t\tattrs.y += bounds.y;\n\t\tattrs.width = bounds.width;\n\t\tattrs.height = bounds.height;\n\t\tattrs.overflow = 'visible';\n\t\treturn SvgElement.create('use', attrs, formatter);\n\t}\n\n\tfunction exportGradient(color, item) {\n\t\tvar gradientNode = getDefinition(color, 'color');\n\t\tif (!gradientNode) {\n\t\t\tvar gradient = color.getGradient(),\n\t\t\t\tradial = gradient._radial,\n\t\t\t\torigin = color.getOrigin(),\n\t\t\t\tdestination = color.getDestination(),\n\t\t\t\tattrs;\n\t\t\tif (radial) {\n\t\t\t\tattrs = {\n\t\t\t\t\tcx: origin.x,\n\t\t\t\t\tcy: origin.y,\n\t\t\t\t\tr: origin.getDistance(destination)\n\t\t\t\t};\n\t\t\t\tvar highlight = color.getHighlight();\n\t\t\t\tif (highlight) {\n\t\t\t\t\tattrs.fx = highlight.x;\n\t\t\t\t\tattrs.fy = highlight.y;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tattrs = {\n\t\t\t\t\tx1: origin.x,\n\t\t\t\t\ty1: origin.y,\n\t\t\t\t\tx2: destination.x,\n\t\t\t\t\ty2: destination.y\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (item instanceof paper.PointText) {\n\t\t\t\tattrs.gradientTransform = getTransform(\n\t\t\t\t\titem._matrix.clone().invert(), false, formatter).transform;\n\t\t\t}\n\n\t\t\tattrs.gradientUnits = 'userSpaceOnUse';\n\t\t\tgradientNode = SvgElement.create((radial ? 'radial' : 'linear')\n\t\t\t\t\t+ 'Gradient', attrs, formatter);\n\t\t\tvar stops = gradient._stops;\n\t\t\tfor (var i = 0, l = stops.length; i < l; i++) {\n\t\t\t\tvar stop = stops[i],\n\t\t\t\t\tstopColor = stop._color,\n\t\t\t\t\talpha = stopColor.getAlpha(),\n\t\t\t\t\toffset = stop._offset;\n\t\t\t\tattrs = {\n\t\t\t\t\toffset: offset == null ? i / (l - 1) : offset\n\t\t\t\t};\n\t\t\t\tif (stopColor)\n\t\t\t\t\tattrs['stop-color'] = stopColor.toCSS(true);\n\t\t\t\tif (alpha < 1)\n\t\t\t\t\tattrs['stop-opacity'] = alpha;\n\t\t\t\tgradientNode.appendChild(\n\t\t\t\t\t\tSvgElement.create('stop', attrs, formatter));\n\t\t\t}\n\t\t\tsetDefinition(color, gradientNode, 'color');\n\t\t}\n\t\treturn 'url(#' + gradientNode.id + ')';\n\t}\n\n\tfunction exportText(item) {\n\t\tvar node = SvgElement.create('text', getTransform(item._matrix, false),\n\t\t\t\tformatter);\n\t\tnode.setAttribute('font-size', item.fontSize);\n\t\tnode.setAttribute('xml:space', 'preserve');\n\t\tfor (var i = 0; i < item._lines.length; i++) {\n\t\t\tvar tspanNode = SvgElement.create('tspan', {\n\t\t\t\tx: '0',\n\t\t\t\tdy: i === 0 ? '0' : item.getLeading() + 'px'\n\t\t\t}, formatter);\n\t\t\ttspanNode.textContent = item._lines[i] ? item._lines[i] : ' ';\n\t\t\tnode.appendChild(tspanNode);\n\t\t}\n\t\treturn node;\n\t}\n\n\tvar exporters = {\n\t\tGroup: exportGroup,\n\t\tLayer: exportGroup,\n\t\tRaster: exportRaster,\n\t\tPath: exportPath,\n\t\tShape: exportShape,\n\t\tCompoundPath: exportCompoundPath,\n\t\tSymbolItem: exportSymbolItem,\n\t\tPointText: exportText\n\t};\n\n\tfunction applyStyle(item, node, isRoot) {\n\t\tvar attrs = {},\n\t\t\tparent = !isRoot && item.getParent(),\n\t\t\tstyle = [];\n\n\t\tif (item._name != null)\n\t\t\tattrs.id = item._name;\n\n\t\tBase.each(SvgStyles, function(entry) {\n\t\t\tvar get = entry.get,\n\t\t\t\ttype = entry.type,\n\t\t\t\tvalue = item[get]();\n\n\t\t\tif (value === undefined) return;\n\n\t\t\tif (entry.exportFilter\n\t\t\t\t\t? entry.exportFilter(item, value)\n\t\t\t\t\t: !parent || !Base.equals(parent[get](), value) ||\n\t\t\t\t\t item instanceof paper.PointText) {\n\t\t\t\tif (type === 'color' && value != null) {\n\t\t\t\t\tvar alpha = value.getAlpha();\n\t\t\t\t\tif (alpha < 1)\n\t\t\t\t\t\tattrs[entry.attribute + '-opacity'] = alpha;\n\t\t\t\t}\n\t\t\t\tif (type === 'style') {\n\t\t\t\t\tstyle.push(entry.attribute + ': ' + value);\n\t\t\t\t} else {\n\t\t\t\t\tattrs[entry.attribute] = value == null ? 'none'\n\t\t\t\t\t\t\t: type === 'color' ? value.gradient\n\t\t\t\t\t\t\t\t? exportGradient(value, item)\n\t\t\t\t\t\t\t\t: value.toCSS(true)\n\t\t\t\t\t\t\t: type === 'array' ? value.join(',')\n\t\t\t\t\t\t\t: type === 'lookup' ? entry.toSVG[value]\n\t\t\t\t\t\t\t: value;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tif (style.length)\n\t\t\tattrs.style = style.join(';');\n\n\t\tif (attrs.opacity === 1)\n\t\t\tdelete attrs.opacity;\n\n\t\tif (!item._visible)\n\t\t\tattrs.visibility = 'hidden';\n\n\t\treturn SvgElement.set(node, attrs, formatter);\n\t}\n\n\tvar definitions;\n\tfunction getDefinition(item, type) {\n\t\tif (!definitions)\n\t\t\tdefinitions = { ids: {}, svgs: {} };\n\t\treturn item && definitions.svgs[type + '-'\n\t\t\t\t+ (item._id || item.__id || (item.__id = UID.get('svg')))];\n\t}\n\n\tfunction setDefinition(item, node, type) {\n\t\tif (!definitions)\n\t\t\tgetDefinition();\n\t\tvar typeId = definitions.ids[type] = (definitions.ids[type] || 0) + 1;\n\t\tnode.id = type + '-' + typeId;\n\t\tdefinitions.svgs[type + '-' + (item._id || item.__id)] = node;\n\t}\n\n\tfunction exportDefinitions(node, options) {\n\t\tvar svg = node,\n\t\t\tdefs = null;\n\t\tif (definitions) {\n\t\t\tsvg = node.nodeName.toLowerCase() === 'svg' && node;\n\t\t\tfor (var i in definitions.svgs) {\n\t\t\t\tif (!defs) {\n\t\t\t\t\tif (!svg) {\n\t\t\t\t\t\tsvg = SvgElement.create('svg');\n\t\t\t\t\t\tsvg.appendChild(node);\n\t\t\t\t\t}\n\t\t\t\t\tdefs = svg.insertBefore(SvgElement.create('defs'),\n\t\t\t\t\t\t\tsvg.firstChild);\n\t\t\t\t}\n\t\t\t\tdefs.appendChild(definitions.svgs[i]);\n\t\t\t}\n\t\t\tdefinitions = null;\n\t\t}\n\t\treturn options.asString\n\t\t\t\t? new self.XMLSerializer().serializeToString(svg)\n\t\t\t\t: svg;\n\t}\n\n\tfunction exportSVG(item, options, isRoot) {\n\t\tvar exporter = exporters[item._class],\n\t\t\tnode = exporter && exporter(item, options);\n\t\tif (node) {\n\t\t\tvar onExport = options.onExport;\n\t\t\tif (onExport)\n\t\t\t\tnode = onExport(item, node, options) || node;\n\t\t\tvar data = JSON.stringify(item._data);\n\t\t\tif (data && data !== '{}' && data !== 'null')\n\t\t\t\tnode.setAttribute('data-paper-data', data);\n\t\t}\n\t\treturn node && applyStyle(item, node, isRoot);\n\t}\n\n\tfunction setOptions(options) {\n\t\tif (!options)\n\t\t\toptions = {};\n\t\tformatter = new Formatter(options.precision);\n\t\treturn options;\n\t}\n\n\tItem.inject({\n\t\texportSVG: function(options) {\n\t\t\toptions = setOptions(options);\n\t\t\treturn exportDefinitions(exportSVG(this, options, true), options);\n\t\t}\n\t});\n\n\tProject.inject({\n\t\texportSVG: function(options) {\n\t\t\toptions = setOptions(options);\n\t\t\tvar children = this._children,\n\t\t\t\tview = this.getView(),\n\t\t\t\tbounds = Base.pick(options.bounds, 'view'),\n\t\t\t\tmx = options.matrix || bounds === 'view' && view._matrix,\n\t\t\t\tmatrix = mx && Matrix.read([mx]),\n\t\t\t\trect = bounds === 'view'\n\t\t\t\t\t? new Rectangle([0, 0], view.getViewSize())\n\t\t\t\t\t: bounds === 'content'\n\t\t\t\t\t\t? Item._getBounds(children, matrix, { stroke: true, drawnTextBounds: true })\n\t\t\t\t\t\t\t.rect\n\t\t\t\t\t\t: Rectangle.read([bounds], 0, { readNull: true }),\n\t\t\t\tattrs = {\n\t\t\t\t\tversion: '1.1',\n\t\t\t\t\txmlns: SvgElement.svg,\n\t\t\t\t\t'xmlns:xlink': SvgElement.xlink,\n\t\t\t\t};\n\t\t\tif (rect) {\n\t\t\t\tattrs.width = rect.width;\n\t\t\t\tattrs.height = rect.height;\n\t\t\t\tif (rect.x || rect.x === 0 || rect.y || rect.y === 0)\n\t\t\t\t\tattrs.viewBox = formatter.rectangle(rect);\n\t\t\t}\n\t\t\tvar node = SvgElement.create('svg', attrs, formatter),\n\t\t\t\tparent = node;\n\t\t\tif (matrix && !matrix.isIdentity()) {\n\t\t\t\tparent = node.appendChild(SvgElement.create('g',\n\t\t\t\t\t\tgetTransform(matrix), formatter));\n\t\t\t}\n\t\t\tfor (var i = 0, l = children.length; i < l; i++) {\n\t\t\t\tparent.appendChild(exportSVG(children[i], options, true));\n\t\t\t}\n\t\t\treturn exportDefinitions(node, options);\n\t\t}\n\t});\n};\n\nnew function() {\n\n\tvar definitions = {},\n\t\trootSize;\n\n\tfunction getValue(node, name, isString, allowNull, allowPercent,\n\t\t\tdefaultValue) {\n\t\tvar value = SvgElement.get(node, name) || defaultValue,\n\t\t\tres = value == null\n\t\t\t\t? allowNull\n\t\t\t\t\t? null\n\t\t\t\t\t: isString ? '' : 0\n\t\t\t\t: isString\n\t\t\t\t\t? value\n\t\t\t\t\t: parseFloat(value);\n\t\treturn /%\\s*$/.test(value)\n\t\t\t? (res / 100) * (allowPercent ? 1\n\t\t\t\t: rootSize[/x|^width/.test(name) ? 'width' : 'height'])\n\t\t\t: res;\n\t}\n\n\tfunction getPoint(node, x, y, allowNull, allowPercent, defaultX, defaultY) {\n\t\tx = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX);\n\t\ty = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY);\n\t\treturn allowNull && (x == null || y == null) ? null\n\t\t\t\t: new Point(x, y);\n\t}\n\n\tfunction getSize(node, w, h, allowNull, allowPercent) {\n\t\tw = getValue(node, w || 'width', false, allowNull, allowPercent);\n\t\th = getValue(node, h || 'height', false, allowNull, allowPercent);\n\t\treturn allowNull && (w == null || h == null) ? null\n\t\t\t\t: new Size(w, h);\n\t}\n\n\tfunction convertValue(value, type, lookup) {\n\t\treturn value === 'none' ? null\n\t\t\t\t: type === 'number' ? parseFloat(value)\n\t\t\t\t: type === 'array' ?\n\t\t\t\t\tvalue ? value.split(/[\\s,]+/g).map(parseFloat) : []\n\t\t\t\t: type === 'color' ? getDefinition(value) || value\n\t\t\t\t: type === 'lookup' ? lookup[value]\n\t\t\t\t: value;\n\t}\n\n\tfunction importGroup(node, type, options, isRoot) {\n\t\tvar nodes = node.childNodes,\n\t\t\tisClip = type === 'clippath',\n\t\t\tisDefs = type === 'defs',\n\t\t\titem = new Group(),\n\t\t\tproject = item._project,\n\t\t\tcurrentStyle = project._currentStyle,\n\t\t\tchildren = [];\n\t\tif (!isClip && !isDefs) {\n\t\t\titem = applyAttributes(item, node, isRoot);\n\t\t\tproject._currentStyle = item._style.clone();\n\t\t}\n\t\tif (isRoot) {\n\t\t\tvar defs = node.querySelectorAll('defs');\n\t\t\tfor (var i = 0, l = defs.length; i < l; i++) {\n\t\t\t\timportNode(defs[i], options, false);\n\t\t\t}\n\t\t}\n\t\tfor (var i = 0, l = nodes.length; i < l; i++) {\n\t\t\tvar childNode = nodes[i],\n\t\t\t\tchild;\n\t\t\tif (childNode.nodeType === 1\n\t\t\t\t\t&& !/^defs$/i.test(childNode.nodeName)\n\t\t\t\t\t&& (child = importNode(childNode, options, false))\n\t\t\t\t\t&& !(child instanceof SymbolDefinition))\n\t\t\t\tchildren.push(child);\n\t\t}\n\t\titem.addChildren(children);\n\t\tif (isClip)\n\t\t\titem = applyAttributes(item.reduce(), node, isRoot);\n\t\tproject._currentStyle = currentStyle;\n\t\tif (isClip || isDefs) {\n\t\t\titem.remove();\n\t\t\titem = null;\n\t\t}\n\t\treturn item;\n\t}\n\n\tfunction importPoly(node, type) {\n\t\tvar coords = node.getAttribute('points').match(\n\t\t\t\t\t/[+-]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][+-]?\\d+)?/g),\n\t\t\tpoints = [];\n\t\tfor (var i = 0, l = coords.length; i < l; i += 2)\n\t\t\tpoints.push(new Point(\n\t\t\t\t\tparseFloat(coords[i]),\n\t\t\t\t\tparseFloat(coords[i + 1])));\n\t\tvar path = new Path(points);\n\t\tif (type === 'polygon')\n\t\t\tpath.closePath();\n\t\treturn path;\n\t}\n\n\tfunction importPath(node) {\n\t\treturn PathItem.create(node.getAttribute('d'));\n\t}\n\n\tfunction importGradient(node, type) {\n\t\tvar id = (getValue(node, 'href', true) || '').substring(1),\n\t\t\tradial = type === 'radialgradient',\n\t\t\tgradient;\n\t\tif (id) {\n\t\t\tgradient = definitions[id].getGradient();\n\t\t\tif (gradient._radial ^ radial) {\n\t\t\t\tgradient = gradient.clone();\n\t\t\t\tgradient._radial = radial;\n\t\t\t}\n\t\t} else {\n\t\t\tvar nodes = node.childNodes,\n\t\t\t\tstops = [];\n\t\t\tfor (var i = 0, l = nodes.length; i < l; i++) {\n\t\t\t\tvar child = nodes[i];\n\t\t\t\tif (child.nodeType === 1)\n\t\t\t\t\tstops.push(applyAttributes(new GradientStop(), child));\n\t\t\t}\n\t\t\tgradient = new Gradient(stops, radial);\n\t\t}\n\t\tvar origin, destination, highlight,\n\t\t\tscaleToBounds = getValue(node, 'gradientUnits', true) !==\n\t\t\t\t'userSpaceOnUse';\n\t\tif (radial) {\n\t\t\torigin = getPoint(node, 'cx', 'cy', false, scaleToBounds,\n\t\t\t\t'50%', '50%');\n\t\t\tdestination = origin.add(\n\t\t\t\tgetValue(node, 'r', false, false, scaleToBounds, '50%'), 0);\n\t\t\thighlight = getPoint(node, 'fx', 'fy', true, scaleToBounds);\n\t\t} else {\n\t\t\torigin = getPoint(node, 'x1', 'y1', false, scaleToBounds,\n\t\t\t\t'0%', '0%');\n\t\t\tdestination = getPoint(node, 'x2', 'y2', false, scaleToBounds,\n\t\t\t\t'100%', '0%');\n\t\t}\n\t\tvar color = applyAttributes(\n\t\t\t\tnew Color(gradient, origin, destination, highlight), node);\n\t\tcolor._scaleToBounds = scaleToBounds;\n\t\treturn null;\n\t}\n\n\tvar importers = {\n\t\t'#document': function (node, type, options, isRoot) {\n\t\t\tvar nodes = node.childNodes;\n\t\t\tfor (var i = 0, l = nodes.length; i < l; i++) {\n\t\t\t\tvar child = nodes[i];\n\t\t\t\tif (child.nodeType === 1)\n\t\t\t\t\treturn importNode(child, options, isRoot);\n\t\t\t}\n\t\t},\n\t\tg: importGroup,\n\t\tsvg: importGroup,\n\t\tclippath: importGroup,\n\t\tpolygon: importPoly,\n\t\tpolyline: importPoly,\n\t\tpath: importPath,\n\t\tlineargradient: importGradient,\n\t\tradialgradient: importGradient,\n\n\t\timage: function (node) {\n\t\t\tvar raster = new Raster(getValue(node, 'href', true));\n\t\t\traster.on('load', function() {\n\t\t\t\tvar size = getSize(node);\n\t\t\t\tthis.setSize(size);\n\t\t\t\tvar center = getPoint(node).add(size.divide(2));\n\t\t\t\tthis._matrix.append(new Matrix().translate(center));\n\t\t\t});\n\t\t\treturn raster;\n\t\t},\n\n\t\tsymbol: function(node, type, options, isRoot) {\n\t\t\treturn new SymbolDefinition(\n\t\t\t\t\timportGroup(node, type, options, isRoot), true);\n\t\t},\n\n\t\tdefs: importGroup,\n\n\t\tuse: function(node) {\n\t\t\tvar id = (getValue(node, 'href', true) || '').substring(1),\n\t\t\t\tdefinition = definitions[id],\n\t\t\t\tpoint = getPoint(node);\n\t\t\treturn definition\n\t\t\t\t\t? definition instanceof SymbolDefinition\n\t\t\t\t\t\t? definition.place(point)\n\t\t\t\t\t\t: definition.clone().translate(point)\n\t\t\t\t\t: null;\n\t\t},\n\n\t\tcircle: function(node) {\n\t\t\treturn new Shape.Circle(\n\t\t\t\t\tgetPoint(node, 'cx', 'cy'),\n\t\t\t\t\tgetValue(node, 'r'));\n\t\t},\n\n\t\tellipse: function(node) {\n\t\t\treturn new Shape.Ellipse({\n\t\t\t\tcenter: getPoint(node, 'cx', 'cy'),\n\t\t\t\tradius: getSize(node, 'rx', 'ry')\n\t\t\t});\n\t\t},\n\n\t\trect: function(node) {\n\t\t\treturn new Shape.Rectangle(new Rectangle(\n\t\t\t\t\t\tgetPoint(node),\n\t\t\t\t\t\tgetSize(node)\n\t\t\t\t\t), getSize(node, 'rx', 'ry'));\n\t\t\t},\n\n\t\tline: function(node) {\n\t\t\treturn new Path.Line(\n\t\t\t\t\tgetPoint(node, 'x1', 'y1'),\n\t\t\t\t\tgetPoint(node, 'x2', 'y2'));\n\t\t},\n\n\t\ttext: function(node) {\n\n\t\t\tvar fontSize = parseFloat(node.getAttribute(\"font-size\"));\n\t\t\tvar alignmentBaseline = node.getAttribute(\"alignment-baseline\");\n\t\t\tif (node.childElementCount === 0) {\n\t\t\t\tvar text = new PointText();\n\t\t\t\ttext.setContent(node.textContent.trim() || '');\n\t\t\t\ttext.translate(0, text._style.getLeading());\n\t\t\t\tif (!isNaN(fontSize)) text.setFontSize(fontSize);\n\t\t\t\treturn text;\n\t\t\t} else {\n\t\t\t\tvar lines = [];\n\t\t\t\tvar spacing = 1.2;\n\t\t\t\tfor (var i = 0; i < node.childNodes.length; i++) {\n\t\t\t\t\tvar child = node.childNodes[i];\n\t\t\t\t\tif (!child.getAttribute) continue;\n\t\t\t\t\tlines.push(child.textContent);\n\t\t\t\t\tvar dyString = child.getAttribute('dy');\n\t\t\t\t\tif (dyString) {\n\t\t\t\t\t\tvar dy = parseFloat(dyString);\n\t\t\t\t\t\tif (!isNaN(dy)) {\n\t\t\t\t\t\t\tif (dyString.endsWith('em')) {\n\t\t\t\t\t\t\t\tspacing = dy;\n\t\t\t\t\t\t\t} else if (dyString.endsWith('px') && !isNaN(fontSize)) {\n\t\t\t\t\t\t\t\tspacing = dy / fontSize;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tvar text = new PointText();\n\t\t\t\tif (!isNaN(fontSize)) text.setFontSize(fontSize);\n\t\t\t\ttext.setLeading(text.fontSize * spacing);\n\t\t\t\tif (alignmentBaseline === 'text-before-edge') {\n\t\t\t\t\ttext.setContent(' ');\n\t\t\t\t\ttext.translate(0, text.bounds.height);\n\t\t\t\t}\n\t\t\t\ttext.setContent(lines.join('\\n'));\n\t\t\t\treturn text;\n\t\t\t}\n\t\t},\n\n\t\tswitch: importGroup\n\t};\n\n\tfunction applyTransform(item, value, name, node) {\n\t\tif (item.transform) {\n\t\t\tvar transforms = (node.getAttribute(name) || '').split(/\\)\\s*/g),\n\t\t\t\tmatrix = new Matrix();\n\t\t\tfor (var i = 0, l = transforms.length; i < l; i++) {\n\t\t\t\tvar transform = transforms[i];\n\t\t\t\tif (!transform)\n\t\t\t\t\tbreak;\n\t\t\t\tvar parts = transform.split(/\\(\\s*/),\n\t\t\t\t\tcommand = parts[0],\n\t\t\t\t\tv = parts[1].split(/[\\s,]+/g);\n\t\t\t\tfor (var j = 0, m = v.length; j < m; j++)\n\t\t\t\t\tv[j] = parseFloat(v[j]);\n\t\t\t\tswitch (command) {\n\t\t\t\tcase 'matrix':\n\t\t\t\t\tmatrix.append(\n\t\t\t\t\t\t\tnew Matrix(v[0], v[1], v[2], v[3], v[4], v[5]));\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'rotate':\n\t\t\t\t\tmatrix.rotate(v[0], v[1] || 0, v[2] || 0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'translate':\n\t\t\t\t\tmatrix.translate(v[0], v[1] || 0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'scale':\n\t\t\t\t\tmatrix.scale(v);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'skewX':\n\t\t\t\t\tmatrix.skew(v[0], 0);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'skewY':\n\t\t\t\t\tmatrix.skew(0, v[0]);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\titem.transform(matrix);\n\t\t}\n\t}\n\n\tfunction applyOpacity(item, value, name) {\n\t\tvar key = name === 'fill-opacity' ? 'getFillColor' : 'getStrokeColor',\n\t\t\tcolor = item[key] && item[key]();\n\t\tif (color)\n\t\t\tcolor.setAlpha(parseFloat(value));\n\t}\n\n\tvar attributes = Base.set(Base.each(SvgStyles, function(entry) {\n\t\tthis[entry.attribute] = function(item, value) {\n\t\t\tif (item[entry.set]) {\n\t\t\t\titem[entry.set](convertValue(value, entry.type, entry.fromSVG));\n\t\t\t\tif (entry.type === 'color') {\n\t\t\t\t\tvar color = item[entry.get]();\n\t\t\t\t\tif (color) {\n\t\t\t\t\t\tif (color._scaleToBounds) {\n\t\t\t\t\t\t\tvar bounds = item.getBounds();\n\t\t\t\t\t\t\tcolor.transform(new Matrix()\n\t\t\t\t\t\t\t\t.translate(bounds.getPoint())\n\t\t\t\t\t\t\t\t.scale(bounds.getSize()));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}, {}), {\n\t\tid: function(item, value) {\n\t\t\tdefinitions[value] = item;\n\t\t},\n\n\t\t'clip-path': function(item, value) {\n\t\t\tvar clip = getDefinition(value);\n\t\t\tif (clip) {\n\t\t\t\tclip = clip.clone();\n\t\t\t\tclip.setClipMask(true);\n\t\t\t\tif (item instanceof Group) {\n\t\t\t\t\titem.insertChild(0, clip);\n\t\t\t\t} else {\n\t\t\t\t\treturn new Group(clip, item);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tgradientTransform: applyTransform,\n\t\ttransform: applyTransform,\n\n\t\t'fill-opacity': applyOpacity,\n\t\t'stroke-opacity': applyOpacity,\n\n\t\tvisibility: function(item, value) {\n\t\t\tif (item.setVisible)\n\t\t\t\titem.setVisible(value === 'visible');\n\t\t},\n\n\t\tdisplay: function(item, value) {\n\t\t\tif (item.setVisible)\n\t\t\t\titem.setVisible(value !== null);\n\t\t},\n\n\t\t'stop-color': function(item, value) {\n\t\t\tif (item.setColor)\n\t\t\t\titem.setColor(value);\n\t\t},\n\n\t\t'stop-opacity': function(item, value) {\n\t\t\tif (item._color)\n\t\t\t\titem._color.setAlpha(parseFloat(value));\n\t\t},\n\n\t\toffset: function(item, value) {\n\t\t\tif (item.setOffset) {\n\t\t\t\tvar percent = value.match(/(.*)%$/);\n\t\t\t\titem.setOffset(percent ? percent[1] / 100 : parseFloat(value));\n\t\t\t}\n\t\t},\n\n\t\tviewBox: function(item, value, name, node, styles) {\n\t\t\tvar rect = new Rectangle(convertValue(value, 'array')),\n\t\t\t\tsize = getSize(node, null, null, true),\n\t\t\t\tgroup,\n\t\t\t\tmatrix;\n\t\t\tif (item instanceof Group) {\n\t\t\t\tvar scale = size ? size.divide(rect.getSize()) : 1,\n\t\t\t\tmatrix = new Matrix().scale(scale)\n\t\t\t\t\t\t.translate(rect.getPoint().negate());\n\t\t\t\tgroup = item;\n\t\t\t} else if (item instanceof SymbolDefinition) {\n\t\t\t\tif (size)\n\t\t\t\t\trect.setSize(size);\n\t\t\t\tgroup = item._item;\n\t\t\t}\n\t\t\tif (group) {\n\t\t\t\tif (getAttribute(node, 'overflow', styles) !== 'visible') {\n\t\t\t\t\tvar clip = new Shape.Rectangle(rect);\n\t\t\t\t\tclip.setClipMask(true);\n\t\t\t\t\tgroup.addChild(clip);\n\t\t\t\t}\n\t\t\t\tif (matrix)\n\t\t\t\t\tgroup.transform(matrix);\n\t\t\t}\n\t\t},\n\n\t\t'fill-rule': function(item, value) {\n\t\t\tif (value === 'evenodd' || value === 'nonzero') item.fillRule = value;\n\t\t}\n\t});\n\n\tfunction getAttribute(node, name, styles) {\n\t\tvar attr = node.attributes[name],\n\t\t\tvalue = attr && attr.value;\n\t\tif (!value && node.style) {\n\t\t\tvar style = Base.camelize(name);\n\t\t\tvalue = node.style[style];\n\t\t\tif (!value && styles.node[style] !== styles.parent[style])\n\t\t\t\tvalue = styles.node[style];\n\t\t}\n\t\treturn !value ? undefined\n\t\t\t\t: value === 'none' ? null\n\t\t\t\t: value;\n\t}\n\n\tfunction applyAttributes(item, node, isRoot) {\n\t\tvar parent = node.parentNode,\n\t\t\tstyles = {\n\t\t\t\tnode: DomElement.getStyles(node) || {},\n\t\t\t\tparent: !isRoot && !/^defs$/i.test(parent.tagName)\n\t\t\t\t\t\t&& DomElement.getStyles(parent) || {}\n\t\t\t};\n\t\tBase.each(attributes, function(apply, name) {\n\t\t\tvar value = getAttribute(node, name, styles);\n\t\t\titem = value !== undefined\n\t\t\t\t\t&& apply(item, value, name, node, styles) || item;\n\t\t});\n\t\treturn item;\n\t}\n\n\tfunction getDefinition(value) {\n\t\tvar match = value && value.match(/\\((?:[\"'#]*)([^\"')]+)/),\n\t\t\tname = match && match[1],\n\t\t\tres = name && definitions[window\n\t\t\t\t\t? name.replace(window.location.href.split('#')[0] + '#', '')\n\t\t\t\t\t: name];\n\t\tif (res && res._scaleToBounds) {\n\t\t\tres = res.clone();\n\t\t\tres._scaleToBounds = true;\n\t\t}\n\t\treturn res;\n\t}\n\n\tfunction importNode(node, options, isRoot) {\n\t\tvar type = node.nodeName.toLowerCase(),\n\t\t\tisElement = type !== '#document',\n\t\t\tbody = document.body,\n\t\t\tcontainer,\n\t\t\tparent,\n\t\t\tnext;\n\t\tif (isRoot && isElement) {\n\t\t\trootSize = paper.getView().getSize();\n\t\t\trootSize = getSize(node, null, null, true) || rootSize;\n\t\t\tcontainer = SvgElement.create('svg', {\n\t\t\t\tstyle: 'stroke-width: 1px; stroke-miterlimit: 10'\n\t\t\t});\n\t\t\tparent = node.parentNode;\n\t\t\tnext = node.nextSibling;\n\t\t\tcontainer.appendChild(node);\n\t\t\tbody.appendChild(container);\n\t\t}\n\t\tvar settings = paper.settings,\n\t\t\tapplyMatrix = settings.applyMatrix,\n\t\t\tinsertItems = settings.insertItems;\n\t\tsettings.applyMatrix = false;\n\t\tsettings.insertItems = false;\n\t\tvar importer = importers[type],\n\t\t\titem = importer && importer(node, type, options, isRoot) || null;\n\t\tsettings.insertItems = insertItems;\n\t\tsettings.applyMatrix = applyMatrix;\n\t\tif (item) {\n\t\t\tif (isElement && !(item instanceof Group))\n\t\t\t\titem = applyAttributes(item, node, isRoot);\n\t\t\tvar onImport = options.onImport,\n\t\t\t\tdata = isElement && node.getAttribute('data-paper-data');\n\t\t\tif (onImport)\n\t\t\t\titem = onImport(node, item, options) || item;\n\t\t\tif (options.expandShapes && item instanceof Shape) {\n\t\t\t\titem.remove();\n\t\t\t\titem = item.toPath();\n\t\t\t}\n\t\t\tif (data)\n\t\t\t\titem._data = JSON.parse(data);\n\t\t}\n\t\tif (container) {\n\t\t\tbody.removeChild(container);\n\t\t\tif (parent) {\n\t\t\t\tif (next) {\n\t\t\t\t\tparent.insertBefore(node, next);\n\t\t\t\t} else {\n\t\t\t\t\tparent.appendChild(node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (isRoot) {\n\t\t\tdefinitions = {};\n\t\t\tif (item && Base.pick(options.applyMatrix, applyMatrix))\n\t\t\t\titem.matrix.apply(true, true);\n\t\t}\n\t\treturn item;\n\t}\n\n\tfunction importSVG(source, options, owner) {\n\t\tif (!source)\n\t\t\treturn null;\n\t\toptions = typeof options === 'function' ? { onLoad: options }\n\t\t\t\t: options || {};\n\t\tvar scope = paper,\n\t\t\titem = null;\n\n\t\tfunction onLoad(svg) {\n\t\t\ttry {\n\t\t\t\tvar node = typeof svg === 'object'\n\t\t\t\t\t? svg\n\t\t\t\t\t: new self.DOMParser().parseFromString(\n\t\t\t\t\t\tsvg,\n\t\t\t\t\t\t'image/svg+xml'\n\t\t\t\t\t);\n\t\t\t\tif (!node.nodeName) {\n\t\t\t\t\tnode = null;\n\t\t\t\t\tthrow new Error('Unsupported SVG source: ' + source);\n\t\t\t\t}\n\t\t\t\tpaper = scope;\n\t\t\t\titem = importNode(node, options, true);\n\t\t\t\tif (!options || options.insert !== false) {\n\t\t\t\t\towner._insertItem(undefined, item);\n\t\t\t\t}\n\t\t\t\tvar onLoad = options.onLoad;\n\t\t\t\tif (onLoad)\n\t\t\t\t\tonLoad(item, svg);\n\t\t\t} catch (e) {\n\t\t\t\tonError(e);\n\t\t\t}\n\t\t}\n\n\t\tfunction onError(message, status) {\n\t\t\tvar onError = options.onError;\n\t\t\tif (onError) {\n\t\t\t\tonError(message, status);\n\t\t\t} else {\n\t\t\t\tthrow new Error(message);\n\t\t\t}\n\t\t}\n\n\t\tif (typeof source === 'string' && !/^[\\s\\S]* 3) {\n\t cats.sort(function(a, b) {return b.length - a.length;});\n\t f += \"switch(str.length){\";\n\t for (var i = 0; i < cats.length; ++i) {\n\t\tvar cat = cats[i];\n\t\tf += \"case \" + cat[0].length + \":\";\n\t\tcompareTo(cat);\n\t }\n\t f += \"}\";\n\n\t} else {\n\t compareTo(words);\n\t}\n\treturn new Function(\"str\", f);\n }\n\n var isReservedWord3 = makePredicate(\"abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile\");\n\n var isReservedWord5 = makePredicate(\"class enum extends super const export import\");\n\n var isStrictReservedWord = makePredicate(\"implements interface let package private protected public static yield\");\n\n var isStrictBadIdWord = makePredicate(\"eval arguments\");\n\n var isKeyword = makePredicate(\"break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this\");\n\n var nonASCIIwhitespace = /[\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000\\ufeff]/;\n var nonASCIIidentifierStartChars = \"\\xaa\\xb5\\xba\\xc0-\\xd6\\xd8-\\xf6\\xf8-\\u02c1\\u02c6-\\u02d1\\u02e0-\\u02e4\\u02ec\\u02ee\\u0370-\\u0374\\u0376\\u0377\\u037a-\\u037d\\u0386\\u0388-\\u038a\\u038c\\u038e-\\u03a1\\u03a3-\\u03f5\\u03f7-\\u0481\\u048a-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05d0-\\u05ea\\u05f0-\\u05f2\\u0620-\\u064a\\u066e\\u066f\\u0671-\\u06d3\\u06d5\\u06e5\\u06e6\\u06ee\\u06ef\\u06fa-\\u06fc\\u06ff\\u0710\\u0712-\\u072f\\u074d-\\u07a5\\u07b1\\u07ca-\\u07ea\\u07f4\\u07f5\\u07fa\\u0800-\\u0815\\u081a\\u0824\\u0828\\u0840-\\u0858\\u08a0\\u08a2-\\u08ac\\u0904-\\u0939\\u093d\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097f\\u0985-\\u098c\\u098f\\u0990\\u0993-\\u09a8\\u09aa-\\u09b0\\u09b2\\u09b6-\\u09b9\\u09bd\\u09ce\\u09dc\\u09dd\\u09df-\\u09e1\\u09f0\\u09f1\\u0a05-\\u0a0a\\u0a0f\\u0a10\\u0a13-\\u0a28\\u0a2a-\\u0a30\\u0a32\\u0a33\\u0a35\\u0a36\\u0a38\\u0a39\\u0a59-\\u0a5c\\u0a5e\\u0a72-\\u0a74\\u0a85-\\u0a8d\\u0a8f-\\u0a91\\u0a93-\\u0aa8\\u0aaa-\\u0ab0\\u0ab2\\u0ab3\\u0ab5-\\u0ab9\\u0abd\\u0ad0\\u0ae0\\u0ae1\\u0b05-\\u0b0c\\u0b0f\\u0b10\\u0b13-\\u0b28\\u0b2a-\\u0b30\\u0b32\\u0b33\\u0b35-\\u0b39\\u0b3d\\u0b5c\\u0b5d\\u0b5f-\\u0b61\\u0b71\\u0b83\\u0b85-\\u0b8a\\u0b8e-\\u0b90\\u0b92-\\u0b95\\u0b99\\u0b9a\\u0b9c\\u0b9e\\u0b9f\\u0ba3\\u0ba4\\u0ba8-\\u0baa\\u0bae-\\u0bb9\\u0bd0\\u0c05-\\u0c0c\\u0c0e-\\u0c10\\u0c12-\\u0c28\\u0c2a-\\u0c33\\u0c35-\\u0c39\\u0c3d\\u0c58\\u0c59\\u0c60\\u0c61\\u0c85-\\u0c8c\\u0c8e-\\u0c90\\u0c92-\\u0ca8\\u0caa-\\u0cb3\\u0cb5-\\u0cb9\\u0cbd\\u0cde\\u0ce0\\u0ce1\\u0cf1\\u0cf2\\u0d05-\\u0d0c\\u0d0e-\\u0d10\\u0d12-\\u0d3a\\u0d3d\\u0d4e\\u0d60\\u0d61\\u0d7a-\\u0d7f\\u0d85-\\u0d96\\u0d9a-\\u0db1\\u0db3-\\u0dbb\\u0dbd\\u0dc0-\\u0dc6\\u0e01-\\u0e30\\u0e32\\u0e33\\u0e40-\\u0e46\\u0e81\\u0e82\\u0e84\\u0e87\\u0e88\\u0e8a\\u0e8d\\u0e94-\\u0e97\\u0e99-\\u0e9f\\u0ea1-\\u0ea3\\u0ea5\\u0ea7\\u0eaa\\u0eab\\u0ead-\\u0eb0\\u0eb2\\u0eb3\\u0ebd\\u0ec0-\\u0ec4\\u0ec6\\u0edc-\\u0edf\\u0f00\\u0f40-\\u0f47\\u0f49-\\u0f6c\\u0f88-\\u0f8c\\u1000-\\u102a\\u103f\\u1050-\\u1055\\u105a-\\u105d\\u1061\\u1065\\u1066\\u106e-\\u1070\\u1075-\\u1081\\u108e\\u10a0-\\u10c5\\u10c7\\u10cd\\u10d0-\\u10fa\\u10fc-\\u1248\\u124a-\\u124d\\u1250-\\u1256\\u1258\\u125a-\\u125d\\u1260-\\u1288\\u128a-\\u128d\\u1290-\\u12b0\\u12b2-\\u12b5\\u12b8-\\u12be\\u12c0\\u12c2-\\u12c5\\u12c8-\\u12d6\\u12d8-\\u1310\\u1312-\\u1315\\u1318-\\u135a\\u1380-\\u138f\\u13a0-\\u13f4\\u1401-\\u166c\\u166f-\\u167f\\u1681-\\u169a\\u16a0-\\u16ea\\u16ee-\\u16f0\\u1700-\\u170c\\u170e-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176c\\u176e-\\u1770\\u1780-\\u17b3\\u17d7\\u17dc\\u1820-\\u1877\\u1880-\\u18a8\\u18aa\\u18b0-\\u18f5\\u1900-\\u191c\\u1950-\\u196d\\u1970-\\u1974\\u1980-\\u19ab\\u19c1-\\u19c7\\u1a00-\\u1a16\\u1a20-\\u1a54\\u1aa7\\u1b05-\\u1b33\\u1b45-\\u1b4b\\u1b83-\\u1ba0\\u1bae\\u1baf\\u1bba-\\u1be5\\u1c00-\\u1c23\\u1c4d-\\u1c4f\\u1c5a-\\u1c7d\\u1ce9-\\u1cec\\u1cee-\\u1cf1\\u1cf5\\u1cf6\\u1d00-\\u1dbf\\u1e00-\\u1f15\\u1f18-\\u1f1d\\u1f20-\\u1f45\\u1f48-\\u1f4d\\u1f50-\\u1f57\\u1f59\\u1f5b\\u1f5d\\u1f5f-\\u1f7d\\u1f80-\\u1fb4\\u1fb6-\\u1fbc\\u1fbe\\u1fc2-\\u1fc4\\u1fc6-\\u1fcc\\u1fd0-\\u1fd3\\u1fd6-\\u1fdb\\u1fe0-\\u1fec\\u1ff2-\\u1ff4\\u1ff6-\\u1ffc\\u2071\\u207f\\u2090-\\u209c\\u2102\\u2107\\u210a-\\u2113\\u2115\\u2119-\\u211d\\u2124\\u2126\\u2128\\u212a-\\u212d\\u212f-\\u2139\\u213c-\\u213f\\u2145-\\u2149\\u214e\\u2160-\\u2188\\u2c00-\\u2c2e\\u2c30-\\u2c5e\\u2c60-\\u2ce4\\u2ceb-\\u2cee\\u2cf2\\u2cf3\\u2d00-\\u2d25\\u2d27\\u2d2d\\u2d30-\\u2d67\\u2d6f\\u2d80-\\u2d96\\u2da0-\\u2da6\\u2da8-\\u2dae\\u2db0-\\u2db6\\u2db8-\\u2dbe\\u2dc0-\\u2dc6\\u2dc8-\\u2dce\\u2dd0-\\u2dd6\\u2dd8-\\u2dde\\u2e2f\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303c\\u3041-\\u3096\\u309d-\\u309f\\u30a1-\\u30fa\\u30fc-\\u30ff\\u3105-\\u312d\\u3131-\\u318e\\u31a0-\\u31ba\\u31f0-\\u31ff\\u3400-\\u4db5\\u4e00-\\u9fcc\\ua000-\\ua48c\\ua4d0-\\ua4fd\\ua500-\\ua60c\\ua610-\\ua61f\\ua62a\\ua62b\\ua640-\\ua66e\\ua67f-\\ua697\\ua6a0-\\ua6ef\\ua717-\\ua71f\\ua722-\\ua788\\ua78b-\\ua78e\\ua790-\\ua793\\ua7a0-\\ua7aa\\ua7f8-\\ua801\\ua803-\\ua805\\ua807-\\ua80a\\ua80c-\\ua822\\ua840-\\ua873\\ua882-\\ua8b3\\ua8f2-\\ua8f7\\ua8fb\\ua90a-\\ua925\\ua930-\\ua946\\ua960-\\ua97c\\ua984-\\ua9b2\\ua9cf\\uaa00-\\uaa28\\uaa40-\\uaa42\\uaa44-\\uaa4b\\uaa60-\\uaa76\\uaa7a\\uaa80-\\uaaaf\\uaab1\\uaab5\\uaab6\\uaab9-\\uaabd\\uaac0\\uaac2\\uaadb-\\uaadd\\uaae0-\\uaaea\\uaaf2-\\uaaf4\\uab01-\\uab06\\uab09-\\uab0e\\uab11-\\uab16\\uab20-\\uab26\\uab28-\\uab2e\\uabc0-\\uabe2\\uac00-\\ud7a3\\ud7b0-\\ud7c6\\ud7cb-\\ud7fb\\uf900-\\ufa6d\\ufa70-\\ufad9\\ufb00-\\ufb06\\ufb13-\\ufb17\\ufb1d\\ufb1f-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40\\ufb41\\ufb43\\ufb44\\ufb46-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb\\ufe70-\\ufe74\\ufe76-\\ufefc\\uff21-\\uff3a\\uff41-\\uff5a\\uff66-\\uffbe\\uffc2-\\uffc7\\uffca-\\uffcf\\uffd2-\\uffd7\\uffda-\\uffdc\";\n var nonASCIIidentifierChars = \"\\u0300-\\u036f\\u0483-\\u0487\\u0591-\\u05bd\\u05bf\\u05c1\\u05c2\\u05c4\\u05c5\\u05c7\\u0610-\\u061a\\u0620-\\u0649\\u0672-\\u06d3\\u06e7-\\u06e8\\u06fb-\\u06fc\\u0730-\\u074a\\u0800-\\u0814\\u081b-\\u0823\\u0825-\\u0827\\u0829-\\u082d\\u0840-\\u0857\\u08e4-\\u08fe\\u0900-\\u0903\\u093a-\\u093c\\u093e-\\u094f\\u0951-\\u0957\\u0962-\\u0963\\u0966-\\u096f\\u0981-\\u0983\\u09bc\\u09be-\\u09c4\\u09c7\\u09c8\\u09d7\\u09df-\\u09e0\\u0a01-\\u0a03\\u0a3c\\u0a3e-\\u0a42\\u0a47\\u0a48\\u0a4b-\\u0a4d\\u0a51\\u0a66-\\u0a71\\u0a75\\u0a81-\\u0a83\\u0abc\\u0abe-\\u0ac5\\u0ac7-\\u0ac9\\u0acb-\\u0acd\\u0ae2-\\u0ae3\\u0ae6-\\u0aef\\u0b01-\\u0b03\\u0b3c\\u0b3e-\\u0b44\\u0b47\\u0b48\\u0b4b-\\u0b4d\\u0b56\\u0b57\\u0b5f-\\u0b60\\u0b66-\\u0b6f\\u0b82\\u0bbe-\\u0bc2\\u0bc6-\\u0bc8\\u0bca-\\u0bcd\\u0bd7\\u0be6-\\u0bef\\u0c01-\\u0c03\\u0c46-\\u0c48\\u0c4a-\\u0c4d\\u0c55\\u0c56\\u0c62-\\u0c63\\u0c66-\\u0c6f\\u0c82\\u0c83\\u0cbc\\u0cbe-\\u0cc4\\u0cc6-\\u0cc8\\u0cca-\\u0ccd\\u0cd5\\u0cd6\\u0ce2-\\u0ce3\\u0ce6-\\u0cef\\u0d02\\u0d03\\u0d46-\\u0d48\\u0d57\\u0d62-\\u0d63\\u0d66-\\u0d6f\\u0d82\\u0d83\\u0dca\\u0dcf-\\u0dd4\\u0dd6\\u0dd8-\\u0ddf\\u0df2\\u0df3\\u0e34-\\u0e3a\\u0e40-\\u0e45\\u0e50-\\u0e59\\u0eb4-\\u0eb9\\u0ec8-\\u0ecd\\u0ed0-\\u0ed9\\u0f18\\u0f19\\u0f20-\\u0f29\\u0f35\\u0f37\\u0f39\\u0f41-\\u0f47\\u0f71-\\u0f84\\u0f86-\\u0f87\\u0f8d-\\u0f97\\u0f99-\\u0fbc\\u0fc6\\u1000-\\u1029\\u1040-\\u1049\\u1067-\\u106d\\u1071-\\u1074\\u1082-\\u108d\\u108f-\\u109d\\u135d-\\u135f\\u170e-\\u1710\\u1720-\\u1730\\u1740-\\u1750\\u1772\\u1773\\u1780-\\u17b2\\u17dd\\u17e0-\\u17e9\\u180b-\\u180d\\u1810-\\u1819\\u1920-\\u192b\\u1930-\\u193b\\u1951-\\u196d\\u19b0-\\u19c0\\u19c8-\\u19c9\\u19d0-\\u19d9\\u1a00-\\u1a15\\u1a20-\\u1a53\\u1a60-\\u1a7c\\u1a7f-\\u1a89\\u1a90-\\u1a99\\u1b46-\\u1b4b\\u1b50-\\u1b59\\u1b6b-\\u1b73\\u1bb0-\\u1bb9\\u1be6-\\u1bf3\\u1c00-\\u1c22\\u1c40-\\u1c49\\u1c5b-\\u1c7d\\u1cd0-\\u1cd2\\u1d00-\\u1dbe\\u1e01-\\u1f15\\u200c\\u200d\\u203f\\u2040\\u2054\\u20d0-\\u20dc\\u20e1\\u20e5-\\u20f0\\u2d81-\\u2d96\\u2de0-\\u2dff\\u3021-\\u3028\\u3099\\u309a\\ua640-\\ua66d\\ua674-\\ua67d\\ua69f\\ua6f0-\\ua6f1\\ua7f8-\\ua800\\ua806\\ua80b\\ua823-\\ua827\\ua880-\\ua881\\ua8b4-\\ua8c4\\ua8d0-\\ua8d9\\ua8f3-\\ua8f7\\ua900-\\ua909\\ua926-\\ua92d\\ua930-\\ua945\\ua980-\\ua983\\ua9b3-\\ua9c0\\uaa00-\\uaa27\\uaa40-\\uaa41\\uaa4c-\\uaa4d\\uaa50-\\uaa59\\uaa7b\\uaae0-\\uaae9\\uaaf2-\\uaaf3\\uabc0-\\uabe1\\uabec\\uabed\\uabf0-\\uabf9\\ufb20-\\ufb28\\ufe00-\\ufe0f\\ufe20-\\ufe26\\ufe33\\ufe34\\ufe4d-\\ufe4f\\uff10-\\uff19\\uff3f\";\n var nonASCIIidentifierStart = new RegExp(\"[\" + nonASCIIidentifierStartChars + \"]\");\n var nonASCIIidentifier = new RegExp(\"[\" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + \"]\");\n\n var newline = /[\\n\\r\\u2028\\u2029]/;\n\n var lineBreak = /\\r\\n|[\\n\\r\\u2028\\u2029]/g;\n\n var isIdentifierStart = exports.isIdentifierStart = function(code) {\n\tif (code < 65) return code === 36;\n\tif (code < 91) return true;\n\tif (code < 97) return code === 95;\n\tif (code < 123)return true;\n\treturn code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));\n };\n\n var isIdentifierChar = exports.isIdentifierChar = function(code) {\n\tif (code < 48) return code === 36;\n\tif (code < 58) return true;\n\tif (code < 65) return false;\n\tif (code < 91) return true;\n\tif (code < 97) return code === 95;\n\tif (code < 123)return true;\n\treturn code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));\n };\n\n function line_loc_t() {\n\tthis.line = tokCurLine;\n\tthis.column = tokPos - tokLineStart;\n }\n\n function initTokenState() {\n\ttokCurLine = 1;\n\ttokPos = tokLineStart = 0;\n\ttokRegexpAllowed = true;\n\tskipSpace();\n }\n\n function finishToken(type, val) {\n\ttokEnd = tokPos;\n\tif (options.locations) tokEndLoc = new line_loc_t;\n\ttokType = type;\n\tskipSpace();\n\ttokVal = val;\n\ttokRegexpAllowed = type.beforeExpr;\n }\n\n function skipBlockComment() {\n\tvar startLoc = options.onComment && options.locations && new line_loc_t;\n\tvar start = tokPos, end = input.indexOf(\"*/\", tokPos += 2);\n\tif (end === -1) raise(tokPos - 2, \"Unterminated comment\");\n\ttokPos = end + 2;\n\tif (options.locations) {\n\t lineBreak.lastIndex = start;\n\t var match;\n\t while ((match = lineBreak.exec(input)) && match.index < tokPos) {\n\t\t++tokCurLine;\n\t\ttokLineStart = match.index + match[0].length;\n\t }\n\t}\n\tif (options.onComment)\n\t options.onComment(true, input.slice(start + 2, end), start, tokPos,\n\t\t\t\t\t\tstartLoc, options.locations && new line_loc_t);\n }\n\n function skipLineComment() {\n\tvar start = tokPos;\n\tvar startLoc = options.onComment && options.locations && new line_loc_t;\n\tvar ch = input.charCodeAt(tokPos+=2);\n\twhile (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {\n\t ++tokPos;\n\t ch = input.charCodeAt(tokPos);\n\t}\n\tif (options.onComment)\n\t options.onComment(false, input.slice(start + 2, tokPos), start, tokPos,\n\t\t\t\t\t\tstartLoc, options.locations && new line_loc_t);\n }\n\n function skipSpace() {\n\twhile (tokPos < inputLen) {\n\t var ch = input.charCodeAt(tokPos);\n\t if (ch === 32) {\n\t\t++tokPos;\n\t } else if (ch === 13) {\n\t\t++tokPos;\n\t\tvar next = input.charCodeAt(tokPos);\n\t\tif (next === 10) {\n\t\t ++tokPos;\n\t\t}\n\t\tif (options.locations) {\n\t\t ++tokCurLine;\n\t\t tokLineStart = tokPos;\n\t\t}\n\t } else if (ch === 10 || ch === 8232 || ch === 8233) {\n\t\t++tokPos;\n\t\tif (options.locations) {\n\t\t ++tokCurLine;\n\t\t tokLineStart = tokPos;\n\t\t}\n\t } else if (ch > 8 && ch < 14) {\n\t\t++tokPos;\n\t } else if (ch === 47) {\n\t\tvar next = input.charCodeAt(tokPos + 1);\n\t\tif (next === 42) {\n\t\t skipBlockComment();\n\t\t} else if (next === 47) {\n\t\t skipLineComment();\n\t\t} else break;\n\t } else if (ch === 160) {\n\t\t++tokPos;\n\t } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {\n\t\t++tokPos;\n\t } else {\n\t\tbreak;\n\t }\n\t}\n }\n\n function readToken_dot() {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tif (next >= 48 && next <= 57) return readNumber(true);\n\t++tokPos;\n\treturn finishToken(_dot);\n }\n\n function readToken_slash() {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tif (tokRegexpAllowed) {++tokPos; return readRegexp();}\n\tif (next === 61) return finishOp(_assign, 2);\n\treturn finishOp(_slash, 1);\n }\n\n function readToken_mult_modulo() {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tif (next === 61) return finishOp(_assign, 2);\n\treturn finishOp(_multiplyModulo, 1);\n }\n\n function readToken_pipe_amp(code) {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tif (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2);\n\tif (next === 61) return finishOp(_assign, 2);\n\treturn finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1);\n }\n\n function readToken_caret() {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tif (next === 61) return finishOp(_assign, 2);\n\treturn finishOp(_bitwiseXOR, 1);\n }\n\n function readToken_plus_min(code) {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tif (next === code) {\n\t if (next == 45 && input.charCodeAt(tokPos + 2) == 62 &&\n\t\t newline.test(input.slice(lastEnd, tokPos))) {\n\t\ttokPos += 3;\n\t\tskipLineComment();\n\t\tskipSpace();\n\t\treturn readToken();\n\t }\n\t return finishOp(_incDec, 2);\n\t}\n\tif (next === 61) return finishOp(_assign, 2);\n\treturn finishOp(_plusMin, 1);\n }\n\n function readToken_lt_gt(code) {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tvar size = 1;\n\tif (next === code) {\n\t size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2;\n\t if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1);\n\t return finishOp(_bitShift, size);\n\t}\n\tif (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 &&\n\t\tinput.charCodeAt(tokPos + 3) == 45) {\n\t tokPos += 4;\n\t skipLineComment();\n\t skipSpace();\n\t return readToken();\n\t}\n\tif (next === 61)\n\t size = input.charCodeAt(tokPos + 2) === 61 ? 3 : 2;\n\treturn finishOp(_relational, size);\n }\n\n function readToken_eq_excl(code) {\n\tvar next = input.charCodeAt(tokPos + 1);\n\tif (next === 61) return finishOp(_equality, input.charCodeAt(tokPos + 2) === 61 ? 3 : 2);\n\treturn finishOp(code === 61 ? _eq : _prefix, 1);\n }\n\n function getTokenFromCode(code) {\n\tswitch(code) {\n\tcase 46:\n\t return readToken_dot();\n\n\tcase 40: ++tokPos; return finishToken(_parenL);\n\tcase 41: ++tokPos; return finishToken(_parenR);\n\tcase 59: ++tokPos; return finishToken(_semi);\n\tcase 44: ++tokPos; return finishToken(_comma);\n\tcase 91: ++tokPos; return finishToken(_bracketL);\n\tcase 93: ++tokPos; return finishToken(_bracketR);\n\tcase 123: ++tokPos; return finishToken(_braceL);\n\tcase 125: ++tokPos; return finishToken(_braceR);\n\tcase 58: ++tokPos; return finishToken(_colon);\n\tcase 63: ++tokPos; return finishToken(_question);\n\n\tcase 48:\n\t var next = input.charCodeAt(tokPos + 1);\n\t if (next === 120 || next === 88) return readHexNumber();\n\tcase 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57:\n\t return readNumber(false);\n\n\tcase 34: case 39:\n\t return readString(code);\n\n\tcase 47:\n\t return readToken_slash(code);\n\n\tcase 37: case 42:\n\t return readToken_mult_modulo();\n\n\tcase 124: case 38:\n\t return readToken_pipe_amp(code);\n\n\tcase 94:\n\t return readToken_caret();\n\n\tcase 43: case 45:\n\t return readToken_plus_min(code);\n\n\tcase 60: case 62:\n\t return readToken_lt_gt(code);\n\n\tcase 61: case 33:\n\t return readToken_eq_excl(code);\n\n\tcase 126:\n\t return finishOp(_prefix, 1);\n\t}\n\n\treturn false;\n }\n\n function readToken(forceRegexp) {\n\tif (!forceRegexp) tokStart = tokPos;\n\telse tokPos = tokStart + 1;\n\tif (options.locations) tokStartLoc = new line_loc_t;\n\tif (forceRegexp) return readRegexp();\n\tif (tokPos >= inputLen) return finishToken(_eof);\n\n\tvar code = input.charCodeAt(tokPos);\n\tif (isIdentifierStart(code) || code === 92 ) return readWord();\n\n\tvar tok = getTokenFromCode(code);\n\n\tif (tok === false) {\n\t var ch = String.fromCharCode(code);\n\t if (ch === \"\\\\\" || nonASCIIidentifierStart.test(ch)) return readWord();\n\t raise(tokPos, \"Unexpected character '\" + ch + \"'\");\n\t}\n\treturn tok;\n }\n\n function finishOp(type, size) {\n\tvar str = input.slice(tokPos, tokPos + size);\n\ttokPos += size;\n\tfinishToken(type, str);\n }\n\n function readRegexp() {\n\tvar content = \"\", escaped, inClass, start = tokPos;\n\tfor (;;) {\n\t if (tokPos >= inputLen) raise(start, \"Unterminated regular expression\");\n\t var ch = input.charAt(tokPos);\n\t if (newline.test(ch)) raise(start, \"Unterminated regular expression\");\n\t if (!escaped) {\n\t\tif (ch === \"[\") inClass = true;\n\t\telse if (ch === \"]\" && inClass) inClass = false;\n\t\telse if (ch === \"/\" && !inClass) break;\n\t\tescaped = ch === \"\\\\\";\n\t } else escaped = false;\n\t ++tokPos;\n\t}\n\tvar content = input.slice(start, tokPos);\n\t++tokPos;\n\tvar mods = readWord1();\n\tif (mods && !/^[gmsiy]*$/.test(mods)) raise(start, \"Invalid regexp flag\");\n\ttry {\n\t var value = new RegExp(content, mods);\n\t} catch (e) {\n\t if (e instanceof SyntaxError) raise(start, e.message);\n\t raise(e);\n\t}\n\treturn finishToken(_regexp, value);\n }\n\n function readInt(radix, len) {\n\tvar start = tokPos, total = 0;\n\tfor (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {\n\t var code = input.charCodeAt(tokPos), val;\n\t if (code >= 97) val = code - 97 + 10;\n\t else if (code >= 65) val = code - 65 + 10;\n\t else if (code >= 48 && code <= 57) val = code - 48;\n\t else val = Infinity;\n\t if (val >= radix) break;\n\t ++tokPos;\n\t total = total * radix + val;\n\t}\n\tif (tokPos === start || len != null && tokPos - start !== len) return null;\n\n\treturn total;\n }\n\n function readHexNumber() {\n\ttokPos += 2;\n\tvar val = readInt(16);\n\tif (val == null) raise(tokStart + 2, \"Expected hexadecimal number\");\n\tif (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, \"Identifier directly after number\");\n\treturn finishToken(_num, val);\n }\n\n function readNumber(startsWithDot) {\n\tvar start = tokPos, isFloat = false, octal = input.charCodeAt(tokPos) === 48;\n\tif (!startsWithDot && readInt(10) === null) raise(start, \"Invalid number\");\n\tif (input.charCodeAt(tokPos) === 46) {\n\t ++tokPos;\n\t readInt(10);\n\t isFloat = true;\n\t}\n\tvar next = input.charCodeAt(tokPos);\n\tif (next === 69 || next === 101) {\n\t next = input.charCodeAt(++tokPos);\n\t if (next === 43 || next === 45) ++tokPos;\n\t if (readInt(10) === null) raise(start, \"Invalid number\");\n\t isFloat = true;\n\t}\n\tif (isIdentifierStart(input.charCodeAt(tokPos))) raise(tokPos, \"Identifier directly after number\");\n\n\tvar str = input.slice(start, tokPos), val;\n\tif (isFloat) val = parseFloat(str);\n\telse if (!octal || str.length === 1) val = parseInt(str, 10);\n\telse if (/[89]/.test(str) || strict) raise(start, \"Invalid number\");\n\telse val = parseInt(str, 8);\n\treturn finishToken(_num, val);\n }\n\n function readString(quote) {\n\ttokPos++;\n\tvar out = \"\";\n\tfor (;;) {\n\t if (tokPos >= inputLen) raise(tokStart, \"Unterminated string constant\");\n\t var ch = input.charCodeAt(tokPos);\n\t if (ch === quote) {\n\t\t++tokPos;\n\t\treturn finishToken(_string, out);\n\t }\n\t if (ch === 92) {\n\t\tch = input.charCodeAt(++tokPos);\n\t\tvar octal = /^[0-7]+/.exec(input.slice(tokPos, tokPos + 3));\n\t\tif (octal) octal = octal[0];\n\t\twhile (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1);\n\t\tif (octal === \"0\") octal = null;\n\t\t++tokPos;\n\t\tif (octal) {\n\t\t if (strict) raise(tokPos - 2, \"Octal literal in strict mode\");\n\t\t out += String.fromCharCode(parseInt(octal, 8));\n\t\t tokPos += octal.length - 1;\n\t\t} else {\n\t\t switch (ch) {\n\t\t case 110: out += \"\\n\"; break;\n\t\t case 114: out += \"\\r\"; break;\n\t\t case 120: out += String.fromCharCode(readHexChar(2)); break;\n\t\t case 117: out += String.fromCharCode(readHexChar(4)); break;\n\t\t case 85: out += String.fromCharCode(readHexChar(8)); break;\n\t\t case 116: out += \"\\t\"; break;\n\t\t case 98: out += \"\\b\"; break;\n\t\t case 118: out += \"\\u000b\"; break;\n\t\t case 102: out += \"\\f\"; break;\n\t\t case 48: out += \"\\0\"; break;\n\t\t case 13: if (input.charCodeAt(tokPos) === 10) ++tokPos;\n\t\t case 10:\n\t\t\tif (options.locations) { tokLineStart = tokPos; ++tokCurLine; }\n\t\t\tbreak;\n\t\t default: out += String.fromCharCode(ch); break;\n\t\t }\n\t\t}\n\t } else {\n\t\tif (ch === 13 || ch === 10 || ch === 8232 || ch === 8233) raise(tokStart, \"Unterminated string constant\");\n\t\tout += String.fromCharCode(ch);\n\t\t++tokPos;\n\t }\n\t}\n }\n\n function readHexChar(len) {\n\tvar n = readInt(16, len);\n\tif (n === null) raise(tokStart, \"Bad character escape sequence\");\n\treturn n;\n }\n\n var containsEsc;\n\n function readWord1() {\n\tcontainsEsc = false;\n\tvar word, first = true, start = tokPos;\n\tfor (;;) {\n\t var ch = input.charCodeAt(tokPos);\n\t if (isIdentifierChar(ch)) {\n\t\tif (containsEsc) word += input.charAt(tokPos);\n\t\t++tokPos;\n\t } else if (ch === 92) {\n\t\tif (!containsEsc) word = input.slice(start, tokPos);\n\t\tcontainsEsc = true;\n\t\tif (input.charCodeAt(++tokPos) != 117)\n\t\t raise(tokPos, \"Expecting Unicode escape sequence \\\\uXXXX\");\n\t\t++tokPos;\n\t\tvar esc = readHexChar(4);\n\t\tvar escStr = String.fromCharCode(esc);\n\t\tif (!escStr) raise(tokPos - 1, \"Invalid Unicode escape\");\n\t\tif (!(first ? isIdentifierStart(esc) : isIdentifierChar(esc)))\n\t\t raise(tokPos - 4, \"Invalid Unicode escape\");\n\t\tword += escStr;\n\t } else {\n\t\tbreak;\n\t }\n\t first = false;\n\t}\n\treturn containsEsc ? word : input.slice(start, tokPos);\n }\n\n function readWord() {\n\tvar word = readWord1();\n\tvar type = _name;\n\tif (!containsEsc && isKeyword(word))\n\t type = keywordTypes[word];\n\treturn finishToken(type, word);\n }\n\n function next() {\n\tlastStart = tokStart;\n\tlastEnd = tokEnd;\n\tlastEndLoc = tokEndLoc;\n\treadToken();\n }\n\n function setStrict(strct) {\n\tstrict = strct;\n\ttokPos = tokStart;\n\tif (options.locations) {\n\t while (tokPos < tokLineStart) {\n\t\ttokLineStart = input.lastIndexOf(\"\\n\", tokLineStart - 2) + 1;\n\t\t--tokCurLine;\n\t }\n\t}\n\tskipSpace();\n\treadToken();\n }\n\n function node_t() {\n\tthis.type = null;\n\tthis.start = tokStart;\n\tthis.end = null;\n }\n\n function node_loc_t() {\n\tthis.start = tokStartLoc;\n\tthis.end = null;\n\tif (sourceFile !== null) this.source = sourceFile;\n }\n\n function startNode() {\n\tvar node = new node_t();\n\tif (options.locations)\n\t node.loc = new node_loc_t();\n\tif (options.directSourceFile)\n\t node.sourceFile = options.directSourceFile;\n\tif (options.ranges)\n\t node.range = [tokStart, 0];\n\treturn node;\n }\n\n function startNodeFrom(other) {\n\tvar node = new node_t();\n\tnode.start = other.start;\n\tif (options.locations) {\n\t node.loc = new node_loc_t();\n\t node.loc.start = other.loc.start;\n\t}\n\tif (options.ranges)\n\t node.range = [other.range[0], 0];\n\n\treturn node;\n }\n\n function finishNode(node, type) {\n\tnode.type = type;\n\tnode.end = lastEnd;\n\tif (options.locations)\n\t node.loc.end = lastEndLoc;\n\tif (options.ranges)\n\t node.range[1] = lastEnd;\n\treturn node;\n }\n\n function isUseStrict(stmt) {\n\treturn options.ecmaVersion >= 5 && stmt.type === \"ExpressionStatement\" &&\n\t stmt.expression.type === \"Literal\" && stmt.expression.value === \"use strict\";\n }\n\n function eat(type) {\n\tif (tokType === type) {\n\t next();\n\t return true;\n\t}\n }\n\n function canInsertSemicolon() {\n\treturn !options.strictSemicolons &&\n\t (tokType === _eof || tokType === _braceR || newline.test(input.slice(lastEnd, tokStart)));\n }\n\n function semicolon() {\n\tif (!eat(_semi) && !canInsertSemicolon()) unexpected();\n }\n\n function expect(type) {\n\tif (tokType === type) next();\n\telse unexpected();\n }\n\n function unexpected() {\n\traise(tokStart, \"Unexpected token\");\n }\n\n function checkLVal(expr) {\n\tif (expr.type !== \"Identifier\" && expr.type !== \"MemberExpression\")\n\t raise(expr.start, \"Assigning to rvalue\");\n\tif (strict && expr.type === \"Identifier\" && isStrictBadIdWord(expr.name))\n\t raise(expr.start, \"Assigning to \" + expr.name + \" in strict mode\");\n }\n\n function parseTopLevel(program) {\n\tlastStart = lastEnd = tokPos;\n\tif (options.locations) lastEndLoc = new line_loc_t;\n\tinFunction = strict = null;\n\tlabels = [];\n\treadToken();\n\n\tvar node = program || startNode(), first = true;\n\tif (!program) node.body = [];\n\twhile (tokType !== _eof) {\n\t var stmt = parseStatement();\n\t node.body.push(stmt);\n\t if (first && isUseStrict(stmt)) setStrict(true);\n\t first = false;\n\t}\n\treturn finishNode(node, \"Program\");\n }\n\n var loopLabel = {kind: \"loop\"}, switchLabel = {kind: \"switch\"};\n\n function parseStatement() {\n\tif (tokType === _slash || tokType === _assign && tokVal == \"/=\")\n\t readToken(true);\n\n\tvar starttype = tokType, node = startNode();\n\n\tswitch (starttype) {\n\tcase _break: case _continue:\n\t next();\n\t var isBreak = starttype === _break;\n\t if (eat(_semi) || canInsertSemicolon()) node.label = null;\n\t else if (tokType !== _name) unexpected();\n\t else {\n\t\tnode.label = parseIdent();\n\t\tsemicolon();\n\t }\n\n\t for (var i = 0; i < labels.length; ++i) {\n\t\tvar lab = labels[i];\n\t\tif (node.label == null || lab.name === node.label.name) {\n\t\t if (lab.kind != null && (isBreak || lab.kind === \"loop\")) break;\n\t\t if (node.label && isBreak) break;\n\t\t}\n\t }\n\t if (i === labels.length) raise(node.start, \"Unsyntactic \" + starttype.keyword);\n\t return finishNode(node, isBreak ? \"BreakStatement\" : \"ContinueStatement\");\n\n\tcase _debugger:\n\t next();\n\t semicolon();\n\t return finishNode(node, \"DebuggerStatement\");\n\n\tcase _do:\n\t next();\n\t labels.push(loopLabel);\n\t node.body = parseStatement();\n\t labels.pop();\n\t expect(_while);\n\t node.test = parseParenExpression();\n\t semicolon();\n\t return finishNode(node, \"DoWhileStatement\");\n\n\tcase _for:\n\t next();\n\t labels.push(loopLabel);\n\t expect(_parenL);\n\t if (tokType === _semi) return parseFor(node, null);\n\t if (tokType === _var) {\n\t\tvar init = startNode();\n\t\tnext();\n\t\tparseVar(init, true);\n\t\tfinishNode(init, \"VariableDeclaration\");\n\t\tif (init.declarations.length === 1 && eat(_in))\n\t\t return parseForIn(node, init);\n\t\treturn parseFor(node, init);\n\t }\n\t var init = parseExpression(false, true);\n\t if (eat(_in)) {checkLVal(init); return parseForIn(node, init);}\n\t return parseFor(node, init);\n\n\tcase _function:\n\t next();\n\t return parseFunction(node, true);\n\n\tcase _if:\n\t next();\n\t node.test = parseParenExpression();\n\t node.consequent = parseStatement();\n\t node.alternate = eat(_else) ? parseStatement() : null;\n\t return finishNode(node, \"IfStatement\");\n\n\tcase _return:\n\t if (!inFunction && !options.allowReturnOutsideFunction)\n\t\traise(tokStart, \"'return' outside of function\");\n\t next();\n\n\t if (eat(_semi) || canInsertSemicolon()) node.argument = null;\n\t else { node.argument = parseExpression(); semicolon(); }\n\t return finishNode(node, \"ReturnStatement\");\n\n\tcase _switch:\n\t next();\n\t node.discriminant = parseParenExpression();\n\t node.cases = [];\n\t expect(_braceL);\n\t labels.push(switchLabel);\n\n\t for (var cur, sawDefault; tokType != _braceR;) {\n\t\tif (tokType === _case || tokType === _default) {\n\t\t var isCase = tokType === _case;\n\t\t if (cur) finishNode(cur, \"SwitchCase\");\n\t\t node.cases.push(cur = startNode());\n\t\t cur.consequent = [];\n\t\t next();\n\t\t if (isCase) cur.test = parseExpression();\n\t\t else {\n\t\t\tif (sawDefault) raise(lastStart, \"Multiple default clauses\"); sawDefault = true;\n\t\t\tcur.test = null;\n\t\t }\n\t\t expect(_colon);\n\t\t} else {\n\t\t if (!cur) unexpected();\n\t\t cur.consequent.push(parseStatement());\n\t\t}\n\t }\n\t if (cur) finishNode(cur, \"SwitchCase\");\n\t next();\n\t labels.pop();\n\t return finishNode(node, \"SwitchStatement\");\n\n\tcase _throw:\n\t next();\n\t if (newline.test(input.slice(lastEnd, tokStart)))\n\t\traise(lastEnd, \"Illegal newline after throw\");\n\t node.argument = parseExpression();\n\t semicolon();\n\t return finishNode(node, \"ThrowStatement\");\n\n\tcase _try:\n\t next();\n\t node.block = parseBlock();\n\t node.handler = null;\n\t if (tokType === _catch) {\n\t\tvar clause = startNode();\n\t\tnext();\n\t\texpect(_parenL);\n\t\tclause.param = parseIdent();\n\t\tif (strict && isStrictBadIdWord(clause.param.name))\n\t\t raise(clause.param.start, \"Binding \" + clause.param.name + \" in strict mode\");\n\t\texpect(_parenR);\n\t\tclause.guard = null;\n\t\tclause.body = parseBlock();\n\t\tnode.handler = finishNode(clause, \"CatchClause\");\n\t }\n\t node.guardedHandlers = empty;\n\t node.finalizer = eat(_finally) ? parseBlock() : null;\n\t if (!node.handler && !node.finalizer)\n\t\traise(node.start, \"Missing catch or finally clause\");\n\t return finishNode(node, \"TryStatement\");\n\n\tcase _var:\n\t next();\n\t parseVar(node);\n\t semicolon();\n\t return finishNode(node, \"VariableDeclaration\");\n\n\tcase _while:\n\t next();\n\t node.test = parseParenExpression();\n\t labels.push(loopLabel);\n\t node.body = parseStatement();\n\t labels.pop();\n\t return finishNode(node, \"WhileStatement\");\n\n\tcase _with:\n\t if (strict) raise(tokStart, \"'with' in strict mode\");\n\t next();\n\t node.object = parseParenExpression();\n\t node.body = parseStatement();\n\t return finishNode(node, \"WithStatement\");\n\n\tcase _braceL:\n\t return parseBlock();\n\n\tcase _semi:\n\t next();\n\t return finishNode(node, \"EmptyStatement\");\n\n\tdefault:\n\t var maybeName = tokVal, expr = parseExpression();\n\t if (starttype === _name && expr.type === \"Identifier\" && eat(_colon)) {\n\t\tfor (var i = 0; i < labels.length; ++i)\n\t\t if (labels[i].name === maybeName) raise(expr.start, \"Label '\" + maybeName + \"' is already declared\");\n\t\tvar kind = tokType.isLoop ? \"loop\" : tokType === _switch ? \"switch\" : null;\n\t\tlabels.push({name: maybeName, kind: kind});\n\t\tnode.body = parseStatement();\n\t\tlabels.pop();\n\t\tnode.label = expr;\n\t\treturn finishNode(node, \"LabeledStatement\");\n\t } else {\n\t\tnode.expression = expr;\n\t\tsemicolon();\n\t\treturn finishNode(node, \"ExpressionStatement\");\n\t }\n\t}\n }\n\n function parseParenExpression() {\n\texpect(_parenL);\n\tvar val = parseExpression();\n\texpect(_parenR);\n\treturn val;\n }\n\n function parseBlock(allowStrict) {\n\tvar node = startNode(), first = true, strict = false, oldStrict;\n\tnode.body = [];\n\texpect(_braceL);\n\twhile (!eat(_braceR)) {\n\t var stmt = parseStatement();\n\t node.body.push(stmt);\n\t if (first && allowStrict && isUseStrict(stmt)) {\n\t\toldStrict = strict;\n\t\tsetStrict(strict = true);\n\t }\n\t first = false;\n\t}\n\tif (strict && !oldStrict) setStrict(false);\n\treturn finishNode(node, \"BlockStatement\");\n }\n\n function parseFor(node, init) {\n\tnode.init = init;\n\texpect(_semi);\n\tnode.test = tokType === _semi ? null : parseExpression();\n\texpect(_semi);\n\tnode.update = tokType === _parenR ? null : parseExpression();\n\texpect(_parenR);\n\tnode.body = parseStatement();\n\tlabels.pop();\n\treturn finishNode(node, \"ForStatement\");\n }\n\n function parseForIn(node, init) {\n\tnode.left = init;\n\tnode.right = parseExpression();\n\texpect(_parenR);\n\tnode.body = parseStatement();\n\tlabels.pop();\n\treturn finishNode(node, \"ForInStatement\");\n }\n\n function parseVar(node, noIn) {\n\tnode.declarations = [];\n\tnode.kind = \"var\";\n\tfor (;;) {\n\t var decl = startNode();\n\t decl.id = parseIdent();\n\t if (strict && isStrictBadIdWord(decl.id.name))\n\t\traise(decl.id.start, \"Binding \" + decl.id.name + \" in strict mode\");\n\t decl.init = eat(_eq) ? parseExpression(true, noIn) : null;\n\t node.declarations.push(finishNode(decl, \"VariableDeclarator\"));\n\t if (!eat(_comma)) break;\n\t}\n\treturn node;\n }\n\n function parseExpression(noComma, noIn) {\n\tvar expr = parseMaybeAssign(noIn);\n\tif (!noComma && tokType === _comma) {\n\t var node = startNodeFrom(expr);\n\t node.expressions = [expr];\n\t while (eat(_comma)) node.expressions.push(parseMaybeAssign(noIn));\n\t return finishNode(node, \"SequenceExpression\");\n\t}\n\treturn expr;\n }\n\n function parseMaybeAssign(noIn) {\n\tvar left = parseMaybeConditional(noIn);\n\tif (tokType.isAssign) {\n\t var node = startNodeFrom(left);\n\t node.operator = tokVal;\n\t node.left = left;\n\t next();\n\t node.right = parseMaybeAssign(noIn);\n\t checkLVal(left);\n\t return finishNode(node, \"AssignmentExpression\");\n\t}\n\treturn left;\n }\n\n function parseMaybeConditional(noIn) {\n\tvar expr = parseExprOps(noIn);\n\tif (eat(_question)) {\n\t var node = startNodeFrom(expr);\n\t node.test = expr;\n\t node.consequent = parseExpression(true);\n\t expect(_colon);\n\t node.alternate = parseExpression(true, noIn);\n\t return finishNode(node, \"ConditionalExpression\");\n\t}\n\treturn expr;\n }\n\n function parseExprOps(noIn) {\n\treturn parseExprOp(parseMaybeUnary(), -1, noIn);\n }\n\n function parseExprOp(left, minPrec, noIn) {\n\tvar prec = tokType.binop;\n\tif (prec != null && (!noIn || tokType !== _in)) {\n\t if (prec > minPrec) {\n\t\tvar node = startNodeFrom(left);\n\t\tnode.left = left;\n\t\tnode.operator = tokVal;\n\t\tvar op = tokType;\n\t\tnext();\n\t\tnode.right = parseExprOp(parseMaybeUnary(), prec, noIn);\n\t\tvar exprNode = finishNode(node, (op === _logicalOR || op === _logicalAND) ? \"LogicalExpression\" : \"BinaryExpression\");\n\t\treturn parseExprOp(exprNode, minPrec, noIn);\n\t }\n\t}\n\treturn left;\n }\n\n function parseMaybeUnary() {\n\tif (tokType.prefix) {\n\t var node = startNode(), update = tokType.isUpdate;\n\t node.operator = tokVal;\n\t node.prefix = true;\n\t tokRegexpAllowed = true;\n\t next();\n\t node.argument = parseMaybeUnary();\n\t if (update) checkLVal(node.argument);\n\t else if (strict && node.operator === \"delete\" &&\n\t\t\t node.argument.type === \"Identifier\")\n\t\traise(node.start, \"Deleting local variable in strict mode\");\n\t return finishNode(node, update ? \"UpdateExpression\" : \"UnaryExpression\");\n\t}\n\tvar expr = parseExprSubscripts();\n\twhile (tokType.postfix && !canInsertSemicolon()) {\n\t var node = startNodeFrom(expr);\n\t node.operator = tokVal;\n\t node.prefix = false;\n\t node.argument = expr;\n\t checkLVal(expr);\n\t next();\n\t expr = finishNode(node, \"UpdateExpression\");\n\t}\n\treturn expr;\n }\n\n function parseExprSubscripts() {\n\treturn parseSubscripts(parseExprAtom());\n }\n\n function parseSubscripts(base, noCalls) {\n\tif (eat(_dot)) {\n\t var node = startNodeFrom(base);\n\t node.object = base;\n\t node.property = parseIdent(true);\n\t node.computed = false;\n\t return parseSubscripts(finishNode(node, \"MemberExpression\"), noCalls);\n\t} else if (eat(_bracketL)) {\n\t var node = startNodeFrom(base);\n\t node.object = base;\n\t node.property = parseExpression();\n\t node.computed = true;\n\t expect(_bracketR);\n\t return parseSubscripts(finishNode(node, \"MemberExpression\"), noCalls);\n\t} else if (!noCalls && eat(_parenL)) {\n\t var node = startNodeFrom(base);\n\t node.callee = base;\n\t node.arguments = parseExprList(_parenR, false);\n\t return parseSubscripts(finishNode(node, \"CallExpression\"), noCalls);\n\t} else return base;\n }\n\n function parseExprAtom() {\n\tswitch (tokType) {\n\tcase _this:\n\t var node = startNode();\n\t next();\n\t return finishNode(node, \"ThisExpression\");\n\tcase _name:\n\t return parseIdent();\n\tcase _num: case _string: case _regexp:\n\t var node = startNode();\n\t node.value = tokVal;\n\t node.raw = input.slice(tokStart, tokEnd);\n\t next();\n\t return finishNode(node, \"Literal\");\n\n\tcase _null: case _true: case _false:\n\t var node = startNode();\n\t node.value = tokType.atomValue;\n\t node.raw = tokType.keyword;\n\t next();\n\t return finishNode(node, \"Literal\");\n\n\tcase _parenL:\n\t var tokStartLoc1 = tokStartLoc, tokStart1 = tokStart;\n\t next();\n\t var val = parseExpression();\n\t val.start = tokStart1;\n\t val.end = tokEnd;\n\t if (options.locations) {\n\t\tval.loc.start = tokStartLoc1;\n\t\tval.loc.end = tokEndLoc;\n\t }\n\t if (options.ranges)\n\t\tval.range = [tokStart1, tokEnd];\n\t expect(_parenR);\n\t return val;\n\n\tcase _bracketL:\n\t var node = startNode();\n\t next();\n\t node.elements = parseExprList(_bracketR, true, true);\n\t return finishNode(node, \"ArrayExpression\");\n\n\tcase _braceL:\n\t return parseObj();\n\n\tcase _function:\n\t var node = startNode();\n\t next();\n\t return parseFunction(node, false);\n\n\tcase _new:\n\t return parseNew();\n\n\tdefault:\n\t unexpected();\n\t}\n }\n\n function parseNew() {\n\tvar node = startNode();\n\tnext();\n\tnode.callee = parseSubscripts(parseExprAtom(), true);\n\tif (eat(_parenL)) node.arguments = parseExprList(_parenR, false);\n\telse node.arguments = empty;\n\treturn finishNode(node, \"NewExpression\");\n }\n\n function parseObj() {\n\tvar node = startNode(), first = true, sawGetSet = false;\n\tnode.properties = [];\n\tnext();\n\twhile (!eat(_braceR)) {\n\t if (!first) {\n\t\texpect(_comma);\n\t\tif (options.allowTrailingCommas && eat(_braceR)) break;\n\t } else first = false;\n\n\t var prop = {key: parsePropertyName()}, isGetSet = false, kind;\n\t if (eat(_colon)) {\n\t\tprop.value = parseExpression(true);\n\t\tkind = prop.kind = \"init\";\n\t } else if (options.ecmaVersion >= 5 && prop.key.type === \"Identifier\" &&\n\t\t\t\t (prop.key.name === \"get\" || prop.key.name === \"set\")) {\n\t\tisGetSet = sawGetSet = true;\n\t\tkind = prop.kind = prop.key.name;\n\t\tprop.key = parsePropertyName();\n\t\tif (tokType !== _parenL) unexpected();\n\t\tprop.value = parseFunction(startNode(), false);\n\t } else unexpected();\n\n\t if (prop.key.type === \"Identifier\" && (strict || sawGetSet)) {\n\t\tfor (var i = 0; i < node.properties.length; ++i) {\n\t\t var other = node.properties[i];\n\t\t if (other.key.name === prop.key.name) {\n\t\t\tvar conflict = kind == other.kind || isGetSet && other.kind === \"init\" ||\n\t\t\t kind === \"init\" && (other.kind === \"get\" || other.kind === \"set\");\n\t\t\tif (conflict && !strict && kind === \"init\" && other.kind === \"init\") conflict = false;\n\t\t\tif (conflict) raise(prop.key.start, \"Redefinition of property\");\n\t\t }\n\t\t}\n\t }\n\t node.properties.push(prop);\n\t}\n\treturn finishNode(node, \"ObjectExpression\");\n }\n\n function parsePropertyName() {\n\tif (tokType === _num || tokType === _string) return parseExprAtom();\n\treturn parseIdent(true);\n }\n\n function parseFunction(node, isStatement) {\n\tif (tokType === _name) node.id = parseIdent();\n\telse if (isStatement) unexpected();\n\telse node.id = null;\n\tnode.params = [];\n\tvar first = true;\n\texpect(_parenL);\n\twhile (!eat(_parenR)) {\n\t if (!first) expect(_comma); else first = false;\n\t node.params.push(parseIdent());\n\t}\n\n\tvar oldInFunc = inFunction, oldLabels = labels;\n\tinFunction = true; labels = [];\n\tnode.body = parseBlock(true);\n\tinFunction = oldInFunc; labels = oldLabels;\n\n\tif (strict || node.body.body.length && isUseStrict(node.body.body[0])) {\n\t for (var i = node.id ? -1 : 0; i < node.params.length; ++i) {\n\t\tvar id = i < 0 ? node.id : node.params[i];\n\t\tif (isStrictReservedWord(id.name) || isStrictBadIdWord(id.name))\n\t\t raise(id.start, \"Defining '\" + id.name + \"' in strict mode\");\n\t\tif (i >= 0) for (var j = 0; j < i; ++j) if (id.name === node.params[j].name)\n\t\t raise(id.start, \"Argument name clash in strict mode\");\n\t }\n\t}\n\n\treturn finishNode(node, isStatement ? \"FunctionDeclaration\" : \"FunctionExpression\");\n }\n\n function parseExprList(close, allowTrailingComma, allowEmpty) {\n\tvar elts = [], first = true;\n\twhile (!eat(close)) {\n\t if (!first) {\n\t\texpect(_comma);\n\t\tif (allowTrailingComma && options.allowTrailingCommas && eat(close)) break;\n\t } else first = false;\n\n\t if (allowEmpty && tokType === _comma) elts.push(null);\n\t else elts.push(parseExpression(true));\n\t}\n\treturn elts;\n }\n\n function parseIdent(liberal) {\n\tvar node = startNode();\n\tif (liberal && options.forbidReserved == \"everywhere\") liberal = false;\n\tif (tokType === _name) {\n\t if (!liberal &&\n\t\t (options.forbidReserved &&\n\t\t (options.ecmaVersion === 3 ? isReservedWord3 : isReservedWord5)(tokVal) ||\n\t\t strict && isStrictReservedWord(tokVal)) &&\n\t\t input.slice(tokStart, tokEnd).indexOf(\"\\\\\") == -1)\n\t\traise(tokStart, \"The keyword '\" + tokVal + \"' is reserved\");\n\t node.name = tokVal;\n\t} else if (liberal && tokType.keyword) {\n\t node.name = tokType.keyword;\n\t} else {\n\t unexpected();\n\t}\n\ttokRegexpAllowed = false;\n\tnext();\n\treturn finishNode(node, \"Identifier\");\n }\n\n});\n\n\t\tif (!acorn.version)\n\t\t\tacorn = null;\n\t}\n\n\tfunction parse(code, options) {\n\t\treturn (global.acorn || acorn).parse(code, options);\n\t}\n\n\tvar binaryOperators = {\n\t\t'+': '__add',\n\t\t'-': '__subtract',\n\t\t'*': '__multiply',\n\t\t'/': '__divide',\n\t\t'%': '__modulo',\n\t\t'==': '__equals',\n\t\t'!=': '__equals'\n\t};\n\n\tvar unaryOperators = {\n\t\t'-': '__negate',\n\t\t'+': '__self'\n\t};\n\n\tvar fields = Base.each(\n\t\t['add', 'subtract', 'multiply', 'divide', 'modulo', 'equals', 'negate'],\n\t\tfunction(name) {\n\t\t\tthis['__' + name] = '#' + name;\n\t\t},\n\t\t{\n\t\t\t__self: function() {\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}\n\t);\n\tPoint.inject(fields);\n\tSize.inject(fields);\n\tColor.inject(fields);\n\n\tfunction __$__(left, operator, right) {\n\t\tvar handler = binaryOperators[operator];\n\t\tif (left && left[handler]) {\n\t\t\tvar res = left[handler](right);\n\t\t\treturn operator === '!=' ? !res : res;\n\t\t}\n\t\tswitch (operator) {\n\t\tcase '+': return left + right;\n\t\tcase '-': return left - right;\n\t\tcase '*': return left * right;\n\t\tcase '/': return left / right;\n\t\tcase '%': return left % right;\n\t\tcase '==': return left == right;\n\t\tcase '!=': return left != right;\n\t\t}\n\t}\n\n\tfunction $__(operator, value) {\n\t\tvar handler = unaryOperators[operator];\n\t\tif (value && value[handler])\n\t\t\treturn value[handler]();\n\t\tswitch (operator) {\n\t\tcase '+': return +value;\n\t\tcase '-': return -value;\n\t\t}\n\t}\n\n\tfunction compile(code, options) {\n\t\tif (!code)\n\t\t\treturn '';\n\t\toptions = options || {};\n\n\t\tvar insertions = [];\n\n\t\tfunction getOffset(offset) {\n\t\t\tfor (var i = 0, l = insertions.length; i < l; i++) {\n\t\t\t\tvar insertion = insertions[i];\n\t\t\t\tif (insertion[0] >= offset)\n\t\t\t\t\tbreak;\n\t\t\t\toffset += insertion[1];\n\t\t\t}\n\t\t\treturn offset;\n\t\t}\n\n\t\tfunction getCode(node) {\n\t\t\treturn code.substring(getOffset(node.range[0]),\n\t\t\t\t\tgetOffset(node.range[1]));\n\t\t}\n\n\t\tfunction getBetween(left, right) {\n\t\t\treturn code.substring(getOffset(left.range[1]),\n\t\t\t\t\tgetOffset(right.range[0]));\n\t\t}\n\n\t\tfunction replaceCode(node, str) {\n\t\t\tvar start = getOffset(node.range[0]),\n\t\t\t\tend = getOffset(node.range[1]),\n\t\t\t\tinsert = 0;\n\t\t\tfor (var i = insertions.length - 1; i >= 0; i--) {\n\t\t\t\tif (start > insertions[i][0]) {\n\t\t\t\t\tinsert = i + 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tinsertions.splice(insert, 0, [start, str.length - end + start]);\n\t\t\tcode = code.substring(0, start) + str + code.substring(end);\n\t\t}\n\n\t\tfunction handleOverloading(node, parent) {\n\t\t\tswitch (node.type) {\n\t\t\tcase 'UnaryExpression':\n\t\t\t\tif (node.operator in unaryOperators\n\t\t\t\t\t\t&& node.argument.type !== 'Literal') {\n\t\t\t\t\tvar arg = getCode(node.argument);\n\t\t\t\t\treplaceCode(node, '$__(\"' + node.operator + '\", '\n\t\t\t\t\t\t\t+ arg + ')');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'BinaryExpression':\n\t\t\t\tif (node.operator in binaryOperators\n\t\t\t\t\t\t&& node.left.type !== 'Literal') {\n\t\t\t\t\tvar left = getCode(node.left),\n\t\t\t\t\t\tright = getCode(node.right),\n\t\t\t\t\t\tbetween = getBetween(node.left, node.right),\n\t\t\t\t\t\toperator = node.operator;\n\t\t\t\t\treplaceCode(node, '__$__(' + left + ','\n\t\t\t\t\t\t\t+ between.replace(new RegExp('\\\\' + operator),\n\t\t\t\t\t\t\t\t'\"' + operator + '\"')\n\t\t\t\t\t\t\t+ ', ' + right + ')');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'UpdateExpression':\n\t\t\tcase 'AssignmentExpression':\n\t\t\t\tvar parentType = parent && parent.type;\n\t\t\t\tif (!(\n\t\t\t\t\t\tparentType === 'ForStatement'\n\t\t\t\t\t\t|| parentType === 'BinaryExpression'\n\t\t\t\t\t\t\t&& /^[=!<>]/.test(parent.operator)\n\t\t\t\t\t\t|| parentType === 'MemberExpression' && parent.computed\n\t\t\t\t)) {\n\t\t\t\t\tif (node.type === 'UpdateExpression') {\n\t\t\t\t\t\tvar arg = getCode(node.argument),\n\t\t\t\t\t\t\texp = '__$__(' + arg + ', \"' + node.operator[0]\n\t\t\t\t\t\t\t\t\t+ '\", 1)',\n\t\t\t\t\t\t\tstr = arg + ' = ' + exp;\n\t\t\t\t\t\tif (node.prefix) {\n\t\t\t\t\t\t\tstr = '(' + str + ')';\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\tparentType === 'AssignmentExpression' ||\n\t\t\t\t\t\t\tparentType === 'VariableDeclarator' ||\n\t\t\t\t\t\t\tparentType === 'BinaryExpression'\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tif (getCode(parent.left || parent.id) === arg)\n\t\t\t\t\t\t\t\tstr = exp;\n\t\t\t\t\t\t\tstr = arg + '; ' + str;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treplaceCode(node, str);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (/^.=$/.test(node.operator)\n\t\t\t\t\t\t\t\t&& node.left.type !== 'Literal') {\n\t\t\t\t\t\t\tvar left = getCode(node.left),\n\t\t\t\t\t\t\t\tright = getCode(node.right),\n\t\t\t\t\t\t\t\texp = left + ' = __$__(' + left + ', \"'\n\t\t\t\t\t\t\t\t\t+ node.operator[0] + '\", ' + right + ')';\n\t\t\t\t\t\t\treplaceCode(node, /^\\(.*\\)$/.test(getCode(node))\n\t\t\t\t\t\t\t\t\t? '(' + exp + ')' : exp);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfunction handleExports(node) {\n\t\t\tswitch (node.type) {\n\t\t\tcase 'ExportDefaultDeclaration':\n\t\t\t\treplaceCode({\n\t\t\t\t\trange: [node.start, node.declaration.start]\n\t\t\t\t}, 'module.exports = ');\n\t\t\t\tbreak;\n\t\t\tcase 'ExportNamedDeclaration':\n\t\t\t\tvar declaration = node.declaration;\n\t\t\t\tvar specifiers = node.specifiers;\n\t\t\t\tif (declaration) {\n\t\t\t\t\tvar declarations = declaration.declarations;\n\t\t\t\t\tif (declarations) {\n\t\t\t\t\t\tdeclarations.forEach(function(dec) {\n\t\t\t\t\t\t\treplaceCode(dec, 'module.exports.' + getCode(dec));\n\t\t\t\t\t\t});\n\t\t\t\t\t\treplaceCode({\n\t\t\t\t\t\t\trange: [\n\t\t\t\t\t\t\t\tnode.start,\n\t\t\t\t\t\t\t\tdeclaration.start + declaration.kind.length\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}, '');\n\t\t\t\t\t}\n\t\t\t\t} else if (specifiers) {\n\t\t\t\t\tvar exports = specifiers.map(function(specifier) {\n\t\t\t\t\t\tvar name = getCode(specifier);\n\t\t\t\t\t\treturn 'module.exports.' + name + ' = ' + name + '; ';\n\t\t\t\t\t}).join('');\n\t\t\t\t\tif (exports) {\n\t\t\t\t\t\treplaceCode(node, exports);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfunction walkAST(node, parent, paperFeatures) {\n\t\t\tif (node) {\n\t\t\t\tfor (var key in node) {\n\t\t\t\t\tif (key !== 'range' && key !== 'loc') {\n\t\t\t\t\t\tvar value = node[key];\n\t\t\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\t\t\tfor (var i = 0, l = value.length; i < l; i++) {\n\t\t\t\t\t\t\t\twalkAST(value[i], node, paperFeatures);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (value && typeof value === 'object') {\n\t\t\t\t\t\t\twalkAST(value, node, paperFeatures);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (paperFeatures.operatorOverloading !== false) {\n\t\t\t\t\thandleOverloading(node, parent);\n\t\t\t\t}\n\t\t\t\tif (paperFeatures.moduleExports !== false) {\n\t\t\t\t\thandleExports(node);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction encodeVLQ(value) {\n\t\t\tvar res = '',\n\t\t\t\tbase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\t\t\tvalue = (Math.abs(value) << 1) + (value < 0 ? 1 : 0);\n\t\t\twhile (value || !res) {\n\t\t\t\tvar next = value & (32 - 1);\n\t\t\t\tvalue >>= 5;\n\t\t\t\tif (value)\n\t\t\t\t\tnext |= 32;\n\t\t\t\tres += base64[next];\n\t\t\t}\n\t\t\treturn res;\n\t\t}\n\n\t\tvar url = options.url || '',\n\t\t\tsourceMaps = options.sourceMaps,\n\t\t\tpaperFeatures = options.paperFeatures || {},\n\t\t\tsource = options.source || code,\n\t\t\toffset = options.offset || 0,\n\t\t\tagent = paper.agent,\n\t\t\tversion = agent.versionNumber,\n\t\t\toffsetCode = false,\n\t\t\tlineBreaks = /\\r\\n|\\n|\\r/mg,\n\t\t\tmap;\n\t\tif (sourceMaps && (agent.chrome && version >= 30\n\t\t\t\t|| agent.webkit && version >= 537.76\n\t\t\t\t|| agent.firefox && version >= 23\n\t\t\t\t|| agent.node)) {\n\t\t\tif (agent.node) {\n\t\t\t\toffset -= 2;\n\t\t\t} else if (window && url && !window.location.href.indexOf(url)) {\n\t\t\t\tvar html = document.getElementsByTagName('html')[0].innerHTML;\n\t\t\t\toffset = html.substr(0, html.indexOf(code) + 1).match(\n\t\t\t\t\t\tlineBreaks).length + 1;\n\t\t\t}\n\t\t\toffsetCode = offset > 0 && !(\n\t\t\t\t\tagent.chrome && version >= 36 ||\n\t\t\t\t\tagent.safari && version >= 600 ||\n\t\t\t\t\tagent.firefox && version >= 40 ||\n\t\t\t\t\tagent.node);\n\t\t\tvar mappings = ['AA' + encodeVLQ(offsetCode ? 0 : offset) + 'A'];\n\t\t\tmappings.length = (code.match(lineBreaks) || []).length + 1\n\t\t\t\t\t+ (offsetCode ? offset : 0);\n\t\t\tmap = {\n\t\t\t\tversion: 3,\n\t\t\t\tfile: url,\n\t\t\t\tnames:[],\n\t\t\t\tmappings: mappings.join(';AACA'),\n\t\t\t\tsourceRoot: '',\n\t\t\t\tsources: [url],\n\t\t\t\tsourcesContent: [source]\n\t\t\t};\n\t\t}\n\t\tif (\n\t\t\tpaperFeatures.operatorOverloading !== false ||\n\t\t\tpaperFeatures.moduleExports !== false\n\t\t) {\n\t\t\twalkAST(parse(code, {\n\t\t\t\tranges: true,\n\t\t\t\tpreserveParens: true,\n\t\t\t\tsourceType: 'module'\n\t\t\t}), null, paperFeatures);\n\t\t}\n\t\tif (map) {\n\t\t\tif (offsetCode) {\n\t\t\t\tcode = new Array(offset + 1).join('\\n') + code;\n\t\t\t}\n\t\t\tif (/^(inline|both)$/.test(sourceMaps)) {\n\t\t\t\tcode += \"\\n//# sourceMappingURL=data:application/json;base64,\"\n\t\t\t\t\t\t+ self.btoa(unescape(encodeURIComponent(\n\t\t\t\t\t\t\tJSON.stringify(map))));\n\t\t\t}\n\t\t\tcode += \"\\n//# sourceURL=\" + (url || 'paperscript');\n\t\t}\n\t\treturn {\n\t\t\turl: url,\n\t\t\tsource: source,\n\t\t\tcode: code,\n\t\t\tmap: map\n\t\t};\n\t}\n\n\tfunction execute(code, scope, options) {\n\t\tpaper = scope;\n\t\tvar view = scope.getView(),\n\t\t\ttool = /\\btool\\.\\w+|\\s+on(?:Key|Mouse)(?:Up|Down|Move|Drag)\\b/\n\t\t\t\t\t.test(code) && !/\\bnew\\s+Tool\\b/.test(code)\n\t\t\t\t\t\t? new Tool() : null,\n\t\t\ttoolHandlers = tool ? tool._events : [],\n\t\t\thandlers = ['onFrame', 'onResize'].concat(toolHandlers),\n\t\t\tparams = [],\n\t\t\targs = [],\n\t\t\tfunc,\n\t\t\tcompiled = typeof code === 'object' ? code : compile(code, options);\n\t\tcode = compiled.code;\n\t\tfunction expose(scope, hidden) {\n\t\t\tfor (var key in scope) {\n\t\t\t\tif ((hidden || !/^_/.test(key)) && new RegExp('([\\\\b\\\\s\\\\W]|^)'\n\t\t\t\t\t\t+ key.replace(/\\$/g, '\\\\$') + '\\\\b').test(code)) {\n\t\t\t\t\tparams.push(key);\n\t\t\t\t\targs.push(scope[key]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\texpose({ __$__: __$__, $__: $__, paper: scope, tool: tool },\n\t\t\t\ttrue);\n\t\texpose(scope);\n\t\tcode = 'var module = { exports: {} }; ' + code;\n\t\tvar exports = Base.each(handlers, function(key) {\n\t\t\tif (new RegExp('\\\\s+' + key + '\\\\b').test(code)) {\n\t\t\t\tparams.push(key);\n\t\t\t\tthis.push('module.exports.' + key + ' = ' + key + ';');\n\t\t\t}\n\t\t}, []).join('\\n');\n\t\tif (exports) {\n\t\t\tcode += '\\n' + exports;\n\t\t}\n\t\tcode += '\\nreturn module.exports;';\n\t\tvar agent = paper.agent;\n\t\tif (document && (agent.chrome\n\t\t\t\t|| agent.firefox && agent.versionNumber < 40)) {\n\t\t\tvar script = document.createElement('script'),\n\t\t\t\thead = document.head || document.getElementsByTagName('head')[0];\n\t\t\tif (agent.firefox)\n\t\t\t\tcode = '\\n' + code;\n\t\t\tscript.appendChild(document.createTextNode(\n\t\t\t\t'document.__paperscript__ = function(' + params + ') {' +\n\t\t\t\t\tcode +\n\t\t\t\t'\\n}'\n\t\t\t));\n\t\t\thead.appendChild(script);\n\t\t\tfunc = document.__paperscript__;\n\t\t\tdelete document.__paperscript__;\n\t\t\thead.removeChild(script);\n\t\t} else {\n\t\t\tfunc = Function(params, code);\n\t\t}\n\t\tvar exports = func && func.apply(scope, args);\n\t\tvar obj = exports || {};\n\t\tBase.each(toolHandlers, function(key) {\n\t\t\tvar value = obj[key];\n\t\t\tif (value)\n\t\t\t\ttool[key] = value;\n\t\t});\n\t\tif (view) {\n\t\t\tif (obj.onResize)\n\t\t\t\tview.setOnResize(obj.onResize);\n\t\t\tview.emit('resize', {\n\t\t\t\tsize: view.size,\n\t\t\t\tdelta: new Point()\n\t\t\t});\n\t\t\tif (obj.onFrame)\n\t\t\t\tview.setOnFrame(obj.onFrame);\n\t\t\tview.requestUpdate();\n\t\t}\n\t\treturn exports;\n\t}\n\n\tfunction loadScript(script) {\n\t\tif (/^text\\/(?:x-|)paperscript$/.test(script.type)\n\t\t\t\t&& PaperScope.getAttribute(script, 'ignore') !== 'true') {\n\t\t\tvar canvasId = PaperScope.getAttribute(script, 'canvas'),\n\t\t\t\tcanvas = document.getElementById(canvasId),\n\t\t\t\tsrc = script.src || script.getAttribute('data-src'),\n\t\t\t\tasync = PaperScope.hasAttribute(script, 'async'),\n\t\t\t\tscopeAttribute = 'data-paper-scope';\n\t\t\tif (!canvas)\n\t\t\t\tthrow new Error('Unable to find canvas with id \"'\n\t\t\t\t\t\t+ canvasId + '\"');\n\t\t\tvar scope = PaperScope.get(canvas.getAttribute(scopeAttribute))\n\t\t\t\t\t\t|| new PaperScope().setup(canvas);\n\t\t\tcanvas.setAttribute(scopeAttribute, scope._id);\n\t\t\tif (src) {\n\t\t\t\tHttp.request({\n\t\t\t\t\turl: src,\n\t\t\t\t\tasync: async,\n\t\t\t\t\tmimeType: 'text/plain',\n\t\t\t\t\tonLoad: function(code) {\n\t\t\t\t\t\texecute(code, scope, src);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\texecute(script.innerHTML, scope, script.baseURI);\n\t\t\t}\n\t\t\tscript.setAttribute('data-paper-ignore', 'true');\n\t\t\treturn scope;\n\t\t}\n\t}\n\n\tfunction loadAll() {\n\t\tBase.each(document && document.getElementsByTagName('script'),\n\t\t\t\tloadScript);\n\t}\n\n\tfunction load(script) {\n\t\treturn script ? loadScript(script) : loadAll();\n\t}\n\n\tif (window) {\n\t\tif (document.readyState === 'complete') {\n\t\t\tsetTimeout(loadAll);\n\t\t} else {\n\t\t\tDomEvent.add(window, { load: loadAll });\n\t\t}\n\t}\n\n\treturn {\n\t\tcompile: compile,\n\t\texecute: execute,\n\t\tload: load,\n\t\tparse: parse,\n\t\tcalculateBinary: __$__,\n\t\tcalculateUnary: $__\n\t};\n\n}.call(this);\n\nvar paper = new (PaperScope.inject(Base.exports, {\n\tBase: Base,\n\tNumerical: Numerical,\n\tKey: Key,\n\tDomEvent: DomEvent,\n\tDomElement: DomElement,\n\tdocument: document,\n\twindow: window,\n\tSymbol: SymbolDefinition,\n\tPlacedSymbol: SymbolItem\n}))();\n\nif (paper.agent.node) {\n\trequire('./node/extend.js')(paper);\n}\n\nif (typeof define === 'function' && define.amd) {\n\tdefine('paper', paper);\n} else if (typeof module === 'object' && module) {\n\tmodule.exports = paper;\n}\n\nreturn paper;\n}.call(this, typeof self === 'object' ? self : null);\n","/**\n * Copyright (c) 2019 Vernier Software. All rights reserved.\n * This code may only be used under the BSD 3-Clause license found at\n * https://raw.githubusercontent.com/VernierST/godirect-js/master/LICENSE\n */\n\n!function(e,t){\"object\"==typeof exports&&\"undefined\"!=typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define(t):(e=e||self).godirect=t()}(this,function(){\"use strict\";function e(e,t,n,r,i,a,o){try{var s=e[a](o),u=s.value}catch(e){return void n(e)}s.done?t(u):Promise.resolve(u).then(r,i)}function t(t){return function(){var n=this,r=arguments;return new Promise(function(i,a){var o=t.apply(n,r);function s(t){e(o,i,a,s,u,\"next\",t)}function u(t){e(o,i,a,s,u,\"throw\",t)}s(void 0)})}}function n(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function r(e,t){for(var n=0;n=0;--a){var o=this.tryEntries[a],s=o.completion;if(\"root\"===o.tryLoc)return i(\"end\");if(o.tryLoc<=this.prev){var u=r.call(o,\"catchLoc\"),c=r.call(o,\"finallyLoc\");if(u&&c){if(this.prev=0;--n){var i=this.tryEntries[n];if(i.tryLoc<=this.prev&&r.call(i,\"finallyLoc\")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),M(n),d}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if(\"throw\"===r.type){var i=r.arg;M(n)}return i}}throw new Error(\"illegal catch attempt\")},delegateYield:function(e,n,r){return this.delegate={iterator:C(e),resultName:n,nextLoc:r},\"next\"===this.method&&(this.arg=t),d}}}function b(e,t,n,r){var i=t&&t.prototype instanceof _?t:_,a=Object.create(i.prototype),o=new A(r||[]);return a._invoke=function(e,t,n){var r=f;return function(i,a){if(r===l)throw new Error(\"Generator is already running\");if(r===p){if(\"throw\"===i)throw a;return L()}for(n.method=i,n.arg=a;;){var o=n.delegate;if(o){var s=R(o,n);if(s){if(s===d)continue;return s}}if(\"next\"===n.method)n.sent=n._sent=n.arg;else if(\"throw\"===n.method){if(r===f)throw r=p,n.arg;n.dispatchException(n.arg)}else\"return\"===n.method&&n.abrupt(\"return\",n.arg);r=l;var u=w(e,t,n);if(\"normal\"===u.type){if(r=n.done?p:h,u.arg===d)continue;return{value:u.arg,done:n.done}}\"throw\"===u.type&&(r=p,n.method=\"throw\",n.arg=u.arg)}}}(e,n,o),a}function w(e,t,n){try{return{type:\"normal\",arg:e.call(t,n)}}catch(e){return{type:\"throw\",arg:e}}}function _(){}function k(){}function x(){}function E(e){[\"next\",\"throw\",\"return\"].forEach(function(t){e[t]=function(e){return this._invoke(t,e)}})}function S(e){var t;this._invoke=function(n,i){function a(){return new Promise(function(t,a){!function t(n,i,a,o){var s=w(e[n],e,i);if(\"throw\"!==s.type){var u=s.arg,c=u.value;return c&&\"object\"==typeof c&&r.call(c,\"__await\")?Promise.resolve(c.__await).then(function(e){t(\"next\",e,a,o)},function(e){t(\"throw\",e,a,o)}):Promise.resolve(c).then(function(e){u.value=e,a(u)},function(e){return t(\"throw\",e,a,o)})}o(s.arg)}(n,i,t,a)})}return t=t?t.then(a,a):a()}}function R(e,n){var r=e.iterator[n.method];if(r===t){if(n.delegate=null,\"throw\"===n.method){if(e.iterator.return&&(n.method=\"return\",n.arg=t,R(e,n),\"throw\"===n.method))return d;n.method=\"throw\",n.arg=new TypeError(\"The iterator does not provide a 'throw' method\")}return d}var i=w(r,e.iterator,n.arg);if(\"throw\"===i.type)return n.method=\"throw\",n.arg=i.arg,n.delegate=null,d;var a=i.arg;return a?a.done?(n[e.resultName]=a.value,n.next=e.nextLoc,\"return\"!==n.method&&(n.method=\"next\",n.arg=t),n.delegate=null,d):a:(n.method=\"throw\",n.arg=new TypeError(\"iterator result is not an object\"),n.delegate=null,d)}function U(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function M(e){var t=e.completion||{};t.type=\"normal\",delete t.arg,e.completion=t}function A(e){this.tryEntries=[{tryLoc:\"root\"}],e.forEach(U,this),this.reset(!0)}function C(e){if(e){var n=e[a];if(n)return n.call(e);if(\"function\"==typeof e.next)return e;if(!isNaN(e.length)){var i=-1,o=function n(){for(;++i-1)return n.splice(r,1),this._listenerMap.set(e,n),!0}return!1}},{key:\"unbind\",value:function(){this._listenerMap.clear()}},{key:\"emit\",value:function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r0&&void 0!==arguments[0]?arguments[0]:{};n(this,e),this.type=t.type,this.mode=t.mode,this.minValue=t.minValue,this.maxValue=t.maxValue,this.uncertainty=t.uncertainty,this.minPeriod=t.minPeriod,this.maxPeriod=t.maxPeriod,this.typicalPeriod=t.typicalPeriod,this.granularity=t.granularity},P=function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};n(this,e),this.number=t.number,this.name=t.name,this.unit=t.unit,this.id=t.id,this.mutalExclusionMask=t.mutalExclusionMask,this.measurementInfo=t.measurementInfo},T=function(e){function t(e){var r;return n(this,t),(r=u(this,o(t).call(this))).number=e.number,r.name=e.name,r.unit=e.unit,r.specs=e,r.enabled=!1,r.values=[],r.value=null,r}return a(t,R),i(t,[{key:\"clear\",value:function(){this.value=null,this.values=[]}},{key:\"setValue\",value:function(e,t){this.value=e,t&&this.values.push(this.value),this.emit(\"value-changed\",this)}},{key:\"setEnabled\",value:function(e){this.enabled!==e&&(this.enabled=e,this.emit(\"state-changed\",this))}}]),t}(),N=function(e){function r(e){var t;if(n(this,r),t=u(this,o(r).call(this)),\"undefined\"==typeof TextDecoder){var i=require(\"text-encoding\");C=i.TextDecoder}else C=TextDecoder;return t.device=e,t.sensors=[],t.opened=!1,t.rollingCounter=0,t.collecting=!1,t.measurementPeriod=10,t.response=null,t.remainingResponseLength=0,t.defaultSensorsMask=0,t.keepValues=!0,t.minMeasurementPeriod=10,t.serialNumber=\"\",t.orderCode=\"\",t.name=\"\",t}return a(r,R),i(r,[{key:\"getBatteryLevel\",value:function(){var e=t(regeneratorRuntime.mark(function e(){var t;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this._getStatus();case 2:return t=e.sent,e.abrupt(\"return\",t.battery);case 4:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"getChargingState\",value:function(){var e=t(regeneratorRuntime.mark(function e(){var t;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this._getStatus();case 2:return t=e.sent,e.abrupt(\"return\",t.chargingStatus);case 4:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"open\",value:function(){var e=t(regeneratorRuntime.mark(function e(){var t,n=arguments;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return t=n.length>0&&void 0!==n[0]&&n[0],e.prev=1,e.next=4,this._connect();case 4:return e.next=6,this._init();case 6:return e.next=8,this._getStatus();case 8:return e.next=10,this._getDeviceInfo();case 10:return e.next=12,this._getDefaultSensorsMask();case 12:return e.next=14,this._getAvailableSensors();case 14:this._onOpened(),t&&this.start(),e.next=21;break;case 18:e.prev=18,e.t0=e.catch(1),console.error(e.t0);case 21:case\"end\":return e.stop()}},e,this,[[1,18]])}));return function(){return e.apply(this,arguments)}}()},{key:\"close\",value:function(){var e=t(regeneratorRuntime.mark(function e(){return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this._stopMeasurements();case 2:return e.next=4,this._sendCommand(d.DISCONNECT);case 4:return e.abrupt(\"return\",this._disconnect());case 5:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"enableDefaultSensors\",value:function(){for(var e=1,t=0;t<32;++t){if((this.defaultSensorsMask&e)===e){var n=this.getSensor(t);n&&n.setEnabled(!0)}e<<=1}}},{key:\"start\",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=this.sensors.filter(function(e){return e.enabled});0===t.length&&(this.enableDefaultSensors(),t=this.sensors.filter(function(e){return e.enabled})),t.forEach(function(e){return e.clear()}),e&&(this.measurementPeriod=e),this._startMeasurements()}},{key:\"stop\",value:function(){this._stopMeasurements()}},{key:\"getSensor\",value:function(e){return this.sensors.find(function(t){return t.number===e})}},{key:\"_connect\",value:function(){var e=t(regeneratorRuntime.mark(function e(){var t=this;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt(\"return\",this.device.setup({onClosed:function(){return t._onClosed()},onResponse:function(e){return t._handleResponse(e)}}));case 1:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"_disconnect\",value:function(){var e=t(regeneratorRuntime.mark(function e(){return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt(\"return\",this.device.close());case 1:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"_init\",value:function(){return this.collecting=!1,this.rollingCounter=255,this.writeQueue=[],this._sendCommand(d.INIT)}},{key:\"_handleResponse\",value:function(e){if(U(\"command notified: \".concat(A(e.buffer))),this.remainingResponseLegnth>0){if(this.remainingResponseLegnth-=e.buffer.byteLength,this.response=new DataView((t=this.response.buffer,n=e.buffer.slice(0),(r=new Uint8Array(t.byteLength+n.byteLength)).set(new Uint8Array(t),0),r.set(new Uint8Array(n),t.byteLength),r.buffer)),this.remainingResponseLegnth>0)return}else this.response=e;var t,n,r,i=this.response.getUint8(1);if(i>this.response.buffer.byteLength)this.remainingResponseLegnth=i-this.response.buffer.byteLength;else switch(U(\"handle command: \".concat(A(this.response.buffer))),this.response.getUint8(0)){case E:this._processMeasurements(this.response);break;default:var a=this.response.getUint8(4),o=this.response.getUint8(5),s=new DataView(this.response.buffer,6);this._resolveWriteCommand(a,o,s),this.remainingResponseLegnth=0,this.response=null}}},{key:\"_getSensorsWithMask\",value:function(e){for(var t=[],n=1,r=0;r<32;++r){if((e&n)===n){var i=this.getSensor(r);i&&(t.push(i),U(\"available: [\".concat(e,\"] \").concat(t[t.length-1].number)))}n<<=1}return t}},{key:\"_processMeasurements\",value:function(e){var t=[],n=!0,r=0,i=0,a=e.getUint8(4);switch(a){case m:t=this._getSensorsWithMask(e.getUint16(5,!0)),r=e.getUint8(7,!0),i=9;break;case v:t=this._getSensorsWithMask(e.getUint32(5,!0)),r=e.getUint8(9,!0),i=11;break;case g:case y:t[0]=this.getSensor(e.getUint8(6)),r=e.getUint8(7,!0),i=8;break;case w:case b:t[0]=this.getSensor(e.getUint8(6)),r=e.getUint8(7,!0),i=8,n=!1;break;case _:case k:case x:U(\"Purposely Ignoring packet type: \".concat(a));break;default:U(\"Unknown packet type: \".concat(a))}for(var o=0;o255?(U(\"Checksum failed!\"),0):n}},{key:\"_sendCommand\",value:function(e){var t=new Uint8Array(d.HEADER.byteLength+e.byteLength);return t.set(new Uint8Array(d.HEADER),0),t.set(new Uint8Array(e),d.HEADER.byteLength),t[1]=t.length,t[2]=this._decRollingCounter(),t[3]=this._calculateChecksum(t),this._queueWriteCommand(t,0,t.length)}},{key:\"_writeCommand\",value:function(){var e=t(regeneratorRuntime.mark(function e(t,n,r){var i;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(!(r>0)){e.next=12;break}return e.prev=1,r>20?(i=t.subarray(n,n+20),r-=20,n+=20):(i=t.subarray(n,n+r),r=0),e.next=5,this.device.writeCommand(i);case 5:e.next=10;break;case 7:e.prev=7,e.t0=e.catch(1),U(\"Write Failure: \".concat(e.t0));case 10:e.next=0;break;case 12:case\"end\":return e.stop()}},e,this,[[1,7]])}));return function(t,n,r){return e.apply(this,arguments)}}()},{key:\"_queueWriteCommand\",value:function(e,t,n){var r=this;U(\"command queued: \".concat(A(e)));var i=new Promise(function(t,n){r.writeQueue.push({command:e[4],rollingCounter:e[2],resolve:t,reject:n}),setTimeout(function(){r.writeQueue=r.writeQueue.filter(function(t){return t.command===e[4]&&t.rollingCounter!==e[2]}),n(new Error(\"write command timed out after 5s. Command: \".concat(e[4].toString(16),\" Rolling Counter: \").concat(e[2].toString(16))))},1e4)});return this._writeCommand(e,t,n),i}},{key:\"_getStatus\",value:function(){var e=t(regeneratorRuntime.mark(function e(){var t,n;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this._sendCommand(d.GET_STATUS);case 2:return t=e.sent,n={masterFirmwareVersion:\"\".concat(t.getUint8(2),\".\").concat(t.getUint8(3)),bleFirmwareVersion:\"\".concat(t.getUint8(6),\".\").concat(t.getUint8(9)),battery:t.getUint8(10),chargingStatus:\"\".concat(t.getUint8(11))},e.abrupt(\"return\",n);case 5:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"_getAvailableSensors\",value:function(){var e=t(regeneratorRuntime.mark(function e(){var t,n,r=this;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,this._sendCommand(d.GET_SENSOR_IDS).then(function(e){r.availableSensors=e.getUint32(0,!0),U(\"Get Available Sensors Returned \".concat(r.availableSensors))});case 2:t=1,n=0;case 4:if(!(n<31)){e.next=12;break}if((this.availableSensors&t)!==t){e.next=8;break}return e.next=8,this._getSensorInfo(n);case 8:t<<=1;case 9:++n,e.next=4;break;case 12:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"_getDefaultSensorsMask\",value:function(){var e=this;return this._sendCommand(d.GET_DEFAULT_SENSORS_MASK).then(function(t){e.defaultSensorsMask=t.getUint32(0,!0),U(\"Default Sensors:\"),M(e)})}},{key:\"_getDeviceInfo\",value:function(){var e=this;return this._sendCommand(d.GET_INFO).then(function(t){var n=new C(\"utf-8\");e.orderCode=n.decode(new Uint8Array(t.buffer,6,16).filter(S)),e.serialNumber=n.decode(new Uint8Array(t.buffer,22,16).filter(S)),e.name=n.decode(new Uint8Array(t.buffer,38,32).filter(S)),U(\"Device Info:\"),M(e)})}},{key:\"_getSensorInfo\",value:function(){var e=t(regeneratorRuntime.mark(function e(t){var n,r=this;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return(n=new Uint8Array(d.GET_SENSOR_INFO))[1]=t,e.abrupt(\"return\",this._sendCommand(n).then(function(e){var t=e.getUint32(2,!0);if(t>0){var n=new C(\"utf-8\"),i=new L({type:e.getUint8(6),mode:e.getUint8(7),minValue:e.getFloat64(108,!0),maxValue:e.getFloat64(116,!0),uncertainty:e.getFloat64(100,!0),minPeriod:e.getUint32(124,!0)/1e3,maxPeriod:((e.getUint32(132,!0)<<32)+e.getUint32(128,!0))/1e3,typicalPeriod:e.getUint32(136,!0)/1e3,granularity:e.getUint32(140,!0)/1e3}),a=new P({number:e.getUint8(0),name:n.decode(new Uint8Array(e.buffer,14,60).filter(S)),unit:n.decode(new Uint8Array(e.buffer,74,32).filter(S)),mutalExclusiveMask:e.getUint32(144,!0),measurementInfo:i,sensorId:t}),o=new T(a);U(\"Get Sensor Info Returned\"),M(o),r.sensors.push(o),o.on(\"state-changed\",function(){U(\"Sensor Restart: \".concat(o.number)),o.enabled&&(r.measurementPeriod=o.specs.measurementInfo.typicalPeriod,r.sensors.forEach(function(e){if(o.number!==e.number&&e.enabled){var t=1<r.measurementPeriod&&(r.measurementPeriod=e.specs.measurementInfo.typicalPeriod)}})),r._restartMeasurements()})}}));case 3:case\"end\":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}()},{key:\"_restartMeasurements\",value:function(){var e=t(regeneratorRuntime.mark(function e(){var t;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(t=this.collecting,!this.collecting){e.next=10;break}return e.prev=2,e.next=5,this._stopMeasurements();case 5:e.next=10;break;case 7:e.prev=7,e.t0=e.catch(2),console.error(e.t0);case 10:if(this.collecting||!t){e.next=19;break}return e.prev=11,e.next=14,this._startMeasurements();case 14:e.next=19;break;case 16:e.prev=16,e.t1=e.catch(11),console.error(e.t1);case 19:case\"end\":return e.stop()}},e,this,[[2,7],[11,16]])}));return function(){return e.apply(this,arguments)}}()},{key:\"_setMeasurementPeriod\",value:function(e){var t=new Uint8Array(d.SET_MEASUREMENT_PERIOD),n=1e3*this.minMeasurementPeriod;return e>0&255,t[4]=e>>8&255,t[5]=e>>16&255,t[6]=e>>24&255,this._sendCommand(t)}},{key:\"_getEnabledChannelMask\",value:function(){var e=0;return this.sensors.filter(function(e){return e.enabled}).forEach(function(t){e+=1<>0&255,n[4]=t>>8&255,n[5]=t>>16&255,n[6]=t>>24&255,e._sendCommand(n).then(function(t){0===t.getUint8(0)&&(e.collecting=!0,e.emit(\"measurements-started\"))})})}},{key:\"_stopMeasurements\",value:function(){var e=this;return this._sendCommand(d.STOP_MEASUREMENTS).then(function(t){0===t.getUint8(0)&&(e.collecting=!1,e.emit(\"measurements-stopped\"))})}}]),r}(),O=function(){function e(t){n(this,e),this.webBluetoothNativeDevice=t,this.deviceCommand=null,this.deviceResponse=null}return i(e,[{key:\"writeCommand\",value:function(){var e=t(regeneratorRuntime.mark(function e(t){return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt(\"return\",this.deviceCommand.writeValue(t));case 1:case\"end\":return e.stop()}},e,this)}));return function(t){return e.apply(this,arguments)}}()},{key:\"setup\",value:function(){var e=t(regeneratorRuntime.mark(function e(t){var n,r,i,a,o=this;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return n=t.onClosed,r=t.onResponse,this.webBluetoothNativeDevice.addEventListener(\"gattserverdisconnected\",n),e.prev=2,e.next=5,this.webBluetoothNativeDevice.gatt.connect();case 5:return i=e.sent,e.next=8,i.getPrimaryService(\"d91714ef-28b9-4f91-ba16-f0d9a604f112\");case 8:return a=e.sent,e.next=11,a.getCharacteristics();case 11:e.sent.forEach(function(e){switch(e.uuid){case\"f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb\":o.deviceCommand=e;break;case\"b41e6675-a329-40e0-aa01-44d2f444babe\":o.deviceResponse=e,o.deviceResponse.addEventListener(\"characteristicvaluechanged\",function(e){var t=e.target.value;r(t)}),o.deviceResponse.startNotifications();break;default:U(\"No case found for \".concat(e.uuid))}}),e.next=18;break;case 15:e.prev=15,e.t0=e.catch(2),console.error(e.t0);case 18:if(this.deviceCommand&&this.deviceResponse){e.next=20;break}throw new Error(\"Expected command and response characteristics not found.\");case 20:case\"end\":return e.stop()}},e,this,[[2,15]])}));return function(t){return e.apply(this,arguments)}}()},{key:\"close\",value:function(){var e=t(regeneratorRuntime.mark(function e(){return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return e.abrupt(\"return\",this.webBluetoothNativeDevice.gatt.disconnect());case 1:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()},{key:\"godirectAdapter\",get:function(){return!0}}]),e}(),D={createDevice:function(){var e=t(regeneratorRuntime.mark(function e(t){var n,r,i,a,o,s,u,c=arguments;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(n=c.length>1&&void 0!==c[1]?c[1]:{},r=n.open,i=void 0===r||r,a=n.startMeasurements,o=void 0===a||a,(s=t).godirectAdapter||(s=new O(t)),u=new N(s),!i){e.next=14;break}return e.prev=5,e.next=8,u.open(o);case 8:e.next=14;break;case 10:throw e.prev=10,e.t0=e.catch(5),console.error(e.t0),e.t0;case 14:return e.abrupt(\"return\",u);case 15:case\"end\":return e.stop()}},e,this,[[5,10]])}));return function(t){return e.apply(this,arguments)}}(),selectDevice:function(){var e=t(regeneratorRuntime.mark(function e(){var t;return regeneratorRuntime.wrap(function(e){for(;;)switch(e.prev=e.next){case 0:if(navigator.bluetooth){e.next=2;break}return e.abrupt(\"return\",Promise.reject(new Error(\"No Web Bluetooth support.\")));case 2:return e.next=4,navigator.bluetooth.requestDevice({filters:[{namePrefix:\"GDX\"}],optionalServices:[\"d91714ef-28b9-4f91-ba16-f0d9a604f112\"]});case 4:return t=e.sent,e.abrupt(\"return\",D.createDevice(t));case 6:case\"end\":return e.stop()}},e,this)}));return function(){return e.apply(this,arguments)}}()};return D});\n","// Reserved word lists for various dialects of the language\n\nvar reservedWords = {\n 3: \"abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile\",\n 5: \"class enum extends super const export import\",\n 6: \"enum\",\n strict: \"implements interface let package private protected public static yield\",\n strictBind: \"eval arguments\"\n};\n\n// And the keywords\n\nvar ecma5AndLessKeywords = \"break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this\";\n\nvar keywords = {\n 5: ecma5AndLessKeywords,\n \"5module\": ecma5AndLessKeywords + \" export import\",\n 6: ecma5AndLessKeywords + \" const class extends export import super\"\n};\n\nvar keywordRelationalOperator = /^in(stanceof)?$/;\n\n// ## Character categories\n\n// Big ugly regular expressions that match characters in the\n// whitespace, identifier, and identifier-start categories. These\n// are only applied when a character is found to actually have a\n// code point above 128.\n// Generated by `bin/generate-identifier-regex.js`.\nvar nonASCIIidentifierStartChars = \"\\xaa\\xb5\\xba\\xc0-\\xd6\\xd8-\\xf6\\xf8-\\u02c1\\u02c6-\\u02d1\\u02e0-\\u02e4\\u02ec\\u02ee\\u0370-\\u0374\\u0376\\u0377\\u037a-\\u037d\\u037f\\u0386\\u0388-\\u038a\\u038c\\u038e-\\u03a1\\u03a3-\\u03f5\\u03f7-\\u0481\\u048a-\\u052f\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05d0-\\u05ea\\u05ef-\\u05f2\\u0620-\\u064a\\u066e\\u066f\\u0671-\\u06d3\\u06d5\\u06e5\\u06e6\\u06ee\\u06ef\\u06fa-\\u06fc\\u06ff\\u0710\\u0712-\\u072f\\u074d-\\u07a5\\u07b1\\u07ca-\\u07ea\\u07f4\\u07f5\\u07fa\\u0800-\\u0815\\u081a\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086a\\u08a0-\\u08b4\\u08b6-\\u08bd\\u0904-\\u0939\\u093d\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098c\\u098f\\u0990\\u0993-\\u09a8\\u09aa-\\u09b0\\u09b2\\u09b6-\\u09b9\\u09bd\\u09ce\\u09dc\\u09dd\\u09df-\\u09e1\\u09f0\\u09f1\\u09fc\\u0a05-\\u0a0a\\u0a0f\\u0a10\\u0a13-\\u0a28\\u0a2a-\\u0a30\\u0a32\\u0a33\\u0a35\\u0a36\\u0a38\\u0a39\\u0a59-\\u0a5c\\u0a5e\\u0a72-\\u0a74\\u0a85-\\u0a8d\\u0a8f-\\u0a91\\u0a93-\\u0aa8\\u0aaa-\\u0ab0\\u0ab2\\u0ab3\\u0ab5-\\u0ab9\\u0abd\\u0ad0\\u0ae0\\u0ae1\\u0af9\\u0b05-\\u0b0c\\u0b0f\\u0b10\\u0b13-\\u0b28\\u0b2a-\\u0b30\\u0b32\\u0b33\\u0b35-\\u0b39\\u0b3d\\u0b5c\\u0b5d\\u0b5f-\\u0b61\\u0b71\\u0b83\\u0b85-\\u0b8a\\u0b8e-\\u0b90\\u0b92-\\u0b95\\u0b99\\u0b9a\\u0b9c\\u0b9e\\u0b9f\\u0ba3\\u0ba4\\u0ba8-\\u0baa\\u0bae-\\u0bb9\\u0bd0\\u0c05-\\u0c0c\\u0c0e-\\u0c10\\u0c12-\\u0c28\\u0c2a-\\u0c39\\u0c3d\\u0c58-\\u0c5a\\u0c60\\u0c61\\u0c80\\u0c85-\\u0c8c\\u0c8e-\\u0c90\\u0c92-\\u0ca8\\u0caa-\\u0cb3\\u0cb5-\\u0cb9\\u0cbd\\u0cde\\u0ce0\\u0ce1\\u0cf1\\u0cf2\\u0d05-\\u0d0c\\u0d0e-\\u0d10\\u0d12-\\u0d3a\\u0d3d\\u0d4e\\u0d54-\\u0d56\\u0d5f-\\u0d61\\u0d7a-\\u0d7f\\u0d85-\\u0d96\\u0d9a-\\u0db1\\u0db3-\\u0dbb\\u0dbd\\u0dc0-\\u0dc6\\u0e01-\\u0e30\\u0e32\\u0e33\\u0e40-\\u0e46\\u0e81\\u0e82\\u0e84\\u0e86-\\u0e8a\\u0e8c-\\u0ea3\\u0ea5\\u0ea7-\\u0eb0\\u0eb2\\u0eb3\\u0ebd\\u0ec0-\\u0ec4\\u0ec6\\u0edc-\\u0edf\\u0f00\\u0f40-\\u0f47\\u0f49-\\u0f6c\\u0f88-\\u0f8c\\u1000-\\u102a\\u103f\\u1050-\\u1055\\u105a-\\u105d\\u1061\\u1065\\u1066\\u106e-\\u1070\\u1075-\\u1081\\u108e\\u10a0-\\u10c5\\u10c7\\u10cd\\u10d0-\\u10fa\\u10fc-\\u1248\\u124a-\\u124d\\u1250-\\u1256\\u1258\\u125a-\\u125d\\u1260-\\u1288\\u128a-\\u128d\\u1290-\\u12b0\\u12b2-\\u12b5\\u12b8-\\u12be\\u12c0\\u12c2-\\u12c5\\u12c8-\\u12d6\\u12d8-\\u1310\\u1312-\\u1315\\u1318-\\u135a\\u1380-\\u138f\\u13a0-\\u13f5\\u13f8-\\u13fd\\u1401-\\u166c\\u166f-\\u167f\\u1681-\\u169a\\u16a0-\\u16ea\\u16ee-\\u16f8\\u1700-\\u170c\\u170e-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176c\\u176e-\\u1770\\u1780-\\u17b3\\u17d7\\u17dc\\u1820-\\u1878\\u1880-\\u18a8\\u18aa\\u18b0-\\u18f5\\u1900-\\u191e\\u1950-\\u196d\\u1970-\\u1974\\u1980-\\u19ab\\u19b0-\\u19c9\\u1a00-\\u1a16\\u1a20-\\u1a54\\u1aa7\\u1b05-\\u1b33\\u1b45-\\u1b4b\\u1b83-\\u1ba0\\u1bae\\u1baf\\u1bba-\\u1be5\\u1c00-\\u1c23\\u1c4d-\\u1c4f\\u1c5a-\\u1c7d\\u1c80-\\u1c88\\u1c90-\\u1cba\\u1cbd-\\u1cbf\\u1ce9-\\u1cec\\u1cee-\\u1cf3\\u1cf5\\u1cf6\\u1cfa\\u1d00-\\u1dbf\\u1e00-\\u1f15\\u1f18-\\u1f1d\\u1f20-\\u1f45\\u1f48-\\u1f4d\\u1f50-\\u1f57\\u1f59\\u1f5b\\u1f5d\\u1f5f-\\u1f7d\\u1f80-\\u1fb4\\u1fb6-\\u1fbc\\u1fbe\\u1fc2-\\u1fc4\\u1fc6-\\u1fcc\\u1fd0-\\u1fd3\\u1fd6-\\u1fdb\\u1fe0-\\u1fec\\u1ff2-\\u1ff4\\u1ff6-\\u1ffc\\u2071\\u207f\\u2090-\\u209c\\u2102\\u2107\\u210a-\\u2113\\u2115\\u2118-\\u211d\\u2124\\u2126\\u2128\\u212a-\\u2139\\u213c-\\u213f\\u2145-\\u2149\\u214e\\u2160-\\u2188\\u2c00-\\u2c2e\\u2c30-\\u2c5e\\u2c60-\\u2ce4\\u2ceb-\\u2cee\\u2cf2\\u2cf3\\u2d00-\\u2d25\\u2d27\\u2d2d\\u2d30-\\u2d67\\u2d6f\\u2d80-\\u2d96\\u2da0-\\u2da6\\u2da8-\\u2dae\\u2db0-\\u2db6\\u2db8-\\u2dbe\\u2dc0-\\u2dc6\\u2dc8-\\u2dce\\u2dd0-\\u2dd6\\u2dd8-\\u2dde\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303c\\u3041-\\u3096\\u309b-\\u309f\\u30a1-\\u30fa\\u30fc-\\u30ff\\u3105-\\u312f\\u3131-\\u318e\\u31a0-\\u31ba\\u31f0-\\u31ff\\u3400-\\u4db5\\u4e00-\\u9fef\\ua000-\\ua48c\\ua4d0-\\ua4fd\\ua500-\\ua60c\\ua610-\\ua61f\\ua62a\\ua62b\\ua640-\\ua66e\\ua67f-\\ua69d\\ua6a0-\\ua6ef\\ua717-\\ua71f\\ua722-\\ua788\\ua78b-\\ua7bf\\ua7c2-\\ua7c6\\ua7f7-\\ua801\\ua803-\\ua805\\ua807-\\ua80a\\ua80c-\\ua822\\ua840-\\ua873\\ua882-\\ua8b3\\ua8f2-\\ua8f7\\ua8fb\\ua8fd\\ua8fe\\ua90a-\\ua925\\ua930-\\ua946\\ua960-\\ua97c\\ua984-\\ua9b2\\ua9cf\\ua9e0-\\ua9e4\\ua9e6-\\ua9ef\\ua9fa-\\ua9fe\\uaa00-\\uaa28\\uaa40-\\uaa42\\uaa44-\\uaa4b\\uaa60-\\uaa76\\uaa7a\\uaa7e-\\uaaaf\\uaab1\\uaab5\\uaab6\\uaab9-\\uaabd\\uaac0\\uaac2\\uaadb-\\uaadd\\uaae0-\\uaaea\\uaaf2-\\uaaf4\\uab01-\\uab06\\uab09-\\uab0e\\uab11-\\uab16\\uab20-\\uab26\\uab28-\\uab2e\\uab30-\\uab5a\\uab5c-\\uab67\\uab70-\\uabe2\\uac00-\\ud7a3\\ud7b0-\\ud7c6\\ud7cb-\\ud7fb\\uf900-\\ufa6d\\ufa70-\\ufad9\\ufb00-\\ufb06\\ufb13-\\ufb17\\ufb1d\\ufb1f-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40\\ufb41\\ufb43\\ufb44\\ufb46-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb\\ufe70-\\ufe74\\ufe76-\\ufefc\\uff21-\\uff3a\\uff41-\\uff5a\\uff66-\\uffbe\\uffc2-\\uffc7\\uffca-\\uffcf\\uffd2-\\uffd7\\uffda-\\uffdc\";\nvar nonASCIIidentifierChars = \"\\u200c\\u200d\\xb7\\u0300-\\u036f\\u0387\\u0483-\\u0487\\u0591-\\u05bd\\u05bf\\u05c1\\u05c2\\u05c4\\u05c5\\u05c7\\u0610-\\u061a\\u064b-\\u0669\\u0670\\u06d6-\\u06dc\\u06df-\\u06e4\\u06e7\\u06e8\\u06ea-\\u06ed\\u06f0-\\u06f9\\u0711\\u0730-\\u074a\\u07a6-\\u07b0\\u07c0-\\u07c9\\u07eb-\\u07f3\\u07fd\\u0816-\\u0819\\u081b-\\u0823\\u0825-\\u0827\\u0829-\\u082d\\u0859-\\u085b\\u08d3-\\u08e1\\u08e3-\\u0903\\u093a-\\u093c\\u093e-\\u094f\\u0951-\\u0957\\u0962\\u0963\\u0966-\\u096f\\u0981-\\u0983\\u09bc\\u09be-\\u09c4\\u09c7\\u09c8\\u09cb-\\u09cd\\u09d7\\u09e2\\u09e3\\u09e6-\\u09ef\\u09fe\\u0a01-\\u0a03\\u0a3c\\u0a3e-\\u0a42\\u0a47\\u0a48\\u0a4b-\\u0a4d\\u0a51\\u0a66-\\u0a71\\u0a75\\u0a81-\\u0a83\\u0abc\\u0abe-\\u0ac5\\u0ac7-\\u0ac9\\u0acb-\\u0acd\\u0ae2\\u0ae3\\u0ae6-\\u0aef\\u0afa-\\u0aff\\u0b01-\\u0b03\\u0b3c\\u0b3e-\\u0b44\\u0b47\\u0b48\\u0b4b-\\u0b4d\\u0b56\\u0b57\\u0b62\\u0b63\\u0b66-\\u0b6f\\u0b82\\u0bbe-\\u0bc2\\u0bc6-\\u0bc8\\u0bca-\\u0bcd\\u0bd7\\u0be6-\\u0bef\\u0c00-\\u0c04\\u0c3e-\\u0c44\\u0c46-\\u0c48\\u0c4a-\\u0c4d\\u0c55\\u0c56\\u0c62\\u0c63\\u0c66-\\u0c6f\\u0c81-\\u0c83\\u0cbc\\u0cbe-\\u0cc4\\u0cc6-\\u0cc8\\u0cca-\\u0ccd\\u0cd5\\u0cd6\\u0ce2\\u0ce3\\u0ce6-\\u0cef\\u0d00-\\u0d03\\u0d3b\\u0d3c\\u0d3e-\\u0d44\\u0d46-\\u0d48\\u0d4a-\\u0d4d\\u0d57\\u0d62\\u0d63\\u0d66-\\u0d6f\\u0d82\\u0d83\\u0dca\\u0dcf-\\u0dd4\\u0dd6\\u0dd8-\\u0ddf\\u0de6-\\u0def\\u0df2\\u0df3\\u0e31\\u0e34-\\u0e3a\\u0e47-\\u0e4e\\u0e50-\\u0e59\\u0eb1\\u0eb4-\\u0ebc\\u0ec8-\\u0ecd\\u0ed0-\\u0ed9\\u0f18\\u0f19\\u0f20-\\u0f29\\u0f35\\u0f37\\u0f39\\u0f3e\\u0f3f\\u0f71-\\u0f84\\u0f86\\u0f87\\u0f8d-\\u0f97\\u0f99-\\u0fbc\\u0fc6\\u102b-\\u103e\\u1040-\\u1049\\u1056-\\u1059\\u105e-\\u1060\\u1062-\\u1064\\u1067-\\u106d\\u1071-\\u1074\\u1082-\\u108d\\u108f-\\u109d\\u135d-\\u135f\\u1369-\\u1371\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17b4-\\u17d3\\u17dd\\u17e0-\\u17e9\\u180b-\\u180d\\u1810-\\u1819\\u18a9\\u1920-\\u192b\\u1930-\\u193b\\u1946-\\u194f\\u19d0-\\u19da\\u1a17-\\u1a1b\\u1a55-\\u1a5e\\u1a60-\\u1a7c\\u1a7f-\\u1a89\\u1a90-\\u1a99\\u1ab0-\\u1abd\\u1b00-\\u1b04\\u1b34-\\u1b44\\u1b50-\\u1b59\\u1b6b-\\u1b73\\u1b80-\\u1b82\\u1ba1-\\u1bad\\u1bb0-\\u1bb9\\u1be6-\\u1bf3\\u1c24-\\u1c37\\u1c40-\\u1c49\\u1c50-\\u1c59\\u1cd0-\\u1cd2\\u1cd4-\\u1ce8\\u1ced\\u1cf4\\u1cf7-\\u1cf9\\u1dc0-\\u1df9\\u1dfb-\\u1dff\\u203f\\u2040\\u2054\\u20d0-\\u20dc\\u20e1\\u20e5-\\u20f0\\u2cef-\\u2cf1\\u2d7f\\u2de0-\\u2dff\\u302a-\\u302f\\u3099\\u309a\\ua620-\\ua629\\ua66f\\ua674-\\ua67d\\ua69e\\ua69f\\ua6f0\\ua6f1\\ua802\\ua806\\ua80b\\ua823-\\ua827\\ua880\\ua881\\ua8b4-\\ua8c5\\ua8d0-\\ua8d9\\ua8e0-\\ua8f1\\ua8ff-\\ua909\\ua926-\\ua92d\\ua947-\\ua953\\ua980-\\ua983\\ua9b3-\\ua9c0\\ua9d0-\\ua9d9\\ua9e5\\ua9f0-\\ua9f9\\uaa29-\\uaa36\\uaa43\\uaa4c\\uaa4d\\uaa50-\\uaa59\\uaa7b-\\uaa7d\\uaab0\\uaab2-\\uaab4\\uaab7\\uaab8\\uaabe\\uaabf\\uaac1\\uaaeb-\\uaaef\\uaaf5\\uaaf6\\uabe3-\\uabea\\uabec\\uabed\\uabf0-\\uabf9\\ufb1e\\ufe00-\\ufe0f\\ufe20-\\ufe2f\\ufe33\\ufe34\\ufe4d-\\ufe4f\\uff10-\\uff19\\uff3f\";\n\nvar nonASCIIidentifierStart = new RegExp(\"[\" + nonASCIIidentifierStartChars + \"]\");\nvar nonASCIIidentifier = new RegExp(\"[\" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + \"]\");\n\nnonASCIIidentifierStartChars = nonASCIIidentifierChars = null;\n\n// These are a run-length and offset encoded representation of the\n// >0xffff code points that are a valid part of identifiers. The\n// offset starts at 0x10000, and each pair of numbers represents an\n// offset to the next range, and then a size of the range. They were\n// generated by bin/generate-identifier-regex.js\n\n// eslint-disable-next-line comma-spacing\nvar astralIdentifierStartCodes = [0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,14,29,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,28,43,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,14,35,477,28,11,0,9,21,155,22,13,52,76,44,33,24,27,35,30,0,12,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,21,0,33,47,21,1,2,0,185,46,42,3,37,47,21,0,60,42,14,0,72,26,230,43,117,63,32,0,161,7,3,38,17,0,2,0,29,0,11,39,8,0,22,0,12,45,20,0,35,56,264,8,2,36,18,0,50,29,113,6,2,1,2,37,22,0,26,5,2,1,2,31,15,0,328,18,270,921,103,110,18,195,2749,1070,4050,582,8634,568,8,30,114,29,19,47,17,3,32,20,6,18,689,63,129,74,6,0,67,12,65,1,2,0,29,6135,9,754,9486,286,50,2,18,3,9,395,2309,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,2357,44,11,6,17,0,370,43,1301,196,60,67,8,0,1205,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42710,42,4148,12,221,3,5761,15,7472,3104,541];\n\n// eslint-disable-next-line comma-spacing\nvar astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,574,3,9,9,525,10,176,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,6,1,45,0,13,2,49,13,9,3,4,9,83,11,7,0,161,11,6,9,7,3,56,1,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,5,0,82,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,243,14,166,9,232,6,3,6,4,0,29,9,41,6,2,3,9,0,10,10,47,15,406,7,2,7,17,9,57,21,2,13,123,5,4,0,2,1,2,6,2,0,9,9,49,4,2,1,2,4,9,9,330,3,19306,9,135,4,60,6,26,9,1014,0,2,54,8,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,262,6,10,9,419,13,1495,6,110,6,6,9,792487,239];\n\n// This has a complexity linear to the value of the code. The\n// assumption is that looking up astral identifier characters is\n// rare.\nfunction isInAstralSet(code, set) {\n var pos = 0x10000;\n for (var i = 0; i < set.length; i += 2) {\n pos += set[i];\n if (pos > code) { return false }\n pos += set[i + 1];\n if (pos >= code) { return true }\n }\n}\n\n// Test whether a given character code starts an identifier.\n\nfunction isIdentifierStart(code, astral) {\n if (code < 65) { return code === 36 }\n if (code < 91) { return true }\n if (code < 97) { return code === 95 }\n if (code < 123) { return true }\n if (code <= 0xffff) { return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)) }\n if (astral === false) { return false }\n return isInAstralSet(code, astralIdentifierStartCodes)\n}\n\n// Test whether a given character is part of an identifier.\n\nfunction isIdentifierChar(code, astral) {\n if (code < 48) { return code === 36 }\n if (code < 58) { return true }\n if (code < 65) { return false }\n if (code < 91) { return true }\n if (code < 97) { return code === 95 }\n if (code < 123) { return true }\n if (code <= 0xffff) { return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)) }\n if (astral === false) { return false }\n return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes)\n}\n\n// ## Token types\n\n// The assignment of fine-grained, information-carrying type objects\n// allows the tokenizer to store the information it has about a\n// token in a way that is very cheap for the parser to look up.\n\n// All token type variables start with an underscore, to make them\n// easy to recognize.\n\n// The `beforeExpr` property is used to disambiguate between regular\n// expressions and divisions. It is set on all token types that can\n// be followed by an expression (thus, a slash after them would be a\n// regular expression).\n//\n// The `startsExpr` property is used to check if the token ends a\n// `yield` expression. It is set on all token types that either can\n// directly start an expression (like a quotation mark) or can\n// continue an expression (like the body of a string).\n//\n// `isLoop` marks a keyword as starting a loop, which is important\n// to know when parsing a label, in order to allow or disallow\n// continue jumps to that label.\n\nvar TokenType = function TokenType(label, conf) {\n if ( conf === void 0 ) conf = {};\n\n this.label = label;\n this.keyword = conf.keyword;\n this.beforeExpr = !!conf.beforeExpr;\n this.startsExpr = !!conf.startsExpr;\n this.isLoop = !!conf.isLoop;\n this.isAssign = !!conf.isAssign;\n this.prefix = !!conf.prefix;\n this.postfix = !!conf.postfix;\n this.binop = conf.binop || null;\n this.updateContext = null;\n};\n\nfunction binop(name, prec) {\n return new TokenType(name, {beforeExpr: true, binop: prec})\n}\nvar beforeExpr = {beforeExpr: true}, startsExpr = {startsExpr: true};\n\n// Map keyword names to token types.\n\nvar keywords$1 = {};\n\n// Succinct definitions of keyword token types\nfunction kw(name, options) {\n if ( options === void 0 ) options = {};\n\n options.keyword = name;\n return keywords$1[name] = new TokenType(name, options)\n}\n\nvar types = {\n num: new TokenType(\"num\", startsExpr),\n regexp: new TokenType(\"regexp\", startsExpr),\n string: new TokenType(\"string\", startsExpr),\n name: new TokenType(\"name\", startsExpr),\n eof: new TokenType(\"eof\"),\n\n // Punctuation token types.\n bracketL: new TokenType(\"[\", {beforeExpr: true, startsExpr: true}),\n bracketR: new TokenType(\"]\"),\n braceL: new TokenType(\"{\", {beforeExpr: true, startsExpr: true}),\n braceR: new TokenType(\"}\"),\n parenL: new TokenType(\"(\", {beforeExpr: true, startsExpr: true}),\n parenR: new TokenType(\")\"),\n comma: new TokenType(\",\", beforeExpr),\n semi: new TokenType(\";\", beforeExpr),\n colon: new TokenType(\":\", beforeExpr),\n dot: new TokenType(\".\"),\n question: new TokenType(\"?\", beforeExpr),\n arrow: new TokenType(\"=>\", beforeExpr),\n template: new TokenType(\"template\"),\n invalidTemplate: new TokenType(\"invalidTemplate\"),\n ellipsis: new TokenType(\"...\", beforeExpr),\n backQuote: new TokenType(\"`\", startsExpr),\n dollarBraceL: new TokenType(\"${\", {beforeExpr: true, startsExpr: true}),\n\n // Operators. These carry several kinds of properties to help the\n // parser use them properly (the presence of these properties is\n // what categorizes them as operators).\n //\n // `binop`, when present, specifies that this operator is a binary\n // operator, and will refer to its precedence.\n //\n // `prefix` and `postfix` mark the operator as a prefix or postfix\n // unary operator.\n //\n // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as\n // binary operators with a very low precedence, that should result\n // in AssignmentExpression nodes.\n\n eq: new TokenType(\"=\", {beforeExpr: true, isAssign: true}),\n assign: new TokenType(\"_=\", {beforeExpr: true, isAssign: true}),\n incDec: new TokenType(\"++/--\", {prefix: true, postfix: true, startsExpr: true}),\n prefix: new TokenType(\"!/~\", {beforeExpr: true, prefix: true, startsExpr: true}),\n logicalOR: binop(\"||\", 1),\n logicalAND: binop(\"&&\", 2),\n bitwiseOR: binop(\"|\", 3),\n bitwiseXOR: binop(\"^\", 4),\n bitwiseAND: binop(\"&\", 5),\n equality: binop(\"==/!=/===/!==\", 6),\n relational: binop(\"/<=/>=\", 7),\n bitShift: binop(\"<>/>>>\", 8),\n plusMin: new TokenType(\"+/-\", {beforeExpr: true, binop: 9, prefix: true, startsExpr: true}),\n modulo: binop(\"%\", 10),\n star: binop(\"*\", 10),\n slash: binop(\"/\", 10),\n starstar: new TokenType(\"**\", {beforeExpr: true}),\n\n // Keyword token types.\n _break: kw(\"break\"),\n _case: kw(\"case\", beforeExpr),\n _catch: kw(\"catch\"),\n _continue: kw(\"continue\"),\n _debugger: kw(\"debugger\"),\n _default: kw(\"default\", beforeExpr),\n _do: kw(\"do\", {isLoop: true, beforeExpr: true}),\n _else: kw(\"else\", beforeExpr),\n _finally: kw(\"finally\"),\n _for: kw(\"for\", {isLoop: true}),\n _function: kw(\"function\", startsExpr),\n _if: kw(\"if\"),\n _return: kw(\"return\", beforeExpr),\n _switch: kw(\"switch\"),\n _throw: kw(\"throw\", beforeExpr),\n _try: kw(\"try\"),\n _var: kw(\"var\"),\n _const: kw(\"const\"),\n _while: kw(\"while\", {isLoop: true}),\n _with: kw(\"with\"),\n _new: kw(\"new\", {beforeExpr: true, startsExpr: true}),\n _this: kw(\"this\", startsExpr),\n _super: kw(\"super\", startsExpr),\n _class: kw(\"class\", startsExpr),\n _extends: kw(\"extends\", beforeExpr),\n _export: kw(\"export\"),\n _import: kw(\"import\", startsExpr),\n _null: kw(\"null\", startsExpr),\n _true: kw(\"true\", startsExpr),\n _false: kw(\"false\", startsExpr),\n _in: kw(\"in\", {beforeExpr: true, binop: 7}),\n _instanceof: kw(\"instanceof\", {beforeExpr: true, binop: 7}),\n _typeof: kw(\"typeof\", {beforeExpr: true, prefix: true, startsExpr: true}),\n _void: kw(\"void\", {beforeExpr: true, prefix: true, startsExpr: true}),\n _delete: kw(\"delete\", {beforeExpr: true, prefix: true, startsExpr: true})\n};\n\n// Matches a whole line break (where CRLF is considered a single\n// line break). Used to count lines.\n\nvar lineBreak = /\\r\\n?|\\n|\\u2028|\\u2029/;\nvar lineBreakG = new RegExp(lineBreak.source, \"g\");\n\nfunction isNewLine(code, ecma2019String) {\n return code === 10 || code === 13 || (!ecma2019String && (code === 0x2028 || code === 0x2029))\n}\n\nvar nonASCIIwhitespace = /[\\u1680\\u2000-\\u200a\\u202f\\u205f\\u3000\\ufeff]/;\n\nvar skipWhiteSpace = /(?:\\s|\\/\\/.*|\\/\\*[^]*?\\*\\/)*/g;\n\nvar ref = Object.prototype;\nvar hasOwnProperty = ref.hasOwnProperty;\nvar toString = ref.toString;\n\n// Checks if an object has a property.\n\nfunction has(obj, propName) {\n return hasOwnProperty.call(obj, propName)\n}\n\nvar isArray = Array.isArray || (function (obj) { return (\n toString.call(obj) === \"[object Array]\"\n); });\n\nfunction wordsRegexp(words) {\n return new RegExp(\"^(?:\" + words.replace(/ /g, \"|\") + \")$\")\n}\n\n// These are used when `options.locations` is on, for the\n// `startLoc` and `endLoc` properties.\n\nvar Position = function Position(line, col) {\n this.line = line;\n this.column = col;\n};\n\nPosition.prototype.offset = function offset (n) {\n return new Position(this.line, this.column + n)\n};\n\nvar SourceLocation = function SourceLocation(p, start, end) {\n this.start = start;\n this.end = end;\n if (p.sourceFile !== null) { this.source = p.sourceFile; }\n};\n\n// The `getLineInfo` function is mostly useful when the\n// `locations` option is off (for performance reasons) and you\n// want to find the line/column position for a given character\n// offset. `input` should be the code string that the offset refers\n// into.\n\nfunction getLineInfo(input, offset) {\n for (var line = 1, cur = 0;;) {\n lineBreakG.lastIndex = cur;\n var match = lineBreakG.exec(input);\n if (match && match.index < offset) {\n ++line;\n cur = match.index + match[0].length;\n } else {\n return new Position(line, offset - cur)\n }\n }\n}\n\n// A second optional argument can be given to further configure\n// the parser process. These options are recognized:\n\nvar defaultOptions = {\n // `ecmaVersion` indicates the ECMAScript version to parse. Must be\n // either 3, 5, 6 (2015), 7 (2016), 8 (2017), 9 (2018), or 10\n // (2019). This influences support for strict mode, the set of\n // reserved words, and support for new syntax features. The default\n // is 9.\n ecmaVersion: 9,\n // `sourceType` indicates the mode the code should be parsed in.\n // Can be either `\"script\"` or `\"module\"`. This influences global\n // strict mode and parsing of `import` and `export` declarations.\n sourceType: \"script\",\n // `onInsertedSemicolon` can be a callback that will be called\n // when a semicolon is automatically inserted. It will be passed\n // the position of the comma as an offset, and if `locations` is\n // enabled, it is given the location as a `{line, column}` object\n // as second argument.\n onInsertedSemicolon: null,\n // `onTrailingComma` is similar to `onInsertedSemicolon`, but for\n // trailing commas.\n onTrailingComma: null,\n // By default, reserved words are only enforced if ecmaVersion >= 5.\n // Set `allowReserved` to a boolean value to explicitly turn this on\n // an off. When this option has the value \"never\", reserved words\n // and keywords can also not be used as property names.\n allowReserved: null,\n // When enabled, a return at the top level is not considered an\n // error.\n allowReturnOutsideFunction: false,\n // When enabled, import/export statements are not constrained to\n // appearing at the top of the program.\n allowImportExportEverywhere: false,\n // When enabled, await identifiers are allowed to appear at the top-level scope,\n // but they are still not allowed in non-async functions.\n allowAwaitOutsideFunction: false,\n // When enabled, hashbang directive in the beginning of file\n // is allowed and treated as a line comment.\n allowHashBang: false,\n // When `locations` is on, `loc` properties holding objects with\n // `start` and `end` properties in `{line, column}` form (with\n // line being 1-based and column 0-based) will be attached to the\n // nodes.\n locations: false,\n // A function can be passed as `onToken` option, which will\n // cause Acorn to call that function with object in the same\n // format as tokens returned from `tokenizer().getToken()`. Note\n // that you are not allowed to call the parser from the\n // callback—that will corrupt its internal state.\n onToken: null,\n // A function can be passed as `onComment` option, which will\n // cause Acorn to call that function with `(block, text, start,\n // end)` parameters whenever a comment is skipped. `block` is a\n // boolean indicating whether this is a block (`/* */`) comment,\n // `text` is the content of the comment, and `start` and `end` are\n // character offsets that denote the start and end of the comment.\n // When the `locations` option is on, two more parameters are\n // passed, the full `{line, column}` locations of the start and\n // end of the comments. Note that you are not allowed to call the\n // parser from the callback—that will corrupt its internal state.\n onComment: null,\n // Nodes have their start and end characters offsets recorded in\n // `start` and `end` properties (directly on the node, rather than\n // the `loc` object, which holds line/column data. To also add a\n // [semi-standardized][range] `range` property holding a `[start,\n // end]` array with the same numbers, set the `ranges` option to\n // `true`.\n //\n // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678\n ranges: false,\n // It is possible to parse multiple files into a single AST by\n // passing the tree produced by parsing the first file as\n // `program` option in subsequent parses. This will add the\n // toplevel forms of the parsed file to the `Program` (top) node\n // of an existing parse tree.\n program: null,\n // When `locations` is on, you can pass this to record the source\n // file in every node's `loc` object.\n sourceFile: null,\n // This value, if given, is stored in every node, whether\n // `locations` is on or off.\n directSourceFile: null,\n // When enabled, parenthesized expressions are represented by\n // (non-standard) ParenthesizedExpression nodes\n preserveParens: false\n};\n\n// Interpret and default an options object\n\nfunction getOptions(opts) {\n var options = {};\n\n for (var opt in defaultOptions)\n { options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt]; }\n\n if (options.ecmaVersion >= 2015)\n { options.ecmaVersion -= 2009; }\n\n if (options.allowReserved == null)\n { options.allowReserved = options.ecmaVersion < 5; }\n\n if (isArray(options.onToken)) {\n var tokens = options.onToken;\n options.onToken = function (token) { return tokens.push(token); };\n }\n if (isArray(options.onComment))\n { options.onComment = pushComment(options, options.onComment); }\n\n return options\n}\n\nfunction pushComment(options, array) {\n return function(block, text, start, end, startLoc, endLoc) {\n var comment = {\n type: block ? \"Block\" : \"Line\",\n value: text,\n start: start,\n end: end\n };\n if (options.locations)\n { comment.loc = new SourceLocation(this, startLoc, endLoc); }\n if (options.ranges)\n { comment.range = [start, end]; }\n array.push(comment);\n }\n}\n\n// Each scope gets a bitset that may contain these flags\nvar\n SCOPE_TOP = 1,\n SCOPE_FUNCTION = 2,\n SCOPE_VAR = SCOPE_TOP | SCOPE_FUNCTION,\n SCOPE_ASYNC = 4,\n SCOPE_GENERATOR = 8,\n SCOPE_ARROW = 16,\n SCOPE_SIMPLE_CATCH = 32,\n SCOPE_SUPER = 64,\n SCOPE_DIRECT_SUPER = 128;\n\nfunction functionFlags(async, generator) {\n return SCOPE_FUNCTION | (async ? SCOPE_ASYNC : 0) | (generator ? SCOPE_GENERATOR : 0)\n}\n\n// Used in checkLVal and declareName to determine the type of a binding\nvar\n BIND_NONE = 0, // Not a binding\n BIND_VAR = 1, // Var-style binding\n BIND_LEXICAL = 2, // Let- or const-style binding\n BIND_FUNCTION = 3, // Function declaration\n BIND_SIMPLE_CATCH = 4, // Simple (identifier pattern) catch binding\n BIND_OUTSIDE = 5; // Special case for function names as bound inside the function\n\nvar Parser = function Parser(options, input, startPos) {\n this.options = options = getOptions(options);\n this.sourceFile = options.sourceFile;\n this.keywords = wordsRegexp(keywords[options.ecmaVersion >= 6 ? 6 : options.sourceType === \"module\" ? \"5module\" : 5]);\n var reserved = \"\";\n if (options.allowReserved !== true) {\n for (var v = options.ecmaVersion;; v--)\n { if (reserved = reservedWords[v]) { break } }\n if (options.sourceType === \"module\") { reserved += \" await\"; }\n }\n this.reservedWords = wordsRegexp(reserved);\n var reservedStrict = (reserved ? reserved + \" \" : \"\") + reservedWords.strict;\n this.reservedWordsStrict = wordsRegexp(reservedStrict);\n this.reservedWordsStrictBind = wordsRegexp(reservedStrict + \" \" + reservedWords.strictBind);\n this.input = String(input);\n\n // Used to signal to callers of `readWord1` whether the word\n // contained any escape sequences. This is needed because words with\n // escape sequences must not be interpreted as keywords.\n this.containsEsc = false;\n\n // Set up token state\n\n // The current position of the tokenizer in the input.\n if (startPos) {\n this.pos = startPos;\n this.lineStart = this.input.lastIndexOf(\"\\n\", startPos - 1) + 1;\n this.curLine = this.input.slice(0, this.lineStart).split(lineBreak).length;\n } else {\n this.pos = this.lineStart = 0;\n this.curLine = 1;\n }\n\n // Properties of the current token:\n // Its type\n this.type = types.eof;\n // For tokens that include more information than their type, the value\n this.value = null;\n // Its start and end offset\n this.start = this.end = this.pos;\n // And, if locations are used, the {line, column} object\n // corresponding to those offsets\n this.startLoc = this.endLoc = this.curPosition();\n\n // Position information for the previous token\n this.lastTokEndLoc = this.lastTokStartLoc = null;\n this.lastTokStart = this.lastTokEnd = this.pos;\n\n // The context stack is used to superficially track syntactic\n // context to predict whether a regular expression is allowed in a\n // given position.\n this.context = this.initialContext();\n this.exprAllowed = true;\n\n // Figure out if it's a module code.\n this.inModule = options.sourceType === \"module\";\n this.strict = this.inModule || this.strictDirective(this.pos);\n\n // Used to signify the start of a potential arrow function\n this.potentialArrowAt = -1;\n\n // Positions to delayed-check that yield/await does not exist in default parameters.\n this.yieldPos = this.awaitPos = this.awaitIdentPos = 0;\n // Labels in scope.\n this.labels = [];\n // Thus-far undefined exports.\n this.undefinedExports = {};\n\n // If enabled, skip leading hashbang line.\n if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === \"#!\")\n { this.skipLineComment(2); }\n\n // Scope tracking for duplicate variable names (see scope.js)\n this.scopeStack = [];\n this.enterScope(SCOPE_TOP);\n\n // For RegExp validation\n this.regexpState = null;\n};\n\nvar prototypeAccessors = { inFunction: { configurable: true },inGenerator: { configurable: true },inAsync: { configurable: true },allowSuper: { configurable: true },allowDirectSuper: { configurable: true },treatFunctionsAsVar: { configurable: true } };\n\nParser.prototype.parse = function parse () {\n var node = this.options.program || this.startNode();\n this.nextToken();\n return this.parseTopLevel(node)\n};\n\nprototypeAccessors.inFunction.get = function () { return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0 };\nprototypeAccessors.inGenerator.get = function () { return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0 };\nprototypeAccessors.inAsync.get = function () { return (this.currentVarScope().flags & SCOPE_ASYNC) > 0 };\nprototypeAccessors.allowSuper.get = function () { return (this.currentThisScope().flags & SCOPE_SUPER) > 0 };\nprototypeAccessors.allowDirectSuper.get = function () { return (this.currentThisScope().flags & SCOPE_DIRECT_SUPER) > 0 };\nprototypeAccessors.treatFunctionsAsVar.get = function () { return this.treatFunctionsAsVarInScope(this.currentScope()) };\n\n// Switch to a getter for 7.0.0.\nParser.prototype.inNonArrowFunction = function inNonArrowFunction () { return (this.currentThisScope().flags & SCOPE_FUNCTION) > 0 };\n\nParser.extend = function extend () {\n var plugins = [], len = arguments.length;\n while ( len-- ) plugins[ len ] = arguments[ len ];\n\n var cls = this;\n for (var i = 0; i < plugins.length; i++) { cls = plugins[i](cls); }\n return cls\n};\n\nParser.parse = function parse (input, options) {\n return new this(options, input).parse()\n};\n\nParser.parseExpressionAt = function parseExpressionAt (input, pos, options) {\n var parser = new this(options, input, pos);\n parser.nextToken();\n return parser.parseExpression()\n};\n\nParser.tokenizer = function tokenizer (input, options) {\n return new this(options, input)\n};\n\nObject.defineProperties( Parser.prototype, prototypeAccessors );\n\nvar pp = Parser.prototype;\n\n// ## Parser utilities\n\nvar literal = /^(?:'((?:\\\\.|[^'])*?)'|\"((?:\\\\.|[^\"])*?)\")/;\npp.strictDirective = function(start) {\n for (;;) {\n // Try to find string literal.\n skipWhiteSpace.lastIndex = start;\n start += skipWhiteSpace.exec(this.input)[0].length;\n var match = literal.exec(this.input.slice(start));\n if (!match) { return false }\n if ((match[1] || match[2]) === \"use strict\") { return true }\n start += match[0].length;\n\n // Skip semicolon, if any.\n skipWhiteSpace.lastIndex = start;\n start += skipWhiteSpace.exec(this.input)[0].length;\n if (this.input[start] === \";\")\n { start++; }\n }\n};\n\n// Predicate that tests whether the next token is of the given\n// type, and if yes, consumes it as a side effect.\n\npp.eat = function(type) {\n if (this.type === type) {\n this.next();\n return true\n } else {\n return false\n }\n};\n\n// Tests whether parsed token is a contextual keyword.\n\npp.isContextual = function(name) {\n return this.type === types.name && this.value === name && !this.containsEsc\n};\n\n// Consumes contextual keyword if possible.\n\npp.eatContextual = function(name) {\n if (!this.isContextual(name)) { return false }\n this.next();\n return true\n};\n\n// Asserts that following token is given contextual keyword.\n\npp.expectContextual = function(name) {\n if (!this.eatContextual(name)) { this.unexpected(); }\n};\n\n// Test whether a semicolon can be inserted at the current position.\n\npp.canInsertSemicolon = function() {\n return this.type === types.eof ||\n this.type === types.braceR ||\n lineBreak.test(this.input.slice(this.lastTokEnd, this.start))\n};\n\npp.insertSemicolon = function() {\n if (this.canInsertSemicolon()) {\n if (this.options.onInsertedSemicolon)\n { this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc); }\n return true\n }\n};\n\n// Consume a semicolon, or, failing that, see if we are allowed to\n// pretend that there is a semicolon at this position.\n\npp.semicolon = function() {\n if (!this.eat(types.semi) && !this.insertSemicolon()) { this.unexpected(); }\n};\n\npp.afterTrailingComma = function(tokType, notNext) {\n if (this.type === tokType) {\n if (this.options.onTrailingComma)\n { this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc); }\n if (!notNext)\n { this.next(); }\n return true\n }\n};\n\n// Expect a token of a given type. If found, consume it, otherwise,\n// raise an unexpected token error.\n\npp.expect = function(type) {\n this.eat(type) || this.unexpected();\n};\n\n// Raise an unexpected token error.\n\npp.unexpected = function(pos) {\n this.raise(pos != null ? pos : this.start, \"Unexpected token\");\n};\n\nfunction DestructuringErrors() {\n this.shorthandAssign =\n this.trailingComma =\n this.parenthesizedAssign =\n this.parenthesizedBind =\n this.doubleProto =\n -1;\n}\n\npp.checkPatternErrors = function(refDestructuringErrors, isAssign) {\n if (!refDestructuringErrors) { return }\n if (refDestructuringErrors.trailingComma > -1)\n { this.raiseRecoverable(refDestructuringErrors.trailingComma, \"Comma is not permitted after the rest element\"); }\n var parens = isAssign ? refDestructuringErrors.parenthesizedAssign : refDestructuringErrors.parenthesizedBind;\n if (parens > -1) { this.raiseRecoverable(parens, \"Parenthesized pattern\"); }\n};\n\npp.checkExpressionErrors = function(refDestructuringErrors, andThrow) {\n if (!refDestructuringErrors) { return false }\n var shorthandAssign = refDestructuringErrors.shorthandAssign;\n var doubleProto = refDestructuringErrors.doubleProto;\n if (!andThrow) { return shorthandAssign >= 0 || doubleProto >= 0 }\n if (shorthandAssign >= 0)\n { this.raise(shorthandAssign, \"Shorthand property assignments are valid only in destructuring patterns\"); }\n if (doubleProto >= 0)\n { this.raiseRecoverable(doubleProto, \"Redefinition of __proto__ property\"); }\n};\n\npp.checkYieldAwaitInDefaultParams = function() {\n if (this.yieldPos && (!this.awaitPos || this.yieldPos < this.awaitPos))\n { this.raise(this.yieldPos, \"Yield expression cannot be a default value\"); }\n if (this.awaitPos)\n { this.raise(this.awaitPos, \"Await expression cannot be a default value\"); }\n};\n\npp.isSimpleAssignTarget = function(expr) {\n if (expr.type === \"ParenthesizedExpression\")\n { return this.isSimpleAssignTarget(expr.expression) }\n return expr.type === \"Identifier\" || expr.type === \"MemberExpression\"\n};\n\nvar pp$1 = Parser.prototype;\n\n// ### Statement parsing\n\n// Parse a program. Initializes the parser, reads any number of\n// statements, and wraps them in a Program node. Optionally takes a\n// `program` argument. If present, the statements will be appended\n// to its body instead of creating a new node.\n\npp$1.parseTopLevel = function(node) {\n var exports = {};\n if (!node.body) { node.body = []; }\n while (this.type !== types.eof) {\n var stmt = this.parseStatement(null, true, exports);\n node.body.push(stmt);\n }\n if (this.inModule)\n { for (var i = 0, list = Object.keys(this.undefinedExports); i < list.length; i += 1)\n {\n var name = list[i];\n\n this.raiseRecoverable(this.undefinedExports[name].start, (\"Export '\" + name + \"' is not defined\"));\n } }\n this.adaptDirectivePrologue(node.body);\n this.next();\n node.sourceType = this.options.sourceType;\n return this.finishNode(node, \"Program\")\n};\n\nvar loopLabel = {kind: \"loop\"}, switchLabel = {kind: \"switch\"};\n\npp$1.isLet = function(context) {\n if (this.options.ecmaVersion < 6 || !this.isContextual(\"let\")) { return false }\n skipWhiteSpace.lastIndex = this.pos;\n var skip = skipWhiteSpace.exec(this.input);\n var next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next);\n // For ambiguous cases, determine if a LexicalDeclaration (or only a\n // Statement) is allowed here. If context is not empty then only a Statement\n // is allowed. However, `let [` is an explicit negative lookahead for\n // ExpressionStatement, so special-case it first.\n if (nextCh === 91) { return true } // '['\n if (context) { return false }\n\n if (nextCh === 123) { return true } // '{'\n if (isIdentifierStart(nextCh, true)) {\n var pos = next + 1;\n while (isIdentifierChar(this.input.charCodeAt(pos), true)) { ++pos; }\n var ident = this.input.slice(next, pos);\n if (!keywordRelationalOperator.test(ident)) { return true }\n }\n return false\n};\n\n// check 'async [no LineTerminator here] function'\n// - 'async /*foo*/ function' is OK.\n// - 'async /*\\n*/ function' is invalid.\npp$1.isAsyncFunction = function() {\n if (this.options.ecmaVersion < 8 || !this.isContextual(\"async\"))\n { return false }\n\n skipWhiteSpace.lastIndex = this.pos;\n var skip = skipWhiteSpace.exec(this.input);\n var next = this.pos + skip[0].length;\n return !lineBreak.test(this.input.slice(this.pos, next)) &&\n this.input.slice(next, next + 8) === \"function\" &&\n (next + 8 === this.input.length || !isIdentifierChar(this.input.charAt(next + 8)))\n};\n\n// Parse a single statement.\n//\n// If expecting a statement and finding a slash operator, parse a\n// regular expression literal. This is to handle cases like\n// `if (foo) /blah/.exec(foo)`, where looking at the previous token\n// does not help.\n\npp$1.parseStatement = function(context, topLevel, exports) {\n var starttype = this.type, node = this.startNode(), kind;\n\n if (this.isLet(context)) {\n starttype = types._var;\n kind = \"let\";\n }\n\n // Most types of statements are recognized by the keyword they\n // start with. Many are trivial to parse, some require a bit of\n // complexity.\n\n switch (starttype) {\n case types._break: case types._continue: return this.parseBreakContinueStatement(node, starttype.keyword)\n case types._debugger: return this.parseDebuggerStatement(node)\n case types._do: return this.parseDoStatement(node)\n case types._for: return this.parseForStatement(node)\n case types._function:\n // Function as sole body of either an if statement or a labeled statement\n // works, but not when it is part of a labeled statement that is the sole\n // body of an if statement.\n if ((context && (this.strict || context !== \"if\" && context !== \"label\")) && this.options.ecmaVersion >= 6) { this.unexpected(); }\n return this.parseFunctionStatement(node, false, !context)\n case types._class:\n if (context) { this.unexpected(); }\n return this.parseClass(node, true)\n case types._if: return this.parseIfStatement(node)\n case types._return: return this.parseReturnStatement(node)\n case types._switch: return this.parseSwitchStatement(node)\n case types._throw: return this.parseThrowStatement(node)\n case types._try: return this.parseTryStatement(node)\n case types._const: case types._var:\n kind = kind || this.value;\n if (context && kind !== \"var\") { this.unexpected(); }\n return this.parseVarStatement(node, kind)\n case types._while: return this.parseWhileStatement(node)\n case types._with: return this.parseWithStatement(node)\n case types.braceL: return this.parseBlock(true, node)\n case types.semi: return this.parseEmptyStatement(node)\n case types._export:\n case types._import:\n if (this.options.ecmaVersion > 10 && starttype === types._import) {\n skipWhiteSpace.lastIndex = this.pos;\n var skip = skipWhiteSpace.exec(this.input);\n var next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next);\n if (nextCh === 40) // '('\n { return this.parseExpressionStatement(node, this.parseExpression()) }\n }\n\n if (!this.options.allowImportExportEverywhere) {\n if (!topLevel)\n { this.raise(this.start, \"'import' and 'export' may only appear at the top level\"); }\n if (!this.inModule)\n { this.raise(this.start, \"'import' and 'export' may appear only with 'sourceType: module'\"); }\n }\n return starttype === types._import ? this.parseImport(node) : this.parseExport(node, exports)\n\n // If the statement does not start with a statement keyword or a\n // brace, it's an ExpressionStatement or LabeledStatement. We\n // simply start parsing an expression, and afterwards, if the\n // next token is a colon and the expression was a simple\n // Identifier node, we switch to interpreting it as a label.\n default:\n if (this.isAsyncFunction()) {\n if (context) { this.unexpected(); }\n this.next();\n return this.parseFunctionStatement(node, true, !context)\n }\n\n var maybeName = this.value, expr = this.parseExpression();\n if (starttype === types.name && expr.type === \"Identifier\" && this.eat(types.colon))\n { return this.parseLabeledStatement(node, maybeName, expr, context) }\n else { return this.parseExpressionStatement(node, expr) }\n }\n};\n\npp$1.parseBreakContinueStatement = function(node, keyword) {\n var isBreak = keyword === \"break\";\n this.next();\n if (this.eat(types.semi) || this.insertSemicolon()) { node.label = null; }\n else if (this.type !== types.name) { this.unexpected(); }\n else {\n node.label = this.parseIdent();\n this.semicolon();\n }\n\n // Verify that there is an actual destination to break or\n // continue to.\n var i = 0;\n for (; i < this.labels.length; ++i) {\n var lab = this.labels[i];\n if (node.label == null || lab.name === node.label.name) {\n if (lab.kind != null && (isBreak || lab.kind === \"loop\")) { break }\n if (node.label && isBreak) { break }\n }\n }\n if (i === this.labels.length) { this.raise(node.start, \"Unsyntactic \" + keyword); }\n return this.finishNode(node, isBreak ? \"BreakStatement\" : \"ContinueStatement\")\n};\n\npp$1.parseDebuggerStatement = function(node) {\n this.next();\n this.semicolon();\n return this.finishNode(node, \"DebuggerStatement\")\n};\n\npp$1.parseDoStatement = function(node) {\n this.next();\n this.labels.push(loopLabel);\n node.body = this.parseStatement(\"do\");\n this.labels.pop();\n this.expect(types._while);\n node.test = this.parseParenExpression();\n if (this.options.ecmaVersion >= 6)\n { this.eat(types.semi); }\n else\n { this.semicolon(); }\n return this.finishNode(node, \"DoWhileStatement\")\n};\n\n// Disambiguating between a `for` and a `for`/`in` or `for`/`of`\n// loop is non-trivial. Basically, we have to parse the init `var`\n// statement or expression, disallowing the `in` operator (see\n// the second parameter to `parseExpression`), and then check\n// whether the next token is `in` or `of`. When there is no init\n// part (semicolon immediately after the opening parenthesis), it\n// is a regular `for` loop.\n\npp$1.parseForStatement = function(node) {\n this.next();\n var awaitAt = (this.options.ecmaVersion >= 9 && (this.inAsync || (!this.inFunction && this.options.allowAwaitOutsideFunction)) && this.eatContextual(\"await\")) ? this.lastTokStart : -1;\n this.labels.push(loopLabel);\n this.enterScope(0);\n this.expect(types.parenL);\n if (this.type === types.semi) {\n if (awaitAt > -1) { this.unexpected(awaitAt); }\n return this.parseFor(node, null)\n }\n var isLet = this.isLet();\n if (this.type === types._var || this.type === types._const || isLet) {\n var init$1 = this.startNode(), kind = isLet ? \"let\" : this.value;\n this.next();\n this.parseVar(init$1, true, kind);\n this.finishNode(init$1, \"VariableDeclaration\");\n if ((this.type === types._in || (this.options.ecmaVersion >= 6 && this.isContextual(\"of\"))) && init$1.declarations.length === 1) {\n if (this.options.ecmaVersion >= 9) {\n if (this.type === types._in) {\n if (awaitAt > -1) { this.unexpected(awaitAt); }\n } else { node.await = awaitAt > -1; }\n }\n return this.parseForIn(node, init$1)\n }\n if (awaitAt > -1) { this.unexpected(awaitAt); }\n return this.parseFor(node, init$1)\n }\n var refDestructuringErrors = new DestructuringErrors;\n var init = this.parseExpression(true, refDestructuringErrors);\n if (this.type === types._in || (this.options.ecmaVersion >= 6 && this.isContextual(\"of\"))) {\n if (this.options.ecmaVersion >= 9) {\n if (this.type === types._in) {\n if (awaitAt > -1) { this.unexpected(awaitAt); }\n } else { node.await = awaitAt > -1; }\n }\n this.toAssignable(init, false, refDestructuringErrors);\n this.checkLVal(init);\n return this.parseForIn(node, init)\n } else {\n this.checkExpressionErrors(refDestructuringErrors, true);\n }\n if (awaitAt > -1) { this.unexpected(awaitAt); }\n return this.parseFor(node, init)\n};\n\npp$1.parseFunctionStatement = function(node, isAsync, declarationPosition) {\n this.next();\n return this.parseFunction(node, FUNC_STATEMENT | (declarationPosition ? 0 : FUNC_HANGING_STATEMENT), false, isAsync)\n};\n\npp$1.parseIfStatement = function(node) {\n this.next();\n node.test = this.parseParenExpression();\n // allow function declarations in branches, but only in non-strict mode\n node.consequent = this.parseStatement(\"if\");\n node.alternate = this.eat(types._else) ? this.parseStatement(\"if\") : null;\n return this.finishNode(node, \"IfStatement\")\n};\n\npp$1.parseReturnStatement = function(node) {\n if (!this.inFunction && !this.options.allowReturnOutsideFunction)\n { this.raise(this.start, \"'return' outside of function\"); }\n this.next();\n\n // In `return` (and `break`/`continue`), the keywords with\n // optional arguments, we eagerly look for a semicolon or the\n // possibility to insert one.\n\n if (this.eat(types.semi) || this.insertSemicolon()) { node.argument = null; }\n else { node.argument = this.parseExpression(); this.semicolon(); }\n return this.finishNode(node, \"ReturnStatement\")\n};\n\npp$1.parseSwitchStatement = function(node) {\n this.next();\n node.discriminant = this.parseParenExpression();\n node.cases = [];\n this.expect(types.braceL);\n this.labels.push(switchLabel);\n this.enterScope(0);\n\n // Statements under must be grouped (by label) in SwitchCase\n // nodes. `cur` is used to keep the node that we are currently\n // adding statements to.\n\n var cur;\n for (var sawDefault = false; this.type !== types.braceR;) {\n if (this.type === types._case || this.type === types._default) {\n var isCase = this.type === types._case;\n if (cur) { this.finishNode(cur, \"SwitchCase\"); }\n node.cases.push(cur = this.startNode());\n cur.consequent = [];\n this.next();\n if (isCase) {\n cur.test = this.parseExpression();\n } else {\n if (sawDefault) { this.raiseRecoverable(this.lastTokStart, \"Multiple default clauses\"); }\n sawDefault = true;\n cur.test = null;\n }\n this.expect(types.colon);\n } else {\n if (!cur) { this.unexpected(); }\n cur.consequent.push(this.parseStatement(null));\n }\n }\n this.exitScope();\n if (cur) { this.finishNode(cur, \"SwitchCase\"); }\n this.next(); // Closing brace\n this.labels.pop();\n return this.finishNode(node, \"SwitchStatement\")\n};\n\npp$1.parseThrowStatement = function(node) {\n this.next();\n if (lineBreak.test(this.input.slice(this.lastTokEnd, this.start)))\n { this.raise(this.lastTokEnd, \"Illegal newline after throw\"); }\n node.argument = this.parseExpression();\n this.semicolon();\n return this.finishNode(node, \"ThrowStatement\")\n};\n\n// Reused empty array added for node fields that are always empty.\n\nvar empty = [];\n\npp$1.parseTryStatement = function(node) {\n this.next();\n node.block = this.parseBlock();\n node.handler = null;\n if (this.type === types._catch) {\n var clause = this.startNode();\n this.next();\n if (this.eat(types.parenL)) {\n clause.param = this.parseBindingAtom();\n var simple = clause.param.type === \"Identifier\";\n this.enterScope(simple ? SCOPE_SIMPLE_CATCH : 0);\n this.checkLVal(clause.param, simple ? BIND_SIMPLE_CATCH : BIND_LEXICAL);\n this.expect(types.parenR);\n } else {\n if (this.options.ecmaVersion < 10) { this.unexpected(); }\n clause.param = null;\n this.enterScope(0);\n }\n clause.body = this.parseBlock(false);\n this.exitScope();\n node.handler = this.finishNode(clause, \"CatchClause\");\n }\n node.finalizer = this.eat(types._finally) ? this.parseBlock() : null;\n if (!node.handler && !node.finalizer)\n { this.raise(node.start, \"Missing catch or finally clause\"); }\n return this.finishNode(node, \"TryStatement\")\n};\n\npp$1.parseVarStatement = function(node, kind) {\n this.next();\n this.parseVar(node, false, kind);\n this.semicolon();\n return this.finishNode(node, \"VariableDeclaration\")\n};\n\npp$1.parseWhileStatement = function(node) {\n this.next();\n node.test = this.parseParenExpression();\n this.labels.push(loopLabel);\n node.body = this.parseStatement(\"while\");\n this.labels.pop();\n return this.finishNode(node, \"WhileStatement\")\n};\n\npp$1.parseWithStatement = function(node) {\n if (this.strict) { this.raise(this.start, \"'with' in strict mode\"); }\n this.next();\n node.object = this.parseParenExpression();\n node.body = this.parseStatement(\"with\");\n return this.finishNode(node, \"WithStatement\")\n};\n\npp$1.parseEmptyStatement = function(node) {\n this.next();\n return this.finishNode(node, \"EmptyStatement\")\n};\n\npp$1.parseLabeledStatement = function(node, maybeName, expr, context) {\n for (var i$1 = 0, list = this.labels; i$1 < list.length; i$1 += 1)\n {\n var label = list[i$1];\n\n if (label.name === maybeName)\n { this.raise(expr.start, \"Label '\" + maybeName + \"' is already declared\");\n } }\n var kind = this.type.isLoop ? \"loop\" : this.type === types._switch ? \"switch\" : null;\n for (var i = this.labels.length - 1; i >= 0; i--) {\n var label$1 = this.labels[i];\n if (label$1.statementStart === node.start) {\n // Update information about previous labels on this node\n label$1.statementStart = this.start;\n label$1.kind = kind;\n } else { break }\n }\n this.labels.push({name: maybeName, kind: kind, statementStart: this.start});\n node.body = this.parseStatement(context ? context.indexOf(\"label\") === -1 ? context + \"label\" : context : \"label\");\n this.labels.pop();\n node.label = expr;\n return this.finishNode(node, \"LabeledStatement\")\n};\n\npp$1.parseExpressionStatement = function(node, expr) {\n node.expression = expr;\n this.semicolon();\n return this.finishNode(node, \"ExpressionStatement\")\n};\n\n// Parse a semicolon-enclosed block of statements, handling `\"use\n// strict\"` declarations when `allowStrict` is true (used for\n// function bodies).\n\npp$1.parseBlock = function(createNewLexicalScope, node) {\n if ( createNewLexicalScope === void 0 ) createNewLexicalScope = true;\n if ( node === void 0 ) node = this.startNode();\n\n node.body = [];\n this.expect(types.braceL);\n if (createNewLexicalScope) { this.enterScope(0); }\n while (!this.eat(types.braceR)) {\n var stmt = this.parseStatement(null);\n node.body.push(stmt);\n }\n if (createNewLexicalScope) { this.exitScope(); }\n return this.finishNode(node, \"BlockStatement\")\n};\n\n// Parse a regular `for` loop. The disambiguation code in\n// `parseStatement` will already have parsed the init statement or\n// expression.\n\npp$1.parseFor = function(node, init) {\n node.init = init;\n this.expect(types.semi);\n node.test = this.type === types.semi ? null : this.parseExpression();\n this.expect(types.semi);\n node.update = this.type === types.parenR ? null : this.parseExpression();\n this.expect(types.parenR);\n node.body = this.parseStatement(\"for\");\n this.exitScope();\n this.labels.pop();\n return this.finishNode(node, \"ForStatement\")\n};\n\n// Parse a `for`/`in` and `for`/`of` loop, which are almost\n// same from parser's perspective.\n\npp$1.parseForIn = function(node, init) {\n var isForIn = this.type === types._in;\n this.next();\n\n if (\n init.type === \"VariableDeclaration\" &&\n init.declarations[0].init != null &&\n (\n !isForIn ||\n this.options.ecmaVersion < 8 ||\n this.strict ||\n init.kind !== \"var\" ||\n init.declarations[0].id.type !== \"Identifier\"\n )\n ) {\n this.raise(\n init.start,\n ((isForIn ? \"for-in\" : \"for-of\") + \" loop variable declaration may not have an initializer\")\n );\n } else if (init.type === \"AssignmentPattern\") {\n this.raise(init.start, \"Invalid left-hand side in for-loop\");\n }\n node.left = init;\n node.right = isForIn ? this.parseExpression() : this.parseMaybeAssign();\n this.expect(types.parenR);\n node.body = this.parseStatement(\"for\");\n this.exitScope();\n this.labels.pop();\n return this.finishNode(node, isForIn ? \"ForInStatement\" : \"ForOfStatement\")\n};\n\n// Parse a list of variable declarations.\n\npp$1.parseVar = function(node, isFor, kind) {\n node.declarations = [];\n node.kind = kind;\n for (;;) {\n var decl = this.startNode();\n this.parseVarId(decl, kind);\n if (this.eat(types.eq)) {\n decl.init = this.parseMaybeAssign(isFor);\n } else if (kind === \"const\" && !(this.type === types._in || (this.options.ecmaVersion >= 6 && this.isContextual(\"of\")))) {\n this.unexpected();\n } else if (decl.id.type !== \"Identifier\" && !(isFor && (this.type === types._in || this.isContextual(\"of\")))) {\n this.raise(this.lastTokEnd, \"Complex binding patterns require an initialization value\");\n } else {\n decl.init = null;\n }\n node.declarations.push(this.finishNode(decl, \"VariableDeclarator\"));\n if (!this.eat(types.comma)) { break }\n }\n return node\n};\n\npp$1.parseVarId = function(decl, kind) {\n decl.id = this.parseBindingAtom();\n this.checkLVal(decl.id, kind === \"var\" ? BIND_VAR : BIND_LEXICAL, false);\n};\n\nvar FUNC_STATEMENT = 1, FUNC_HANGING_STATEMENT = 2, FUNC_NULLABLE_ID = 4;\n\n// Parse a function declaration or literal (depending on the\n// `statement & FUNC_STATEMENT`).\n\n// Remove `allowExpressionBody` for 7.0.0, as it is only called with false\npp$1.parseFunction = function(node, statement, allowExpressionBody, isAsync) {\n this.initFunction(node);\n if (this.options.ecmaVersion >= 9 || this.options.ecmaVersion >= 6 && !isAsync) {\n if (this.type === types.star && (statement & FUNC_HANGING_STATEMENT))\n { this.unexpected(); }\n node.generator = this.eat(types.star);\n }\n if (this.options.ecmaVersion >= 8)\n { node.async = !!isAsync; }\n\n if (statement & FUNC_STATEMENT) {\n node.id = (statement & FUNC_NULLABLE_ID) && this.type !== types.name ? null : this.parseIdent();\n if (node.id && !(statement & FUNC_HANGING_STATEMENT))\n // If it is a regular function declaration in sloppy mode, then it is\n // subject to Annex B semantics (BIND_FUNCTION). Otherwise, the binding\n // mode depends on properties of the current scope (see\n // treatFunctionsAsVar).\n { this.checkLVal(node.id, (this.strict || node.generator || node.async) ? this.treatFunctionsAsVar ? BIND_VAR : BIND_LEXICAL : BIND_FUNCTION); }\n }\n\n var oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldAwaitIdentPos = this.awaitIdentPos;\n this.yieldPos = 0;\n this.awaitPos = 0;\n this.awaitIdentPos = 0;\n this.enterScope(functionFlags(node.async, node.generator));\n\n if (!(statement & FUNC_STATEMENT))\n { node.id = this.type === types.name ? this.parseIdent() : null; }\n\n this.parseFunctionParams(node);\n this.parseFunctionBody(node, allowExpressionBody, false);\n\n this.yieldPos = oldYieldPos;\n this.awaitPos = oldAwaitPos;\n this.awaitIdentPos = oldAwaitIdentPos;\n return this.finishNode(node, (statement & FUNC_STATEMENT) ? \"FunctionDeclaration\" : \"FunctionExpression\")\n};\n\npp$1.parseFunctionParams = function(node) {\n this.expect(types.parenL);\n node.params = this.parseBindingList(types.parenR, false, this.options.ecmaVersion >= 8);\n this.checkYieldAwaitInDefaultParams();\n};\n\n// Parse a class declaration or literal (depending on the\n// `isStatement` parameter).\n\npp$1.parseClass = function(node, isStatement) {\n this.next();\n\n // ecma-262 14.6 Class Definitions\n // A class definition is always strict mode code.\n var oldStrict = this.strict;\n this.strict = true;\n\n this.parseClassId(node, isStatement);\n this.parseClassSuper(node);\n var classBody = this.startNode();\n var hadConstructor = false;\n classBody.body = [];\n this.expect(types.braceL);\n while (!this.eat(types.braceR)) {\n var element = this.parseClassElement(node.superClass !== null);\n if (element) {\n classBody.body.push(element);\n if (element.type === \"MethodDefinition\" && element.kind === \"constructor\") {\n if (hadConstructor) { this.raise(element.start, \"Duplicate constructor in the same class\"); }\n hadConstructor = true;\n }\n }\n }\n node.body = this.finishNode(classBody, \"ClassBody\");\n this.strict = oldStrict;\n return this.finishNode(node, isStatement ? \"ClassDeclaration\" : \"ClassExpression\")\n};\n\npp$1.parseClassElement = function(constructorAllowsSuper) {\n var this$1 = this;\n\n if (this.eat(types.semi)) { return null }\n\n var method = this.startNode();\n var tryContextual = function (k, noLineBreak) {\n if ( noLineBreak === void 0 ) noLineBreak = false;\n\n var start = this$1.start, startLoc = this$1.startLoc;\n if (!this$1.eatContextual(k)) { return false }\n if (this$1.type !== types.parenL && (!noLineBreak || !this$1.canInsertSemicolon())) { return true }\n if (method.key) { this$1.unexpected(); }\n method.computed = false;\n method.key = this$1.startNodeAt(start, startLoc);\n method.key.name = k;\n this$1.finishNode(method.key, \"Identifier\");\n return false\n };\n\n method.kind = \"method\";\n method.static = tryContextual(\"static\");\n var isGenerator = this.eat(types.star);\n var isAsync = false;\n if (!isGenerator) {\n if (this.options.ecmaVersion >= 8 && tryContextual(\"async\", true)) {\n isAsync = true;\n isGenerator = this.options.ecmaVersion >= 9 && this.eat(types.star);\n } else if (tryContextual(\"get\")) {\n method.kind = \"get\";\n } else if (tryContextual(\"set\")) {\n method.kind = \"set\";\n }\n }\n if (!method.key) { this.parsePropertyName(method); }\n var key = method.key;\n var allowsDirectSuper = false;\n if (!method.computed && !method.static && (key.type === \"Identifier\" && key.name === \"constructor\" ||\n key.type === \"Literal\" && key.value === \"constructor\")) {\n if (method.kind !== \"method\") { this.raise(key.start, \"Constructor can't have get/set modifier\"); }\n if (isGenerator) { this.raise(key.start, \"Constructor can't be a generator\"); }\n if (isAsync) { this.raise(key.start, \"Constructor can't be an async method\"); }\n method.kind = \"constructor\";\n allowsDirectSuper = constructorAllowsSuper;\n } else if (method.static && key.type === \"Identifier\" && key.name === \"prototype\") {\n this.raise(key.start, \"Classes may not have a static property named prototype\");\n }\n this.parseClassMethod(method, isGenerator, isAsync, allowsDirectSuper);\n if (method.kind === \"get\" && method.value.params.length !== 0)\n { this.raiseRecoverable(method.value.start, \"getter should have no params\"); }\n if (method.kind === \"set\" && method.value.params.length !== 1)\n { this.raiseRecoverable(method.value.start, \"setter should have exactly one param\"); }\n if (method.kind === \"set\" && method.value.params[0].type === \"RestElement\")\n { this.raiseRecoverable(method.value.params[0].start, \"Setter cannot use rest params\"); }\n return method\n};\n\npp$1.parseClassMethod = function(method, isGenerator, isAsync, allowsDirectSuper) {\n method.value = this.parseMethod(isGenerator, isAsync, allowsDirectSuper);\n return this.finishNode(method, \"MethodDefinition\")\n};\n\npp$1.parseClassId = function(node, isStatement) {\n if (this.type === types.name) {\n node.id = this.parseIdent();\n if (isStatement)\n { this.checkLVal(node.id, BIND_LEXICAL, false); }\n } else {\n if (isStatement === true)\n { this.unexpected(); }\n node.id = null;\n }\n};\n\npp$1.parseClassSuper = function(node) {\n node.superClass = this.eat(types._extends) ? this.parseExprSubscripts() : null;\n};\n\n// Parses module export declaration.\n\npp$1.parseExport = function(node, exports) {\n this.next();\n // export * from '...'\n if (this.eat(types.star)) {\n this.expectContextual(\"from\");\n if (this.type !== types.string) { this.unexpected(); }\n node.source = this.parseExprAtom();\n this.semicolon();\n return this.finishNode(node, \"ExportAllDeclaration\")\n }\n if (this.eat(types._default)) { // export default ...\n this.checkExport(exports, \"default\", this.lastTokStart);\n var isAsync;\n if (this.type === types._function || (isAsync = this.isAsyncFunction())) {\n var fNode = this.startNode();\n this.next();\n if (isAsync) { this.next(); }\n node.declaration = this.parseFunction(fNode, FUNC_STATEMENT | FUNC_NULLABLE_ID, false, isAsync);\n } else if (this.type === types._class) {\n var cNode = this.startNode();\n node.declaration = this.parseClass(cNode, \"nullableID\");\n } else {\n node.declaration = this.parseMaybeAssign();\n this.semicolon();\n }\n return this.finishNode(node, \"ExportDefaultDeclaration\")\n }\n // export var|const|let|function|class ...\n if (this.shouldParseExportStatement()) {\n node.declaration = this.parseStatement(null);\n if (node.declaration.type === \"VariableDeclaration\")\n { this.checkVariableExport(exports, node.declaration.declarations); }\n else\n { this.checkExport(exports, node.declaration.id.name, node.declaration.id.start); }\n node.specifiers = [];\n node.source = null;\n } else { // export { x, y as z } [from '...']\n node.declaration = null;\n node.specifiers = this.parseExportSpecifiers(exports);\n if (this.eatContextual(\"from\")) {\n if (this.type !== types.string) { this.unexpected(); }\n node.source = this.parseExprAtom();\n } else {\n for (var i = 0, list = node.specifiers; i < list.length; i += 1) {\n // check for keywords used as local names\n var spec = list[i];\n\n this.checkUnreserved(spec.local);\n // check if export is defined\n this.checkLocalExport(spec.local);\n }\n\n node.source = null;\n }\n this.semicolon();\n }\n return this.finishNode(node, \"ExportNamedDeclaration\")\n};\n\npp$1.checkExport = function(exports, name, pos) {\n if (!exports) { return }\n if (has(exports, name))\n { this.raiseRecoverable(pos, \"Duplicate export '\" + name + \"'\"); }\n exports[name] = true;\n};\n\npp$1.checkPatternExport = function(exports, pat) {\n var type = pat.type;\n if (type === \"Identifier\")\n { this.checkExport(exports, pat.name, pat.start); }\n else if (type === \"ObjectPattern\")\n { for (var i = 0, list = pat.properties; i < list.length; i += 1)\n {\n var prop = list[i];\n\n this.checkPatternExport(exports, prop);\n } }\n else if (type === \"ArrayPattern\")\n { for (var i$1 = 0, list$1 = pat.elements; i$1 < list$1.length; i$1 += 1) {\n var elt = list$1[i$1];\n\n if (elt) { this.checkPatternExport(exports, elt); }\n } }\n else if (type === \"Property\")\n { this.checkPatternExport(exports, pat.value); }\n else if (type === \"AssignmentPattern\")\n { this.checkPatternExport(exports, pat.left); }\n else if (type === \"RestElement\")\n { this.checkPatternExport(exports, pat.argument); }\n else if (type === \"ParenthesizedExpression\")\n { this.checkPatternExport(exports, pat.expression); }\n};\n\npp$1.checkVariableExport = function(exports, decls) {\n if (!exports) { return }\n for (var i = 0, list = decls; i < list.length; i += 1)\n {\n var decl = list[i];\n\n this.checkPatternExport(exports, decl.id);\n }\n};\n\npp$1.shouldParseExportStatement = function() {\n return this.type.keyword === \"var\" ||\n this.type.keyword === \"const\" ||\n this.type.keyword === \"class\" ||\n this.type.keyword === \"function\" ||\n this.isLet() ||\n this.isAsyncFunction()\n};\n\n// Parses a comma-separated list of module exports.\n\npp$1.parseExportSpecifiers = function(exports) {\n var nodes = [], first = true;\n // export { x, y as z } [from '...']\n this.expect(types.braceL);\n while (!this.eat(types.braceR)) {\n if (!first) {\n this.expect(types.comma);\n if (this.afterTrailingComma(types.braceR)) { break }\n } else { first = false; }\n\n var node = this.startNode();\n node.local = this.parseIdent(true);\n node.exported = this.eatContextual(\"as\") ? this.parseIdent(true) : node.local;\n this.checkExport(exports, node.exported.name, node.exported.start);\n nodes.push(this.finishNode(node, \"ExportSpecifier\"));\n }\n return nodes\n};\n\n// Parses import declaration.\n\npp$1.parseImport = function(node) {\n this.next();\n // import '...'\n if (this.type === types.string) {\n node.specifiers = empty;\n node.source = this.parseExprAtom();\n } else {\n node.specifiers = this.parseImportSpecifiers();\n this.expectContextual(\"from\");\n node.source = this.type === types.string ? this.parseExprAtom() : this.unexpected();\n }\n this.semicolon();\n return this.finishNode(node, \"ImportDeclaration\")\n};\n\n// Parses a comma-separated list of module imports.\n\npp$1.parseImportSpecifiers = function() {\n var nodes = [], first = true;\n if (this.type === types.name) {\n // import defaultObj, { x, y as z } from '...'\n var node = this.startNode();\n node.local = this.parseIdent();\n this.checkLVal(node.local, BIND_LEXICAL);\n nodes.push(this.finishNode(node, \"ImportDefaultSpecifier\"));\n if (!this.eat(types.comma)) { return nodes }\n }\n if (this.type === types.star) {\n var node$1 = this.startNode();\n this.next();\n this.expectContextual(\"as\");\n node$1.local = this.parseIdent();\n this.checkLVal(node$1.local, BIND_LEXICAL);\n nodes.push(this.finishNode(node$1, \"ImportNamespaceSpecifier\"));\n return nodes\n }\n this.expect(types.braceL);\n while (!this.eat(types.braceR)) {\n if (!first) {\n this.expect(types.comma);\n if (this.afterTrailingComma(types.braceR)) { break }\n } else { first = false; }\n\n var node$2 = this.startNode();\n node$2.imported = this.parseIdent(true);\n if (this.eatContextual(\"as\")) {\n node$2.local = this.parseIdent();\n } else {\n this.checkUnreserved(node$2.imported);\n node$2.local = node$2.imported;\n }\n this.checkLVal(node$2.local, BIND_LEXICAL);\n nodes.push(this.finishNode(node$2, \"ImportSpecifier\"));\n }\n return nodes\n};\n\n// Set `ExpressionStatement#directive` property for directive prologues.\npp$1.adaptDirectivePrologue = function(statements) {\n for (var i = 0; i < statements.length && this.isDirectiveCandidate(statements[i]); ++i) {\n statements[i].directive = statements[i].expression.raw.slice(1, -1);\n }\n};\npp$1.isDirectiveCandidate = function(statement) {\n return (\n statement.type === \"ExpressionStatement\" &&\n statement.expression.type === \"Literal\" &&\n typeof statement.expression.value === \"string\" &&\n // Reject parenthesized strings.\n (this.input[statement.start] === \"\\\"\" || this.input[statement.start] === \"'\")\n )\n};\n\nvar pp$2 = Parser.prototype;\n\n// Convert existing expression atom to assignable pattern\n// if possible.\n\npp$2.toAssignable = function(node, isBinding, refDestructuringErrors) {\n if (this.options.ecmaVersion >= 6 && node) {\n switch (node.type) {\n case \"Identifier\":\n if (this.inAsync && node.name === \"await\")\n { this.raise(node.start, \"Cannot use 'await' as identifier inside an async function\"); }\n break\n\n case \"ObjectPattern\":\n case \"ArrayPattern\":\n case \"RestElement\":\n break\n\n case \"ObjectExpression\":\n node.type = \"ObjectPattern\";\n if (refDestructuringErrors) { this.checkPatternErrors(refDestructuringErrors, true); }\n for (var i = 0, list = node.properties; i < list.length; i += 1) {\n var prop = list[i];\n\n this.toAssignable(prop, isBinding);\n // Early error:\n // AssignmentRestProperty[Yield, Await] :\n // `...` DestructuringAssignmentTarget[Yield, Await]\n //\n // It is a Syntax Error if |DestructuringAssignmentTarget| is an |ArrayLiteral| or an |ObjectLiteral|.\n if (\n prop.type === \"RestElement\" &&\n (prop.argument.type === \"ArrayPattern\" || prop.argument.type === \"ObjectPattern\")\n ) {\n this.raise(prop.argument.start, \"Unexpected token\");\n }\n }\n break\n\n case \"Property\":\n // AssignmentProperty has type === \"Property\"\n if (node.kind !== \"init\") { this.raise(node.key.start, \"Object pattern can't contain getter or setter\"); }\n this.toAssignable(node.value, isBinding);\n break\n\n case \"ArrayExpression\":\n node.type = \"ArrayPattern\";\n if (refDestructuringErrors) { this.checkPatternErrors(refDestructuringErrors, true); }\n this.toAssignableList(node.elements, isBinding);\n break\n\n case \"SpreadElement\":\n node.type = \"RestElement\";\n this.toAssignable(node.argument, isBinding);\n if (node.argument.type === \"AssignmentPattern\")\n { this.raise(node.argument.start, \"Rest elements cannot have a default value\"); }\n break\n\n case \"AssignmentExpression\":\n if (node.operator !== \"=\") { this.raise(node.left.end, \"Only '=' operator can be used for specifying default value.\"); }\n node.type = \"AssignmentPattern\";\n delete node.operator;\n this.toAssignable(node.left, isBinding);\n // falls through to AssignmentPattern\n\n case \"AssignmentPattern\":\n break\n\n case \"ParenthesizedExpression\":\n this.toAssignable(node.expression, isBinding, refDestructuringErrors);\n break\n\n case \"MemberExpression\":\n if (!isBinding) { break }\n\n default:\n this.raise(node.start, \"Assigning to rvalue\");\n }\n } else if (refDestructuringErrors) { this.checkPatternErrors(refDestructuringErrors, true); }\n return node\n};\n\n// Convert list of expression atoms to binding list.\n\npp$2.toAssignableList = function(exprList, isBinding) {\n var end = exprList.length;\n for (var i = 0; i < end; i++) {\n var elt = exprList[i];\n if (elt) { this.toAssignable(elt, isBinding); }\n }\n if (end) {\n var last = exprList[end - 1];\n if (this.options.ecmaVersion === 6 && isBinding && last && last.type === \"RestElement\" && last.argument.type !== \"Identifier\")\n { this.unexpected(last.argument.start); }\n }\n return exprList\n};\n\n// Parses spread element.\n\npp$2.parseSpread = function(refDestructuringErrors) {\n var node = this.startNode();\n this.next();\n node.argument = this.parseMaybeAssign(false, refDestructuringErrors);\n return this.finishNode(node, \"SpreadElement\")\n};\n\npp$2.parseRestBinding = function() {\n var node = this.startNode();\n this.next();\n\n // RestElement inside of a function parameter must be an identifier\n if (this.options.ecmaVersion === 6 && this.type !== types.name)\n { this.unexpected(); }\n\n node.argument = this.parseBindingAtom();\n\n return this.finishNode(node, \"RestElement\")\n};\n\n// Parses lvalue (assignable) atom.\n\npp$2.parseBindingAtom = function() {\n if (this.options.ecmaVersion >= 6) {\n switch (this.type) {\n case types.bracketL:\n var node = this.startNode();\n this.next();\n node.elements = this.parseBindingList(types.bracketR, true, true);\n return this.finishNode(node, \"ArrayPattern\")\n\n case types.braceL:\n return this.parseObj(true)\n }\n }\n return this.parseIdent()\n};\n\npp$2.parseBindingList = function(close, allowEmpty, allowTrailingComma) {\n var elts = [], first = true;\n while (!this.eat(close)) {\n if (first) { first = false; }\n else { this.expect(types.comma); }\n if (allowEmpty && this.type === types.comma) {\n elts.push(null);\n } else if (allowTrailingComma && this.afterTrailingComma(close)) {\n break\n } else if (this.type === types.ellipsis) {\n var rest = this.parseRestBinding();\n this.parseBindingListItem(rest);\n elts.push(rest);\n if (this.type === types.comma) { this.raise(this.start, \"Comma is not permitted after the rest element\"); }\n this.expect(close);\n break\n } else {\n var elem = this.parseMaybeDefault(this.start, this.startLoc);\n this.parseBindingListItem(elem);\n elts.push(elem);\n }\n }\n return elts\n};\n\npp$2.parseBindingListItem = function(param) {\n return param\n};\n\n// Parses assignment pattern around given atom if possible.\n\npp$2.parseMaybeDefault = function(startPos, startLoc, left) {\n left = left || this.parseBindingAtom();\n if (this.options.ecmaVersion < 6 || !this.eat(types.eq)) { return left }\n var node = this.startNodeAt(startPos, startLoc);\n node.left = left;\n node.right = this.parseMaybeAssign();\n return this.finishNode(node, \"AssignmentPattern\")\n};\n\n// Verify that a node is an lval — something that can be assigned\n// to.\n// bindingType can be either:\n// 'var' indicating that the lval creates a 'var' binding\n// 'let' indicating that the lval creates a lexical ('let' or 'const') binding\n// 'none' indicating that the binding should be checked for illegal identifiers, but not for duplicate references\n\npp$2.checkLVal = function(expr, bindingType, checkClashes) {\n if ( bindingType === void 0 ) bindingType = BIND_NONE;\n\n switch (expr.type) {\n case \"Identifier\":\n if (bindingType === BIND_LEXICAL && expr.name === \"let\")\n { this.raiseRecoverable(expr.start, \"let is disallowed as a lexically bound name\"); }\n if (this.strict && this.reservedWordsStrictBind.test(expr.name))\n { this.raiseRecoverable(expr.start, (bindingType ? \"Binding \" : \"Assigning to \") + expr.name + \" in strict mode\"); }\n if (checkClashes) {\n if (has(checkClashes, expr.name))\n { this.raiseRecoverable(expr.start, \"Argument name clash\"); }\n checkClashes[expr.name] = true;\n }\n if (bindingType !== BIND_NONE && bindingType !== BIND_OUTSIDE) { this.declareName(expr.name, bindingType, expr.start); }\n break\n\n case \"MemberExpression\":\n if (bindingType) { this.raiseRecoverable(expr.start, \"Binding member expression\"); }\n break\n\n case \"ObjectPattern\":\n for (var i = 0, list = expr.properties; i < list.length; i += 1)\n {\n var prop = list[i];\n\n this.checkLVal(prop, bindingType, checkClashes);\n }\n break\n\n case \"Property\":\n // AssignmentProperty has type === \"Property\"\n this.checkLVal(expr.value, bindingType, checkClashes);\n break\n\n case \"ArrayPattern\":\n for (var i$1 = 0, list$1 = expr.elements; i$1 < list$1.length; i$1 += 1) {\n var elem = list$1[i$1];\n\n if (elem) { this.checkLVal(elem, bindingType, checkClashes); }\n }\n break\n\n case \"AssignmentPattern\":\n this.checkLVal(expr.left, bindingType, checkClashes);\n break\n\n case \"RestElement\":\n this.checkLVal(expr.argument, bindingType, checkClashes);\n break\n\n case \"ParenthesizedExpression\":\n this.checkLVal(expr.expression, bindingType, checkClashes);\n break\n\n default:\n this.raise(expr.start, (bindingType ? \"Binding\" : \"Assigning to\") + \" rvalue\");\n }\n};\n\n// A recursive descent parser operates by defining functions for all\n\nvar pp$3 = Parser.prototype;\n\n// Check if property name clashes with already added.\n// Object/class getters and setters are not allowed to clash —\n// either with each other or with an init property — and in\n// strict mode, init properties are also not allowed to be repeated.\n\npp$3.checkPropClash = function(prop, propHash, refDestructuringErrors) {\n if (this.options.ecmaVersion >= 9 && prop.type === \"SpreadElement\")\n { return }\n if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand))\n { return }\n var key = prop.key;\n var name;\n switch (key.type) {\n case \"Identifier\": name = key.name; break\n case \"Literal\": name = String(key.value); break\n default: return\n }\n var kind = prop.kind;\n if (this.options.ecmaVersion >= 6) {\n if (name === \"__proto__\" && kind === \"init\") {\n if (propHash.proto) {\n if (refDestructuringErrors && refDestructuringErrors.doubleProto < 0) { refDestructuringErrors.doubleProto = key.start; }\n // Backwards-compat kludge. Can be removed in version 6.0\n else { this.raiseRecoverable(key.start, \"Redefinition of __proto__ property\"); }\n }\n propHash.proto = true;\n }\n return\n }\n name = \"$\" + name;\n var other = propHash[name];\n if (other) {\n var redefinition;\n if (kind === \"init\") {\n redefinition = this.strict && other.init || other.get || other.set;\n } else {\n redefinition = other.init || other[kind];\n }\n if (redefinition)\n { this.raiseRecoverable(key.start, \"Redefinition of property\"); }\n } else {\n other = propHash[name] = {\n init: false,\n get: false,\n set: false\n };\n }\n other[kind] = true;\n};\n\n// ### Expression parsing\n\n// These nest, from the most general expression type at the top to\n// 'atomic', nondivisible expression types at the bottom. Most of\n// the functions will simply let the function(s) below them parse,\n// and, *if* the syntactic construct they handle is present, wrap\n// the AST node that the inner parser gave them in another node.\n\n// Parse a full expression. The optional arguments are used to\n// forbid the `in` operator (in for loops initalization expressions)\n// and provide reference for storing '=' operator inside shorthand\n// property assignment in contexts where both object expression\n// and object pattern might appear (so it's possible to raise\n// delayed syntax error at correct position).\n\npp$3.parseExpression = function(noIn, refDestructuringErrors) {\n var startPos = this.start, startLoc = this.startLoc;\n var expr = this.parseMaybeAssign(noIn, refDestructuringErrors);\n if (this.type === types.comma) {\n var node = this.startNodeAt(startPos, startLoc);\n node.expressions = [expr];\n while (this.eat(types.comma)) { node.expressions.push(this.parseMaybeAssign(noIn, refDestructuringErrors)); }\n return this.finishNode(node, \"SequenceExpression\")\n }\n return expr\n};\n\n// Parse an assignment expression. This includes applications of\n// operators like `+=`.\n\npp$3.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) {\n if (this.isContextual(\"yield\")) {\n if (this.inGenerator) { return this.parseYield(noIn) }\n // The tokenizer will assume an expression is allowed after\n // `yield`, but this isn't that kind of yield\n else { this.exprAllowed = false; }\n }\n\n var ownDestructuringErrors = false, oldParenAssign = -1, oldTrailingComma = -1, oldShorthandAssign = -1;\n if (refDestructuringErrors) {\n oldParenAssign = refDestructuringErrors.parenthesizedAssign;\n oldTrailingComma = refDestructuringErrors.trailingComma;\n oldShorthandAssign = refDestructuringErrors.shorthandAssign;\n refDestructuringErrors.parenthesizedAssign = refDestructuringErrors.trailingComma = refDestructuringErrors.shorthandAssign = -1;\n } else {\n refDestructuringErrors = new DestructuringErrors;\n ownDestructuringErrors = true;\n }\n\n var startPos = this.start, startLoc = this.startLoc;\n if (this.type === types.parenL || this.type === types.name)\n { this.potentialArrowAt = this.start; }\n var left = this.parseMaybeConditional(noIn, refDestructuringErrors);\n if (afterLeftParse) { left = afterLeftParse.call(this, left, startPos, startLoc); }\n if (this.type.isAssign) {\n var node = this.startNodeAt(startPos, startLoc);\n node.operator = this.value;\n node.left = this.type === types.eq ? this.toAssignable(left, false, refDestructuringErrors) : left;\n if (!ownDestructuringErrors) { DestructuringErrors.call(refDestructuringErrors); }\n refDestructuringErrors.shorthandAssign = -1; // reset because shorthand default was used correctly\n this.checkLVal(left);\n this.next();\n node.right = this.parseMaybeAssign(noIn);\n return this.finishNode(node, \"AssignmentExpression\")\n } else {\n if (ownDestructuringErrors) { this.checkExpressionErrors(refDestructuringErrors, true); }\n }\n if (oldParenAssign > -1) { refDestructuringErrors.parenthesizedAssign = oldParenAssign; }\n if (oldTrailingComma > -1) { refDestructuringErrors.trailingComma = oldTrailingComma; }\n if (oldShorthandAssign > -1) { refDestructuringErrors.shorthandAssign = oldShorthandAssign; }\n return left\n};\n\n// Parse a ternary conditional (`?:`) operator.\n\npp$3.parseMaybeConditional = function(noIn, refDestructuringErrors) {\n var startPos = this.start, startLoc = this.startLoc;\n var expr = this.parseExprOps(noIn, refDestructuringErrors);\n if (this.checkExpressionErrors(refDestructuringErrors)) { return expr }\n if (this.eat(types.question)) {\n var node = this.startNodeAt(startPos, startLoc);\n node.test = expr;\n node.consequent = this.parseMaybeAssign();\n this.expect(types.colon);\n node.alternate = this.parseMaybeAssign(noIn);\n return this.finishNode(node, \"ConditionalExpression\")\n }\n return expr\n};\n\n// Start the precedence parser.\n\npp$3.parseExprOps = function(noIn, refDestructuringErrors) {\n var startPos = this.start, startLoc = this.startLoc;\n var expr = this.parseMaybeUnary(refDestructuringErrors, false);\n if (this.checkExpressionErrors(refDestructuringErrors)) { return expr }\n return expr.start === startPos && expr.type === \"ArrowFunctionExpression\" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, noIn)\n};\n\n// Parse binary operators with the operator precedence parsing\n// algorithm. `left` is the left-hand side of the operator.\n// `minPrec` provides context that allows the function to stop and\n// defer further parser to one of its callers when it encounters an\n// operator that has a lower precedence than the set it is parsing.\n\npp$3.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn) {\n var prec = this.type.binop;\n if (prec != null && (!noIn || this.type !== types._in)) {\n if (prec > minPrec) {\n var logical = this.type === types.logicalOR || this.type === types.logicalAND;\n var op = this.value;\n this.next();\n var startPos = this.start, startLoc = this.startLoc;\n var right = this.parseExprOp(this.parseMaybeUnary(null, false), startPos, startLoc, prec, noIn);\n var node = this.buildBinary(leftStartPos, leftStartLoc, left, right, op, logical);\n return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn)\n }\n }\n return left\n};\n\npp$3.buildBinary = function(startPos, startLoc, left, right, op, logical) {\n var node = this.startNodeAt(startPos, startLoc);\n node.left = left;\n node.operator = op;\n node.right = right;\n return this.finishNode(node, logical ? \"LogicalExpression\" : \"BinaryExpression\")\n};\n\n// Parse unary operators, both prefix and postfix.\n\npp$3.parseMaybeUnary = function(refDestructuringErrors, sawUnary) {\n var startPos = this.start, startLoc = this.startLoc, expr;\n if (this.isContextual(\"await\") && (this.inAsync || (!this.inFunction && this.options.allowAwaitOutsideFunction))) {\n expr = this.parseAwait();\n sawUnary = true;\n } else if (this.type.prefix) {\n var node = this.startNode(), update = this.type === types.incDec;\n node.operator = this.value;\n node.prefix = true;\n this.next();\n node.argument = this.parseMaybeUnary(null, true);\n this.checkExpressionErrors(refDestructuringErrors, true);\n if (update) { this.checkLVal(node.argument); }\n else if (this.strict && node.operator === \"delete\" &&\n node.argument.type === \"Identifier\")\n { this.raiseRecoverable(node.start, \"Deleting local variable in strict mode\"); }\n else { sawUnary = true; }\n expr = this.finishNode(node, update ? \"UpdateExpression\" : \"UnaryExpression\");\n } else {\n expr = this.parseExprSubscripts(refDestructuringErrors);\n if (this.checkExpressionErrors(refDestructuringErrors)) { return expr }\n while (this.type.postfix && !this.canInsertSemicolon()) {\n var node$1 = this.startNodeAt(startPos, startLoc);\n node$1.operator = this.value;\n node$1.prefix = false;\n node$1.argument = expr;\n this.checkLVal(expr);\n this.next();\n expr = this.finishNode(node$1, \"UpdateExpression\");\n }\n }\n\n if (!sawUnary && this.eat(types.starstar))\n { return this.buildBinary(startPos, startLoc, expr, this.parseMaybeUnary(null, false), \"**\", false) }\n else\n { return expr }\n};\n\n// Parse call, dot, and `[]`-subscript expressions.\n\npp$3.parseExprSubscripts = function(refDestructuringErrors) {\n var startPos = this.start, startLoc = this.startLoc;\n var expr = this.parseExprAtom(refDestructuringErrors);\n var skipArrowSubscripts = expr.type === \"ArrowFunctionExpression\" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== \")\";\n if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) { return expr }\n var result = this.parseSubscripts(expr, startPos, startLoc);\n if (refDestructuringErrors && result.type === \"MemberExpression\") {\n if (refDestructuringErrors.parenthesizedAssign >= result.start) { refDestructuringErrors.parenthesizedAssign = -1; }\n if (refDestructuringErrors.parenthesizedBind >= result.start) { refDestructuringErrors.parenthesizedBind = -1; }\n }\n return result\n};\n\npp$3.parseSubscripts = function(base, startPos, startLoc, noCalls) {\n var maybeAsyncArrow = this.options.ecmaVersion >= 8 && base.type === \"Identifier\" && base.name === \"async\" &&\n this.lastTokEnd === base.end && !this.canInsertSemicolon() && this.input.slice(base.start, base.end) === \"async\";\n while (true) {\n var element = this.parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow);\n if (element === base || element.type === \"ArrowFunctionExpression\") { return element }\n base = element;\n }\n};\n\npp$3.parseSubscript = function(base, startPos, startLoc, noCalls, maybeAsyncArrow) {\n var computed = this.eat(types.bracketL);\n if (computed || this.eat(types.dot)) {\n var node = this.startNodeAt(startPos, startLoc);\n node.object = base;\n node.property = computed ? this.parseExpression() : this.parseIdent(this.options.allowReserved !== \"never\");\n node.computed = !!computed;\n if (computed) { this.expect(types.bracketR); }\n base = this.finishNode(node, \"MemberExpression\");\n } else if (!noCalls && this.eat(types.parenL)) {\n var refDestructuringErrors = new DestructuringErrors, oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldAwaitIdentPos = this.awaitIdentPos;\n this.yieldPos = 0;\n this.awaitPos = 0;\n this.awaitIdentPos = 0;\n var exprList = this.parseExprList(types.parenR, this.options.ecmaVersion >= 8 && base.type !== \"Import\", false, refDestructuringErrors);\n if (maybeAsyncArrow && !this.canInsertSemicolon() && this.eat(types.arrow)) {\n this.checkPatternErrors(refDestructuringErrors, false);\n this.checkYieldAwaitInDefaultParams();\n if (this.awaitIdentPos > 0)\n { this.raise(this.awaitIdentPos, \"Cannot use 'await' as identifier inside an async function\"); }\n this.yieldPos = oldYieldPos;\n this.awaitPos = oldAwaitPos;\n this.awaitIdentPos = oldAwaitIdentPos;\n return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList, true)\n }\n this.checkExpressionErrors(refDestructuringErrors, true);\n this.yieldPos = oldYieldPos || this.yieldPos;\n this.awaitPos = oldAwaitPos || this.awaitPos;\n this.awaitIdentPos = oldAwaitIdentPos || this.awaitIdentPos;\n var node$1 = this.startNodeAt(startPos, startLoc);\n node$1.callee = base;\n node$1.arguments = exprList;\n if (node$1.callee.type === \"Import\") {\n if (node$1.arguments.length !== 1) {\n this.raise(node$1.start, \"import() requires exactly one argument\");\n }\n\n var importArg = node$1.arguments[0];\n if (importArg && importArg.type === \"SpreadElement\") {\n this.raise(importArg.start, \"... is not allowed in import()\");\n }\n }\n base = this.finishNode(node$1, \"CallExpression\");\n } else if (this.type === types.backQuote) {\n var node$2 = this.startNodeAt(startPos, startLoc);\n node$2.tag = base;\n node$2.quasi = this.parseTemplate({isTagged: true});\n base = this.finishNode(node$2, \"TaggedTemplateExpression\");\n }\n return base\n};\n\n// Parse an atomic expression — either a single token that is an\n// expression, an expression started by a keyword like `function` or\n// `new`, or an expression wrapped in punctuation like `()`, `[]`,\n// or `{}`.\n\npp$3.parseExprAtom = function(refDestructuringErrors) {\n // If a division operator appears in an expression position, the\n // tokenizer got confused, and we force it to read a regexp instead.\n if (this.type === types.slash) { this.readRegexp(); }\n\n var node, canBeArrow = this.potentialArrowAt === this.start;\n switch (this.type) {\n case types._super:\n if (!this.allowSuper)\n { this.raise(this.start, \"'super' keyword outside a method\"); }\n node = this.startNode();\n this.next();\n if (this.type === types.parenL && !this.allowDirectSuper)\n { this.raise(node.start, \"super() call outside constructor of a subclass\"); }\n // The `super` keyword can appear at below:\n // SuperProperty:\n // super [ Expression ]\n // super . IdentifierName\n // SuperCall:\n // super Arguments\n if (this.type !== types.dot && this.type !== types.bracketL && this.type !== types.parenL)\n { this.unexpected(); }\n return this.finishNode(node, \"Super\")\n\n case types._this:\n node = this.startNode();\n this.next();\n return this.finishNode(node, \"ThisExpression\")\n\n case types.name:\n var startPos = this.start, startLoc = this.startLoc, containsEsc = this.containsEsc;\n var id = this.parseIdent(false);\n if (this.options.ecmaVersion >= 8 && !containsEsc && id.name === \"async\" && !this.canInsertSemicolon() && this.eat(types._function))\n { return this.parseFunction(this.startNodeAt(startPos, startLoc), 0, false, true) }\n if (canBeArrow && !this.canInsertSemicolon()) {\n if (this.eat(types.arrow))\n { return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], false) }\n if (this.options.ecmaVersion >= 8 && id.name === \"async\" && this.type === types.name && !containsEsc) {\n id = this.parseIdent(false);\n if (this.canInsertSemicolon() || !this.eat(types.arrow))\n { this.unexpected(); }\n return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id], true)\n }\n }\n return id\n\n case types.regexp:\n var value = this.value;\n node = this.parseLiteral(value.value);\n node.regex = {pattern: value.pattern, flags: value.flags};\n return node\n\n case types.num: case types.string:\n return this.parseLiteral(this.value)\n\n case types._null: case types._true: case types._false:\n node = this.startNode();\n node.value = this.type === types._null ? null : this.type === types._true;\n node.raw = this.type.keyword;\n this.next();\n return this.finishNode(node, \"Literal\")\n\n case types.parenL:\n var start = this.start, expr = this.parseParenAndDistinguishExpression(canBeArrow);\n if (refDestructuringErrors) {\n if (refDestructuringErrors.parenthesizedAssign < 0 && !this.isSimpleAssignTarget(expr))\n { refDestructuringErrors.parenthesizedAssign = start; }\n if (refDestructuringErrors.parenthesizedBind < 0)\n { refDestructuringErrors.parenthesizedBind = start; }\n }\n return expr\n\n case types.bracketL:\n node = this.startNode();\n this.next();\n node.elements = this.parseExprList(types.bracketR, true, true, refDestructuringErrors);\n return this.finishNode(node, \"ArrayExpression\")\n\n case types.braceL:\n return this.parseObj(false, refDestructuringErrors)\n\n case types._function:\n node = this.startNode();\n this.next();\n return this.parseFunction(node, 0)\n\n case types._class:\n return this.parseClass(this.startNode(), false)\n\n case types._new:\n return this.parseNew()\n\n case types.backQuote:\n return this.parseTemplate()\n\n case types._import:\n if (this.options.ecmaVersion > 10) {\n return this.parseDynamicImport()\n } else {\n return this.unexpected()\n }\n\n default:\n this.unexpected();\n }\n};\n\npp$3.parseDynamicImport = function() {\n var node = this.startNode();\n this.next();\n if (this.type !== types.parenL) {\n this.unexpected();\n }\n return this.finishNode(node, \"Import\")\n};\n\npp$3.parseLiteral = function(value) {\n var node = this.startNode();\n node.value = value;\n node.raw = this.input.slice(this.start, this.end);\n if (node.raw.charCodeAt(node.raw.length - 1) === 110) { node.bigint = node.raw.slice(0, -1); }\n this.next();\n return this.finishNode(node, \"Literal\")\n};\n\npp$3.parseParenExpression = function() {\n this.expect(types.parenL);\n var val = this.parseExpression();\n this.expect(types.parenR);\n return val\n};\n\npp$3.parseParenAndDistinguishExpression = function(canBeArrow) {\n var startPos = this.start, startLoc = this.startLoc, val, allowTrailingComma = this.options.ecmaVersion >= 8;\n if (this.options.ecmaVersion >= 6) {\n this.next();\n\n var innerStartPos = this.start, innerStartLoc = this.startLoc;\n var exprList = [], first = true, lastIsComma = false;\n var refDestructuringErrors = new DestructuringErrors, oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, spreadStart;\n this.yieldPos = 0;\n this.awaitPos = 0;\n // Do not save awaitIdentPos to allow checking awaits nested in parameters\n while (this.type !== types.parenR) {\n first ? first = false : this.expect(types.comma);\n if (allowTrailingComma && this.afterTrailingComma(types.parenR, true)) {\n lastIsComma = true;\n break\n } else if (this.type === types.ellipsis) {\n spreadStart = this.start;\n exprList.push(this.parseParenItem(this.parseRestBinding()));\n if (this.type === types.comma) { this.raise(this.start, \"Comma is not permitted after the rest element\"); }\n break\n } else {\n exprList.push(this.parseMaybeAssign(false, refDestructuringErrors, this.parseParenItem));\n }\n }\n var innerEndPos = this.start, innerEndLoc = this.startLoc;\n this.expect(types.parenR);\n\n if (canBeArrow && !this.canInsertSemicolon() && this.eat(types.arrow)) {\n this.checkPatternErrors(refDestructuringErrors, false);\n this.checkYieldAwaitInDefaultParams();\n this.yieldPos = oldYieldPos;\n this.awaitPos = oldAwaitPos;\n return this.parseParenArrowList(startPos, startLoc, exprList)\n }\n\n if (!exprList.length || lastIsComma) { this.unexpected(this.lastTokStart); }\n if (spreadStart) { this.unexpected(spreadStart); }\n this.checkExpressionErrors(refDestructuringErrors, true);\n this.yieldPos = oldYieldPos || this.yieldPos;\n this.awaitPos = oldAwaitPos || this.awaitPos;\n\n if (exprList.length > 1) {\n val = this.startNodeAt(innerStartPos, innerStartLoc);\n val.expressions = exprList;\n this.finishNodeAt(val, \"SequenceExpression\", innerEndPos, innerEndLoc);\n } else {\n val = exprList[0];\n }\n } else {\n val = this.parseParenExpression();\n }\n\n if (this.options.preserveParens) {\n var par = this.startNodeAt(startPos, startLoc);\n par.expression = val;\n return this.finishNode(par, \"ParenthesizedExpression\")\n } else {\n return val\n }\n};\n\npp$3.parseParenItem = function(item) {\n return item\n};\n\npp$3.parseParenArrowList = function(startPos, startLoc, exprList) {\n return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList)\n};\n\n// New's precedence is slightly tricky. It must allow its argument to\n// be a `[]` or dot subscript expression, but not a call — at least,\n// not without wrapping it in parentheses. Thus, it uses the noCalls\n// argument to parseSubscripts to prevent it from consuming the\n// argument list.\n\nvar empty$1 = [];\n\npp$3.parseNew = function() {\n var node = this.startNode();\n var meta = this.parseIdent(true);\n if (this.options.ecmaVersion >= 6 && this.eat(types.dot)) {\n node.meta = meta;\n var containsEsc = this.containsEsc;\n node.property = this.parseIdent(true);\n if (node.property.name !== \"target\" || containsEsc)\n { this.raiseRecoverable(node.property.start, \"The only valid meta property for new is new.target\"); }\n if (!this.inNonArrowFunction())\n { this.raiseRecoverable(node.start, \"new.target can only be used in functions\"); }\n return this.finishNode(node, \"MetaProperty\")\n }\n var startPos = this.start, startLoc = this.startLoc;\n node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true);\n if (this.options.ecmaVersion > 10 && node.callee.type === \"Import\") {\n this.raise(node.callee.start, \"Cannot use new with import(...)\");\n }\n if (this.eat(types.parenL)) { node.arguments = this.parseExprList(types.parenR, this.options.ecmaVersion >= 8 && node.callee.type !== \"Import\", false); }\n else { node.arguments = empty$1; }\n return this.finishNode(node, \"NewExpression\")\n};\n\n// Parse template expression.\n\npp$3.parseTemplateElement = function(ref) {\n var isTagged = ref.isTagged;\n\n var elem = this.startNode();\n if (this.type === types.invalidTemplate) {\n if (!isTagged) {\n this.raiseRecoverable(this.start, \"Bad escape sequence in untagged template literal\");\n }\n elem.value = {\n raw: this.value,\n cooked: null\n };\n } else {\n elem.value = {\n raw: this.input.slice(this.start, this.end).replace(/\\r\\n?/g, \"\\n\"),\n cooked: this.value\n };\n }\n this.next();\n elem.tail = this.type === types.backQuote;\n return this.finishNode(elem, \"TemplateElement\")\n};\n\npp$3.parseTemplate = function(ref) {\n if ( ref === void 0 ) ref = {};\n var isTagged = ref.isTagged; if ( isTagged === void 0 ) isTagged = false;\n\n var node = this.startNode();\n this.next();\n node.expressions = [];\n var curElt = this.parseTemplateElement({isTagged: isTagged});\n node.quasis = [curElt];\n while (!curElt.tail) {\n if (this.type === types.eof) { this.raise(this.pos, \"Unterminated template literal\"); }\n this.expect(types.dollarBraceL);\n node.expressions.push(this.parseExpression());\n this.expect(types.braceR);\n node.quasis.push(curElt = this.parseTemplateElement({isTagged: isTagged}));\n }\n this.next();\n return this.finishNode(node, \"TemplateLiteral\")\n};\n\npp$3.isAsyncProp = function(prop) {\n return !prop.computed && prop.key.type === \"Identifier\" && prop.key.name === \"async\" &&\n (this.type === types.name || this.type === types.num || this.type === types.string || this.type === types.bracketL || this.type.keyword || (this.options.ecmaVersion >= 9 && this.type === types.star)) &&\n !lineBreak.test(this.input.slice(this.lastTokEnd, this.start))\n};\n\n// Parse an object literal or binding pattern.\n\npp$3.parseObj = function(isPattern, refDestructuringErrors) {\n var node = this.startNode(), first = true, propHash = {};\n node.properties = [];\n this.next();\n while (!this.eat(types.braceR)) {\n if (!first) {\n this.expect(types.comma);\n if (this.afterTrailingComma(types.braceR)) { break }\n } else { first = false; }\n\n var prop = this.parseProperty(isPattern, refDestructuringErrors);\n if (!isPattern) { this.checkPropClash(prop, propHash, refDestructuringErrors); }\n node.properties.push(prop);\n }\n return this.finishNode(node, isPattern ? \"ObjectPattern\" : \"ObjectExpression\")\n};\n\npp$3.parseProperty = function(isPattern, refDestructuringErrors) {\n var prop = this.startNode(), isGenerator, isAsync, startPos, startLoc;\n if (this.options.ecmaVersion >= 9 && this.eat(types.ellipsis)) {\n if (isPattern) {\n prop.argument = this.parseIdent(false);\n if (this.type === types.comma) {\n this.raise(this.start, \"Comma is not permitted after the rest element\");\n }\n return this.finishNode(prop, \"RestElement\")\n }\n // To disallow parenthesized identifier via `this.toAssignable()`.\n if (this.type === types.parenL && refDestructuringErrors) {\n if (refDestructuringErrors.parenthesizedAssign < 0) {\n refDestructuringErrors.parenthesizedAssign = this.start;\n }\n if (refDestructuringErrors.parenthesizedBind < 0) {\n refDestructuringErrors.parenthesizedBind = this.start;\n }\n }\n // Parse argument.\n prop.argument = this.parseMaybeAssign(false, refDestructuringErrors);\n // To disallow trailing comma via `this.toAssignable()`.\n if (this.type === types.comma && refDestructuringErrors && refDestructuringErrors.trailingComma < 0) {\n refDestructuringErrors.trailingComma = this.start;\n }\n // Finish\n return this.finishNode(prop, \"SpreadElement\")\n }\n if (this.options.ecmaVersion >= 6) {\n prop.method = false;\n prop.shorthand = false;\n if (isPattern || refDestructuringErrors) {\n startPos = this.start;\n startLoc = this.startLoc;\n }\n if (!isPattern)\n { isGenerator = this.eat(types.star); }\n }\n var containsEsc = this.containsEsc;\n this.parsePropertyName(prop);\n if (!isPattern && !containsEsc && this.options.ecmaVersion >= 8 && !isGenerator && this.isAsyncProp(prop)) {\n isAsync = true;\n isGenerator = this.options.ecmaVersion >= 9 && this.eat(types.star);\n this.parsePropertyName(prop, refDestructuringErrors);\n } else {\n isAsync = false;\n }\n this.parsePropertyValue(prop, isPattern, isGenerator, isAsync, startPos, startLoc, refDestructuringErrors, containsEsc);\n return this.finishNode(prop, \"Property\")\n};\n\npp$3.parsePropertyValue = function(prop, isPattern, isGenerator, isAsync, startPos, startLoc, refDestructuringErrors, containsEsc) {\n if ((isGenerator || isAsync) && this.type === types.colon)\n { this.unexpected(); }\n\n if (this.eat(types.colon)) {\n prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refDestructuringErrors);\n prop.kind = \"init\";\n } else if (this.options.ecmaVersion >= 6 && this.type === types.parenL) {\n if (isPattern) { this.unexpected(); }\n prop.kind = \"init\";\n prop.method = true;\n prop.value = this.parseMethod(isGenerator, isAsync);\n } else if (!isPattern && !containsEsc &&\n this.options.ecmaVersion >= 5 && !prop.computed && prop.key.type === \"Identifier\" &&\n (prop.key.name === \"get\" || prop.key.name === \"set\") &&\n (this.type !== types.comma && this.type !== types.braceR)) {\n if (isGenerator || isAsync) { this.unexpected(); }\n prop.kind = prop.key.name;\n this.parsePropertyName(prop);\n prop.value = this.parseMethod(false);\n var paramCount = prop.kind === \"get\" ? 0 : 1;\n if (prop.value.params.length !== paramCount) {\n var start = prop.value.start;\n if (prop.kind === \"get\")\n { this.raiseRecoverable(start, \"getter should have no params\"); }\n else\n { this.raiseRecoverable(start, \"setter should have exactly one param\"); }\n } else {\n if (prop.kind === \"set\" && prop.value.params[0].type === \"RestElement\")\n { this.raiseRecoverable(prop.value.params[0].start, \"Setter cannot use rest params\"); }\n }\n } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === \"Identifier\") {\n if (isGenerator || isAsync) { this.unexpected(); }\n this.checkUnreserved(prop.key);\n if (prop.key.name === \"await\" && !this.awaitIdentPos)\n { this.awaitIdentPos = startPos; }\n prop.kind = \"init\";\n if (isPattern) {\n prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);\n } else if (this.type === types.eq && refDestructuringErrors) {\n if (refDestructuringErrors.shorthandAssign < 0)\n { refDestructuringErrors.shorthandAssign = this.start; }\n prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);\n } else {\n prop.value = prop.key;\n }\n prop.shorthand = true;\n } else { this.unexpected(); }\n};\n\npp$3.parsePropertyName = function(prop) {\n if (this.options.ecmaVersion >= 6) {\n if (this.eat(types.bracketL)) {\n prop.computed = true;\n prop.key = this.parseMaybeAssign();\n this.expect(types.bracketR);\n return prop.key\n } else {\n prop.computed = false;\n }\n }\n return prop.key = this.type === types.num || this.type === types.string ? this.parseExprAtom() : this.parseIdent(this.options.allowReserved !== \"never\")\n};\n\n// Initialize empty function node.\n\npp$3.initFunction = function(node) {\n node.id = null;\n if (this.options.ecmaVersion >= 6) { node.generator = node.expression = false; }\n if (this.options.ecmaVersion >= 8) { node.async = false; }\n};\n\n// Parse object or class method.\n\npp$3.parseMethod = function(isGenerator, isAsync, allowDirectSuper) {\n var node = this.startNode(), oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldAwaitIdentPos = this.awaitIdentPos;\n\n this.initFunction(node);\n if (this.options.ecmaVersion >= 6)\n { node.generator = isGenerator; }\n if (this.options.ecmaVersion >= 8)\n { node.async = !!isAsync; }\n\n this.yieldPos = 0;\n this.awaitPos = 0;\n this.awaitIdentPos = 0;\n this.enterScope(functionFlags(isAsync, node.generator) | SCOPE_SUPER | (allowDirectSuper ? SCOPE_DIRECT_SUPER : 0));\n\n this.expect(types.parenL);\n node.params = this.parseBindingList(types.parenR, false, this.options.ecmaVersion >= 8);\n this.checkYieldAwaitInDefaultParams();\n this.parseFunctionBody(node, false, true);\n\n this.yieldPos = oldYieldPos;\n this.awaitPos = oldAwaitPos;\n this.awaitIdentPos = oldAwaitIdentPos;\n return this.finishNode(node, \"FunctionExpression\")\n};\n\n// Parse arrow function expression with given parameters.\n\npp$3.parseArrowExpression = function(node, params, isAsync) {\n var oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldAwaitIdentPos = this.awaitIdentPos;\n\n this.enterScope(functionFlags(isAsync, false) | SCOPE_ARROW);\n this.initFunction(node);\n if (this.options.ecmaVersion >= 8) { node.async = !!isAsync; }\n\n this.yieldPos = 0;\n this.awaitPos = 0;\n this.awaitIdentPos = 0;\n\n node.params = this.toAssignableList(params, true);\n this.parseFunctionBody(node, true, false);\n\n this.yieldPos = oldYieldPos;\n this.awaitPos = oldAwaitPos;\n this.awaitIdentPos = oldAwaitIdentPos;\n return this.finishNode(node, \"ArrowFunctionExpression\")\n};\n\n// Parse function body and check parameters.\n\npp$3.parseFunctionBody = function(node, isArrowFunction, isMethod) {\n var isExpression = isArrowFunction && this.type !== types.braceL;\n var oldStrict = this.strict, useStrict = false;\n\n if (isExpression) {\n node.body = this.parseMaybeAssign();\n node.expression = true;\n this.checkParams(node, false);\n } else {\n var nonSimple = this.options.ecmaVersion >= 7 && !this.isSimpleParamList(node.params);\n if (!oldStrict || nonSimple) {\n useStrict = this.strictDirective(this.end);\n // If this is a strict mode function, verify that argument names\n // are not repeated, and it does not try to bind the words `eval`\n // or `arguments`.\n if (useStrict && nonSimple)\n { this.raiseRecoverable(node.start, \"Illegal 'use strict' directive in function with non-simple parameter list\"); }\n }\n // Start a new scope with regard to labels and the `inFunction`\n // flag (restore them to their old value afterwards).\n var oldLabels = this.labels;\n this.labels = [];\n if (useStrict) { this.strict = true; }\n\n // Add the params to varDeclaredNames to ensure that an error is thrown\n // if a let/const declaration in the function clashes with one of the params.\n this.checkParams(node, !oldStrict && !useStrict && !isArrowFunction && !isMethod && this.isSimpleParamList(node.params));\n node.body = this.parseBlock(false);\n node.expression = false;\n this.adaptDirectivePrologue(node.body.body);\n this.labels = oldLabels;\n }\n this.exitScope();\n\n // Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'\n if (this.strict && node.id) { this.checkLVal(node.id, BIND_OUTSIDE); }\n this.strict = oldStrict;\n};\n\npp$3.isSimpleParamList = function(params) {\n for (var i = 0, list = params; i < list.length; i += 1)\n {\n var param = list[i];\n\n if (param.type !== \"Identifier\") { return false\n } }\n return true\n};\n\n// Checks function params for various disallowed patterns such as using \"eval\"\n// or \"arguments\" and duplicate parameters.\n\npp$3.checkParams = function(node, allowDuplicates) {\n var nameHash = {};\n for (var i = 0, list = node.params; i < list.length; i += 1)\n {\n var param = list[i];\n\n this.checkLVal(param, BIND_VAR, allowDuplicates ? null : nameHash);\n }\n};\n\n// Parses a comma-separated list of expressions, and returns them as\n// an array. `close` is the token type that ends the list, and\n// `allowEmpty` can be turned on to allow subsequent commas with\n// nothing in between them to be parsed as `null` (which is needed\n// for array literals).\n\npp$3.parseExprList = function(close, allowTrailingComma, allowEmpty, refDestructuringErrors) {\n var elts = [], first = true;\n while (!this.eat(close)) {\n if (!first) {\n this.expect(types.comma);\n if (allowTrailingComma && this.afterTrailingComma(close)) { break }\n } else { first = false; }\n\n var elt = (void 0);\n if (allowEmpty && this.type === types.comma)\n { elt = null; }\n else if (this.type === types.ellipsis) {\n elt = this.parseSpread(refDestructuringErrors);\n if (refDestructuringErrors && this.type === types.comma && refDestructuringErrors.trailingComma < 0)\n { refDestructuringErrors.trailingComma = this.start; }\n } else {\n elt = this.parseMaybeAssign(false, refDestructuringErrors);\n }\n elts.push(elt);\n }\n return elts\n};\n\npp$3.checkUnreserved = function(ref) {\n var start = ref.start;\n var end = ref.end;\n var name = ref.name;\n\n if (this.inGenerator && name === \"yield\")\n { this.raiseRecoverable(start, \"Cannot use 'yield' as identifier inside a generator\"); }\n if (this.inAsync && name === \"await\")\n { this.raiseRecoverable(start, \"Cannot use 'await' as identifier inside an async function\"); }\n if (this.keywords.test(name))\n { this.raise(start, (\"Unexpected keyword '\" + name + \"'\")); }\n if (this.options.ecmaVersion < 6 &&\n this.input.slice(start, end).indexOf(\"\\\\\") !== -1) { return }\n var re = this.strict ? this.reservedWordsStrict : this.reservedWords;\n if (re.test(name)) {\n if (!this.inAsync && name === \"await\")\n { this.raiseRecoverable(start, \"Cannot use keyword 'await' outside an async function\"); }\n this.raiseRecoverable(start, (\"The keyword '\" + name + \"' is reserved\"));\n }\n};\n\n// Parse the next token as an identifier. If `liberal` is true (used\n// when parsing properties), it will also convert keywords into\n// identifiers.\n\npp$3.parseIdent = function(liberal, isBinding) {\n var node = this.startNode();\n if (this.type === types.name) {\n node.name = this.value;\n } else if (this.type.keyword) {\n node.name = this.type.keyword;\n\n // To fix https://github.com/acornjs/acorn/issues/575\n // `class` and `function` keywords push new context into this.context.\n // But there is no chance to pop the context if the keyword is consumed as an identifier such as a property name.\n // If the previous token is a dot, this does not apply because the context-managing code already ignored the keyword\n if ((node.name === \"class\" || node.name === \"function\") &&\n (this.lastTokEnd !== this.lastTokStart + 1 || this.input.charCodeAt(this.lastTokStart) !== 46)) {\n this.context.pop();\n }\n } else {\n this.unexpected();\n }\n this.next();\n this.finishNode(node, \"Identifier\");\n if (!liberal) {\n this.checkUnreserved(node);\n if (node.name === \"await\" && !this.awaitIdentPos)\n { this.awaitIdentPos = node.start; }\n }\n return node\n};\n\n// Parses yield expression inside generator.\n\npp$3.parseYield = function(noIn) {\n if (!this.yieldPos) { this.yieldPos = this.start; }\n\n var node = this.startNode();\n this.next();\n if (this.type === types.semi || this.canInsertSemicolon() || (this.type !== types.star && !this.type.startsExpr)) {\n node.delegate = false;\n node.argument = null;\n } else {\n node.delegate = this.eat(types.star);\n node.argument = this.parseMaybeAssign(noIn);\n }\n return this.finishNode(node, \"YieldExpression\")\n};\n\npp$3.parseAwait = function() {\n if (!this.awaitPos) { this.awaitPos = this.start; }\n\n var node = this.startNode();\n this.next();\n node.argument = this.parseMaybeUnary(null, true);\n return this.finishNode(node, \"AwaitExpression\")\n};\n\nvar pp$4 = Parser.prototype;\n\n// This function is used to raise exceptions on parse errors. It\n// takes an offset integer (into the current `input`) to indicate\n// the location of the error, attaches the position to the end\n// of the error message, and then raises a `SyntaxError` with that\n// message.\n\npp$4.raise = function(pos, message) {\n var loc = getLineInfo(this.input, pos);\n message += \" (\" + loc.line + \":\" + loc.column + \")\";\n var err = new SyntaxError(message);\n err.pos = pos; err.loc = loc; err.raisedAt = this.pos;\n throw err\n};\n\npp$4.raiseRecoverable = pp$4.raise;\n\npp$4.curPosition = function() {\n if (this.options.locations) {\n return new Position(this.curLine, this.pos - this.lineStart)\n }\n};\n\nvar pp$5 = Parser.prototype;\n\nvar Scope = function Scope(flags) {\n this.flags = flags;\n // A list of var-declared names in the current lexical scope\n this.var = [];\n // A list of lexically-declared names in the current lexical scope\n this.lexical = [];\n // A list of lexically-declared FunctionDeclaration names in the current lexical scope\n this.functions = [];\n};\n\n// The functions in this module keep track of declared variables in the current scope in order to detect duplicate variable names.\n\npp$5.enterScope = function(flags) {\n this.scopeStack.push(new Scope(flags));\n};\n\npp$5.exitScope = function() {\n this.scopeStack.pop();\n};\n\n// The spec says:\n// > At the top level of a function, or script, function declarations are\n// > treated like var declarations rather than like lexical declarations.\npp$5.treatFunctionsAsVarInScope = function(scope) {\n return (scope.flags & SCOPE_FUNCTION) || !this.inModule && (scope.flags & SCOPE_TOP)\n};\n\npp$5.declareName = function(name, bindingType, pos) {\n var redeclared = false;\n if (bindingType === BIND_LEXICAL) {\n var scope = this.currentScope();\n redeclared = scope.lexical.indexOf(name) > -1 || scope.functions.indexOf(name) > -1 || scope.var.indexOf(name) > -1;\n scope.lexical.push(name);\n if (this.inModule && (scope.flags & SCOPE_TOP))\n { delete this.undefinedExports[name]; }\n } else if (bindingType === BIND_SIMPLE_CATCH) {\n var scope$1 = this.currentScope();\n scope$1.lexical.push(name);\n } else if (bindingType === BIND_FUNCTION) {\n var scope$2 = this.currentScope();\n if (this.treatFunctionsAsVar)\n { redeclared = scope$2.lexical.indexOf(name) > -1; }\n else\n { redeclared = scope$2.lexical.indexOf(name) > -1 || scope$2.var.indexOf(name) > -1; }\n scope$2.functions.push(name);\n } else {\n for (var i = this.scopeStack.length - 1; i >= 0; --i) {\n var scope$3 = this.scopeStack[i];\n if (scope$3.lexical.indexOf(name) > -1 && !((scope$3.flags & SCOPE_SIMPLE_CATCH) && scope$3.lexical[0] === name) ||\n !this.treatFunctionsAsVarInScope(scope$3) && scope$3.functions.indexOf(name) > -1) {\n redeclared = true;\n break\n }\n scope$3.var.push(name);\n if (this.inModule && (scope$3.flags & SCOPE_TOP))\n { delete this.undefinedExports[name]; }\n if (scope$3.flags & SCOPE_VAR) { break }\n }\n }\n if (redeclared) { this.raiseRecoverable(pos, (\"Identifier '\" + name + \"' has already been declared\")); }\n};\n\npp$5.checkLocalExport = function(id) {\n // scope.functions must be empty as Module code is always strict.\n if (this.scopeStack[0].lexical.indexOf(id.name) === -1 &&\n this.scopeStack[0].var.indexOf(id.name) === -1) {\n this.undefinedExports[id.name] = id;\n }\n};\n\npp$5.currentScope = function() {\n return this.scopeStack[this.scopeStack.length - 1]\n};\n\npp$5.currentVarScope = function() {\n for (var i = this.scopeStack.length - 1;; i--) {\n var scope = this.scopeStack[i];\n if (scope.flags & SCOPE_VAR) { return scope }\n }\n};\n\n// Could be useful for `this`, `new.target`, `super()`, `super.property`, and `super[property]`.\npp$5.currentThisScope = function() {\n for (var i = this.scopeStack.length - 1;; i--) {\n var scope = this.scopeStack[i];\n if (scope.flags & SCOPE_VAR && !(scope.flags & SCOPE_ARROW)) { return scope }\n }\n};\n\nvar Node = function Node(parser, pos, loc) {\n this.type = \"\";\n this.start = pos;\n this.end = 0;\n if (parser.options.locations)\n { this.loc = new SourceLocation(parser, loc); }\n if (parser.options.directSourceFile)\n { this.sourceFile = parser.options.directSourceFile; }\n if (parser.options.ranges)\n { this.range = [pos, 0]; }\n};\n\n// Start an AST node, attaching a start offset.\n\nvar pp$6 = Parser.prototype;\n\npp$6.startNode = function() {\n return new Node(this, this.start, this.startLoc)\n};\n\npp$6.startNodeAt = function(pos, loc) {\n return new Node(this, pos, loc)\n};\n\n// Finish an AST node, adding `type` and `end` properties.\n\nfunction finishNodeAt(node, type, pos, loc) {\n node.type = type;\n node.end = pos;\n if (this.options.locations)\n { node.loc.end = loc; }\n if (this.options.ranges)\n { node.range[1] = pos; }\n return node\n}\n\npp$6.finishNode = function(node, type) {\n return finishNodeAt.call(this, node, type, this.lastTokEnd, this.lastTokEndLoc)\n};\n\n// Finish node at given position\n\npp$6.finishNodeAt = function(node, type, pos, loc) {\n return finishNodeAt.call(this, node, type, pos, loc)\n};\n\n// The algorithm used to determine whether a regexp can appear at a\n\nvar TokContext = function TokContext(token, isExpr, preserveSpace, override, generator) {\n this.token = token;\n this.isExpr = !!isExpr;\n this.preserveSpace = !!preserveSpace;\n this.override = override;\n this.generator = !!generator;\n};\n\nvar types$1 = {\n b_stat: new TokContext(\"{\", false),\n b_expr: new TokContext(\"{\", true),\n b_tmpl: new TokContext(\"${\", false),\n p_stat: new TokContext(\"(\", false),\n p_expr: new TokContext(\"(\", true),\n q_tmpl: new TokContext(\"`\", true, true, function (p) { return p.tryReadTemplateToken(); }),\n f_stat: new TokContext(\"function\", false),\n f_expr: new TokContext(\"function\", true),\n f_expr_gen: new TokContext(\"function\", true, false, null, true),\n f_gen: new TokContext(\"function\", false, false, null, true)\n};\n\nvar pp$7 = Parser.prototype;\n\npp$7.initialContext = function() {\n return [types$1.b_stat]\n};\n\npp$7.braceIsBlock = function(prevType) {\n var parent = this.curContext();\n if (parent === types$1.f_expr || parent === types$1.f_stat)\n { return true }\n if (prevType === types.colon && (parent === types$1.b_stat || parent === types$1.b_expr))\n { return !parent.isExpr }\n\n // The check for `tt.name && exprAllowed` detects whether we are\n // after a `yield` or `of` construct. See the `updateContext` for\n // `tt.name`.\n if (prevType === types._return || prevType === types.name && this.exprAllowed)\n { return lineBreak.test(this.input.slice(this.lastTokEnd, this.start)) }\n if (prevType === types._else || prevType === types.semi || prevType === types.eof || prevType === types.parenR || prevType === types.arrow)\n { return true }\n if (prevType === types.braceL)\n { return parent === types$1.b_stat }\n if (prevType === types._var || prevType === types._const || prevType === types.name)\n { return false }\n return !this.exprAllowed\n};\n\npp$7.inGeneratorContext = function() {\n for (var i = this.context.length - 1; i >= 1; i--) {\n var context = this.context[i];\n if (context.token === \"function\")\n { return context.generator }\n }\n return false\n};\n\npp$7.updateContext = function(prevType) {\n var update, type = this.type;\n if (type.keyword && prevType === types.dot)\n { this.exprAllowed = false; }\n else if (update = type.updateContext)\n { update.call(this, prevType); }\n else\n { this.exprAllowed = type.beforeExpr; }\n};\n\n// Token-specific context update code\n\ntypes.parenR.updateContext = types.braceR.updateContext = function() {\n if (this.context.length === 1) {\n this.exprAllowed = true;\n return\n }\n var out = this.context.pop();\n if (out === types$1.b_stat && this.curContext().token === \"function\") {\n out = this.context.pop();\n }\n this.exprAllowed = !out.isExpr;\n};\n\ntypes.braceL.updateContext = function(prevType) {\n this.context.push(this.braceIsBlock(prevType) ? types$1.b_stat : types$1.b_expr);\n this.exprAllowed = true;\n};\n\ntypes.dollarBraceL.updateContext = function() {\n this.context.push(types$1.b_tmpl);\n this.exprAllowed = true;\n};\n\ntypes.parenL.updateContext = function(prevType) {\n var statementParens = prevType === types._if || prevType === types._for || prevType === types._with || prevType === types._while;\n this.context.push(statementParens ? types$1.p_stat : types$1.p_expr);\n this.exprAllowed = true;\n};\n\ntypes.incDec.updateContext = function() {\n // tokExprAllowed stays unchanged\n};\n\ntypes._function.updateContext = types._class.updateContext = function(prevType) {\n if (prevType.beforeExpr && prevType !== types.semi && prevType !== types._else &&\n !(prevType === types._return && lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) &&\n !((prevType === types.colon || prevType === types.braceL) && this.curContext() === types$1.b_stat))\n { this.context.push(types$1.f_expr); }\n else\n { this.context.push(types$1.f_stat); }\n this.exprAllowed = false;\n};\n\ntypes.backQuote.updateContext = function() {\n if (this.curContext() === types$1.q_tmpl)\n { this.context.pop(); }\n else\n { this.context.push(types$1.q_tmpl); }\n this.exprAllowed = false;\n};\n\ntypes.star.updateContext = function(prevType) {\n if (prevType === types._function) {\n var index = this.context.length - 1;\n if (this.context[index] === types$1.f_expr)\n { this.context[index] = types$1.f_expr_gen; }\n else\n { this.context[index] = types$1.f_gen; }\n }\n this.exprAllowed = true;\n};\n\ntypes.name.updateContext = function(prevType) {\n var allowed = false;\n if (this.options.ecmaVersion >= 6 && prevType !== types.dot) {\n if (this.value === \"of\" && !this.exprAllowed ||\n this.value === \"yield\" && this.inGeneratorContext())\n { allowed = true; }\n }\n this.exprAllowed = allowed;\n};\n\n// This file contains Unicode properties extracted from the ECMAScript\n// specification. The lists are extracted like so:\n// $$('#table-binary-unicode-properties > figure > table > tbody > tr > td:nth-child(1) code').map(el => el.innerText)\n\n// #table-binary-unicode-properties\nvar ecma9BinaryProperties = \"ASCII ASCII_Hex_Digit AHex Alphabetic Alpha Any Assigned Bidi_Control Bidi_C Bidi_Mirrored Bidi_M Case_Ignorable CI Cased Changes_When_Casefolded CWCF Changes_When_Casemapped CWCM Changes_When_Lowercased CWL Changes_When_NFKC_Casefolded CWKCF Changes_When_Titlecased CWT Changes_When_Uppercased CWU Dash Default_Ignorable_Code_Point DI Deprecated Dep Diacritic Dia Emoji Emoji_Component Emoji_Modifier Emoji_Modifier_Base Emoji_Presentation Extender Ext Grapheme_Base Gr_Base Grapheme_Extend Gr_Ext Hex_Digit Hex IDS_Binary_Operator IDSB IDS_Trinary_Operator IDST ID_Continue IDC ID_Start IDS Ideographic Ideo Join_Control Join_C Logical_Order_Exception LOE Lowercase Lower Math Noncharacter_Code_Point NChar Pattern_Syntax Pat_Syn Pattern_White_Space Pat_WS Quotation_Mark QMark Radical Regional_Indicator RI Sentence_Terminal STerm Soft_Dotted SD Terminal_Punctuation Term Unified_Ideograph UIdeo Uppercase Upper Variation_Selector VS White_Space space XID_Continue XIDC XID_Start XIDS\";\nvar ecma10BinaryProperties = ecma9BinaryProperties + \" Extended_Pictographic\";\nvar ecma11BinaryProperties = ecma10BinaryProperties;\nvar unicodeBinaryProperties = {\n 9: ecma9BinaryProperties,\n 10: ecma10BinaryProperties,\n 11: ecma11BinaryProperties\n};\n\n// #table-unicode-general-category-values\nvar unicodeGeneralCategoryValues = \"Cased_Letter LC Close_Punctuation Pe Connector_Punctuation Pc Control Cc cntrl Currency_Symbol Sc Dash_Punctuation Pd Decimal_Number Nd digit Enclosing_Mark Me Final_Punctuation Pf Format Cf Initial_Punctuation Pi Letter L Letter_Number Nl Line_Separator Zl Lowercase_Letter Ll Mark M Combining_Mark Math_Symbol Sm Modifier_Letter Lm Modifier_Symbol Sk Nonspacing_Mark Mn Number N Open_Punctuation Ps Other C Other_Letter Lo Other_Number No Other_Punctuation Po Other_Symbol So Paragraph_Separator Zp Private_Use Co Punctuation P punct Separator Z Space_Separator Zs Spacing_Mark Mc Surrogate Cs Symbol S Titlecase_Letter Lt Unassigned Cn Uppercase_Letter Lu\";\n\n// #table-unicode-script-values\nvar ecma9ScriptValues = \"Adlam Adlm Ahom Ahom Anatolian_Hieroglyphs Hluw Arabic Arab Armenian Armn Avestan Avst Balinese Bali Bamum Bamu Bassa_Vah Bass Batak Batk Bengali Beng Bhaiksuki Bhks Bopomofo Bopo Brahmi Brah Braille Brai Buginese Bugi Buhid Buhd Canadian_Aboriginal Cans Carian Cari Caucasian_Albanian Aghb Chakma Cakm Cham Cham Cherokee Cher Common Zyyy Coptic Copt Qaac Cuneiform Xsux Cypriot Cprt Cyrillic Cyrl Deseret Dsrt Devanagari Deva Duployan Dupl Egyptian_Hieroglyphs Egyp Elbasan Elba Ethiopic Ethi Georgian Geor Glagolitic Glag Gothic Goth Grantha Gran Greek Grek Gujarati Gujr Gurmukhi Guru Han Hani Hangul Hang Hanunoo Hano Hatran Hatr Hebrew Hebr Hiragana Hira Imperial_Aramaic Armi Inherited Zinh Qaai Inscriptional_Pahlavi Phli Inscriptional_Parthian Prti Javanese Java Kaithi Kthi Kannada Knda Katakana Kana Kayah_Li Kali Kharoshthi Khar Khmer Khmr Khojki Khoj Khudawadi Sind Lao Laoo Latin Latn Lepcha Lepc Limbu Limb Linear_A Lina Linear_B Linb Lisu Lisu Lycian Lyci Lydian Lydi Mahajani Mahj Malayalam Mlym Mandaic Mand Manichaean Mani Marchen Marc Masaram_Gondi Gonm Meetei_Mayek Mtei Mende_Kikakui Mend Meroitic_Cursive Merc Meroitic_Hieroglyphs Mero Miao Plrd Modi Modi Mongolian Mong Mro Mroo Multani Mult Myanmar Mymr Nabataean Nbat New_Tai_Lue Talu Newa Newa Nko Nkoo Nushu Nshu Ogham Ogam Ol_Chiki Olck Old_Hungarian Hung Old_Italic Ital Old_North_Arabian Narb Old_Permic Perm Old_Persian Xpeo Old_South_Arabian Sarb Old_Turkic Orkh Oriya Orya Osage Osge Osmanya Osma Pahawh_Hmong Hmng Palmyrene Palm Pau_Cin_Hau Pauc Phags_Pa Phag Phoenician Phnx Psalter_Pahlavi Phlp Rejang Rjng Runic Runr Samaritan Samr Saurashtra Saur Sharada Shrd Shavian Shaw Siddham Sidd SignWriting Sgnw Sinhala Sinh Sora_Sompeng Sora Soyombo Soyo Sundanese Sund Syloti_Nagri Sylo Syriac Syrc Tagalog Tglg Tagbanwa Tagb Tai_Le Tale Tai_Tham Lana Tai_Viet Tavt Takri Takr Tamil Taml Tangut Tang Telugu Telu Thaana Thaa Thai Thai Tibetan Tibt Tifinagh Tfng Tirhuta Tirh Ugaritic Ugar Vai Vaii Warang_Citi Wara Yi Yiii Zanabazar_Square Zanb\";\nvar ecma10ScriptValues = ecma9ScriptValues + \" Dogra Dogr Gunjala_Gondi Gong Hanifi_Rohingya Rohg Makasar Maka Medefaidrin Medf Old_Sogdian Sogo Sogdian Sogd\";\nvar ecma11ScriptValues = ecma10ScriptValues + \" Elymaic Elym Nandinagari Nand Nyiakeng_Puachue_Hmong Hmnp Wancho Wcho\";\nvar unicodeScriptValues = {\n 9: ecma9ScriptValues,\n 10: ecma10ScriptValues,\n 11: ecma11ScriptValues\n};\n\nvar data = {};\nfunction buildUnicodeData(ecmaVersion) {\n var d = data[ecmaVersion] = {\n binary: wordsRegexp(unicodeBinaryProperties[ecmaVersion] + \" \" + unicodeGeneralCategoryValues),\n nonBinary: {\n General_Category: wordsRegexp(unicodeGeneralCategoryValues),\n Script: wordsRegexp(unicodeScriptValues[ecmaVersion])\n }\n };\n d.nonBinary.Script_Extensions = d.nonBinary.Script;\n\n d.nonBinary.gc = d.nonBinary.General_Category;\n d.nonBinary.sc = d.nonBinary.Script;\n d.nonBinary.scx = d.nonBinary.Script_Extensions;\n}\nbuildUnicodeData(9);\nbuildUnicodeData(10);\nbuildUnicodeData(11);\n\nvar pp$8 = Parser.prototype;\n\nvar RegExpValidationState = function RegExpValidationState(parser) {\n this.parser = parser;\n this.validFlags = \"gim\" + (parser.options.ecmaVersion >= 6 ? \"uy\" : \"\") + (parser.options.ecmaVersion >= 9 ? \"s\" : \"\");\n this.unicodeProperties = data[parser.options.ecmaVersion >= 11 ? 11 : parser.options.ecmaVersion];\n this.source = \"\";\n this.flags = \"\";\n this.start = 0;\n this.switchU = false;\n this.switchN = false;\n this.pos = 0;\n this.lastIntValue = 0;\n this.lastStringValue = \"\";\n this.lastAssertionIsQuantifiable = false;\n this.numCapturingParens = 0;\n this.maxBackReference = 0;\n this.groupNames = [];\n this.backReferenceNames = [];\n};\n\nRegExpValidationState.prototype.reset = function reset (start, pattern, flags) {\n var unicode = flags.indexOf(\"u\") !== -1;\n this.start = start | 0;\n this.source = pattern + \"\";\n this.flags = flags;\n this.switchU = unicode && this.parser.options.ecmaVersion >= 6;\n this.switchN = unicode && this.parser.options.ecmaVersion >= 9;\n};\n\nRegExpValidationState.prototype.raise = function raise (message) {\n this.parser.raiseRecoverable(this.start, (\"Invalid regular expression: /\" + (this.source) + \"/: \" + message));\n};\n\n// If u flag is given, this returns the code point at the index (it combines a surrogate pair).\n// Otherwise, this returns the code unit of the index (can be a part of a surrogate pair).\nRegExpValidationState.prototype.at = function at (i) {\n var s = this.source;\n var l = s.length;\n if (i >= l) {\n return -1\n }\n var c = s.charCodeAt(i);\n if (!this.switchU || c <= 0xD7FF || c >= 0xE000 || i + 1 >= l) {\n return c\n }\n return (c << 10) + s.charCodeAt(i + 1) - 0x35FDC00\n};\n\nRegExpValidationState.prototype.nextIndex = function nextIndex (i) {\n var s = this.source;\n var l = s.length;\n if (i >= l) {\n return l\n }\n var c = s.charCodeAt(i);\n if (!this.switchU || c <= 0xD7FF || c >= 0xE000 || i + 1 >= l) {\n return i + 1\n }\n return i + 2\n};\n\nRegExpValidationState.prototype.current = function current () {\n return this.at(this.pos)\n};\n\nRegExpValidationState.prototype.lookahead = function lookahead () {\n return this.at(this.nextIndex(this.pos))\n};\n\nRegExpValidationState.prototype.advance = function advance () {\n this.pos = this.nextIndex(this.pos);\n};\n\nRegExpValidationState.prototype.eat = function eat (ch) {\n if (this.current() === ch) {\n this.advance();\n return true\n }\n return false\n};\n\nfunction codePointToString(ch) {\n if (ch <= 0xFFFF) { return String.fromCharCode(ch) }\n ch -= 0x10000;\n return String.fromCharCode((ch >> 10) + 0xD800, (ch & 0x03FF) + 0xDC00)\n}\n\n/**\n * Validate the flags part of a given RegExpLiteral.\n *\n * @param {RegExpValidationState} state The state to validate RegExp.\n * @returns {void}\n */\npp$8.validateRegExpFlags = function(state) {\n var validFlags = state.validFlags;\n var flags = state.flags;\n\n for (var i = 0; i < flags.length; i++) {\n var flag = flags.charAt(i);\n if (validFlags.indexOf(flag) === -1) {\n this.raise(state.start, \"Invalid regular expression flag\");\n }\n if (flags.indexOf(flag, i + 1) > -1) {\n this.raise(state.start, \"Duplicate regular expression flag\");\n }\n }\n};\n\n/**\n * Validate the pattern part of a given RegExpLiteral.\n *\n * @param {RegExpValidationState} state The state to validate RegExp.\n * @returns {void}\n */\npp$8.validateRegExpPattern = function(state) {\n this.regexp_pattern(state);\n\n // The goal symbol for the parse is |Pattern[~U, ~N]|. If the result of\n // parsing contains a |GroupName|, reparse with the goal symbol\n // |Pattern[~U, +N]| and use this result instead. Throw a *SyntaxError*\n // exception if _P_ did not conform to the grammar, if any elements of _P_\n // were not matched by the parse, or if any Early Error conditions exist.\n if (!state.switchN && this.options.ecmaVersion >= 9 && state.groupNames.length > 0) {\n state.switchN = true;\n this.regexp_pattern(state);\n }\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-Pattern\npp$8.regexp_pattern = function(state) {\n state.pos = 0;\n state.lastIntValue = 0;\n state.lastStringValue = \"\";\n state.lastAssertionIsQuantifiable = false;\n state.numCapturingParens = 0;\n state.maxBackReference = 0;\n state.groupNames.length = 0;\n state.backReferenceNames.length = 0;\n\n this.regexp_disjunction(state);\n\n if (state.pos !== state.source.length) {\n // Make the same messages as V8.\n if (state.eat(0x29 /* ) */)) {\n state.raise(\"Unmatched ')'\");\n }\n if (state.eat(0x5D /* [ */) || state.eat(0x7D /* } */)) {\n state.raise(\"Lone quantifier brackets\");\n }\n }\n if (state.maxBackReference > state.numCapturingParens) {\n state.raise(\"Invalid escape\");\n }\n for (var i = 0, list = state.backReferenceNames; i < list.length; i += 1) {\n var name = list[i];\n\n if (state.groupNames.indexOf(name) === -1) {\n state.raise(\"Invalid named capture referenced\");\n }\n }\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-Disjunction\npp$8.regexp_disjunction = function(state) {\n this.regexp_alternative(state);\n while (state.eat(0x7C /* | */)) {\n this.regexp_alternative(state);\n }\n\n // Make the same message as V8.\n if (this.regexp_eatQuantifier(state, true)) {\n state.raise(\"Nothing to repeat\");\n }\n if (state.eat(0x7B /* { */)) {\n state.raise(\"Lone quantifier brackets\");\n }\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-Alternative\npp$8.regexp_alternative = function(state) {\n while (state.pos < state.source.length && this.regexp_eatTerm(state))\n { }\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-Term\npp$8.regexp_eatTerm = function(state) {\n if (this.regexp_eatAssertion(state)) {\n // Handle `QuantifiableAssertion Quantifier` alternative.\n // `state.lastAssertionIsQuantifiable` is true if the last eaten Assertion\n // is a QuantifiableAssertion.\n if (state.lastAssertionIsQuantifiable && this.regexp_eatQuantifier(state)) {\n // Make the same message as V8.\n if (state.switchU) {\n state.raise(\"Invalid quantifier\");\n }\n }\n return true\n }\n\n if (state.switchU ? this.regexp_eatAtom(state) : this.regexp_eatExtendedAtom(state)) {\n this.regexp_eatQuantifier(state);\n return true\n }\n\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-Assertion\npp$8.regexp_eatAssertion = function(state) {\n var start = state.pos;\n state.lastAssertionIsQuantifiable = false;\n\n // ^, $\n if (state.eat(0x5E /* ^ */) || state.eat(0x24 /* $ */)) {\n return true\n }\n\n // \\b \\B\n if (state.eat(0x5C /* \\ */)) {\n if (state.eat(0x42 /* B */) || state.eat(0x62 /* b */)) {\n return true\n }\n state.pos = start;\n }\n\n // Lookahead / Lookbehind\n if (state.eat(0x28 /* ( */) && state.eat(0x3F /* ? */)) {\n var lookbehind = false;\n if (this.options.ecmaVersion >= 9) {\n lookbehind = state.eat(0x3C /* < */);\n }\n if (state.eat(0x3D /* = */) || state.eat(0x21 /* ! */)) {\n this.regexp_disjunction(state);\n if (!state.eat(0x29 /* ) */)) {\n state.raise(\"Unterminated group\");\n }\n state.lastAssertionIsQuantifiable = !lookbehind;\n return true\n }\n }\n\n state.pos = start;\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-Quantifier\npp$8.regexp_eatQuantifier = function(state, noError) {\n if ( noError === void 0 ) noError = false;\n\n if (this.regexp_eatQuantifierPrefix(state, noError)) {\n state.eat(0x3F /* ? */);\n return true\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-QuantifierPrefix\npp$8.regexp_eatQuantifierPrefix = function(state, noError) {\n return (\n state.eat(0x2A /* * */) ||\n state.eat(0x2B /* + */) ||\n state.eat(0x3F /* ? */) ||\n this.regexp_eatBracedQuantifier(state, noError)\n )\n};\npp$8.regexp_eatBracedQuantifier = function(state, noError) {\n var start = state.pos;\n if (state.eat(0x7B /* { */)) {\n var min = 0, max = -1;\n if (this.regexp_eatDecimalDigits(state)) {\n min = state.lastIntValue;\n if (state.eat(0x2C /* , */) && this.regexp_eatDecimalDigits(state)) {\n max = state.lastIntValue;\n }\n if (state.eat(0x7D /* } */)) {\n // SyntaxError in https://www.ecma-international.org/ecma-262/8.0/#sec-term\n if (max !== -1 && max < min && !noError) {\n state.raise(\"numbers out of order in {} quantifier\");\n }\n return true\n }\n }\n if (state.switchU && !noError) {\n state.raise(\"Incomplete quantifier\");\n }\n state.pos = start;\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-Atom\npp$8.regexp_eatAtom = function(state) {\n return (\n this.regexp_eatPatternCharacters(state) ||\n state.eat(0x2E /* . */) ||\n this.regexp_eatReverseSolidusAtomEscape(state) ||\n this.regexp_eatCharacterClass(state) ||\n this.regexp_eatUncapturingGroup(state) ||\n this.regexp_eatCapturingGroup(state)\n )\n};\npp$8.regexp_eatReverseSolidusAtomEscape = function(state) {\n var start = state.pos;\n if (state.eat(0x5C /* \\ */)) {\n if (this.regexp_eatAtomEscape(state)) {\n return true\n }\n state.pos = start;\n }\n return false\n};\npp$8.regexp_eatUncapturingGroup = function(state) {\n var start = state.pos;\n if (state.eat(0x28 /* ( */)) {\n if (state.eat(0x3F /* ? */) && state.eat(0x3A /* : */)) {\n this.regexp_disjunction(state);\n if (state.eat(0x29 /* ) */)) {\n return true\n }\n state.raise(\"Unterminated group\");\n }\n state.pos = start;\n }\n return false\n};\npp$8.regexp_eatCapturingGroup = function(state) {\n if (state.eat(0x28 /* ( */)) {\n if (this.options.ecmaVersion >= 9) {\n this.regexp_groupSpecifier(state);\n } else if (state.current() === 0x3F /* ? */) {\n state.raise(\"Invalid group\");\n }\n this.regexp_disjunction(state);\n if (state.eat(0x29 /* ) */)) {\n state.numCapturingParens += 1;\n return true\n }\n state.raise(\"Unterminated group\");\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-ExtendedAtom\npp$8.regexp_eatExtendedAtom = function(state) {\n return (\n state.eat(0x2E /* . */) ||\n this.regexp_eatReverseSolidusAtomEscape(state) ||\n this.regexp_eatCharacterClass(state) ||\n this.regexp_eatUncapturingGroup(state) ||\n this.regexp_eatCapturingGroup(state) ||\n this.regexp_eatInvalidBracedQuantifier(state) ||\n this.regexp_eatExtendedPatternCharacter(state)\n )\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-InvalidBracedQuantifier\npp$8.regexp_eatInvalidBracedQuantifier = function(state) {\n if (this.regexp_eatBracedQuantifier(state, true)) {\n state.raise(\"Nothing to repeat\");\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-SyntaxCharacter\npp$8.regexp_eatSyntaxCharacter = function(state) {\n var ch = state.current();\n if (isSyntaxCharacter(ch)) {\n state.lastIntValue = ch;\n state.advance();\n return true\n }\n return false\n};\nfunction isSyntaxCharacter(ch) {\n return (\n ch === 0x24 /* $ */ ||\n ch >= 0x28 /* ( */ && ch <= 0x2B /* + */ ||\n ch === 0x2E /* . */ ||\n ch === 0x3F /* ? */ ||\n ch >= 0x5B /* [ */ && ch <= 0x5E /* ^ */ ||\n ch >= 0x7B /* { */ && ch <= 0x7D /* } */\n )\n}\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-PatternCharacter\n// But eat eager.\npp$8.regexp_eatPatternCharacters = function(state) {\n var start = state.pos;\n var ch = 0;\n while ((ch = state.current()) !== -1 && !isSyntaxCharacter(ch)) {\n state.advance();\n }\n return state.pos !== start\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-ExtendedPatternCharacter\npp$8.regexp_eatExtendedPatternCharacter = function(state) {\n var ch = state.current();\n if (\n ch !== -1 &&\n ch !== 0x24 /* $ */ &&\n !(ch >= 0x28 /* ( */ && ch <= 0x2B /* + */) &&\n ch !== 0x2E /* . */ &&\n ch !== 0x3F /* ? */ &&\n ch !== 0x5B /* [ */ &&\n ch !== 0x5E /* ^ */ &&\n ch !== 0x7C /* | */\n ) {\n state.advance();\n return true\n }\n return false\n};\n\n// GroupSpecifier[U] ::\n// [empty]\n// `?` GroupName[?U]\npp$8.regexp_groupSpecifier = function(state) {\n if (state.eat(0x3F /* ? */)) {\n if (this.regexp_eatGroupName(state)) {\n if (state.groupNames.indexOf(state.lastStringValue) !== -1) {\n state.raise(\"Duplicate capture group name\");\n }\n state.groupNames.push(state.lastStringValue);\n return\n }\n state.raise(\"Invalid group\");\n }\n};\n\n// GroupName[U] ::\n// `<` RegExpIdentifierName[?U] `>`\n// Note: this updates `state.lastStringValue` property with the eaten name.\npp$8.regexp_eatGroupName = function(state) {\n state.lastStringValue = \"\";\n if (state.eat(0x3C /* < */)) {\n if (this.regexp_eatRegExpIdentifierName(state) && state.eat(0x3E /* > */)) {\n return true\n }\n state.raise(\"Invalid capture group name\");\n }\n return false\n};\n\n// RegExpIdentifierName[U] ::\n// RegExpIdentifierStart[?U]\n// RegExpIdentifierName[?U] RegExpIdentifierPart[?U]\n// Note: this updates `state.lastStringValue` property with the eaten name.\npp$8.regexp_eatRegExpIdentifierName = function(state) {\n state.lastStringValue = \"\";\n if (this.regexp_eatRegExpIdentifierStart(state)) {\n state.lastStringValue += codePointToString(state.lastIntValue);\n while (this.regexp_eatRegExpIdentifierPart(state)) {\n state.lastStringValue += codePointToString(state.lastIntValue);\n }\n return true\n }\n return false\n};\n\n// RegExpIdentifierStart[U] ::\n// UnicodeIDStart\n// `$`\n// `_`\n// `\\` RegExpUnicodeEscapeSequence[?U]\npp$8.regexp_eatRegExpIdentifierStart = function(state) {\n var start = state.pos;\n var ch = state.current();\n state.advance();\n\n if (ch === 0x5C /* \\ */ && this.regexp_eatRegExpUnicodeEscapeSequence(state)) {\n ch = state.lastIntValue;\n }\n if (isRegExpIdentifierStart(ch)) {\n state.lastIntValue = ch;\n return true\n }\n\n state.pos = start;\n return false\n};\nfunction isRegExpIdentifierStart(ch) {\n return isIdentifierStart(ch, true) || ch === 0x24 /* $ */ || ch === 0x5F /* _ */\n}\n\n// RegExpIdentifierPart[U] ::\n// UnicodeIDContinue\n// `$`\n// `_`\n// `\\` RegExpUnicodeEscapeSequence[?U]\n// \n// \npp$8.regexp_eatRegExpIdentifierPart = function(state) {\n var start = state.pos;\n var ch = state.current();\n state.advance();\n\n if (ch === 0x5C /* \\ */ && this.regexp_eatRegExpUnicodeEscapeSequence(state)) {\n ch = state.lastIntValue;\n }\n if (isRegExpIdentifierPart(ch)) {\n state.lastIntValue = ch;\n return true\n }\n\n state.pos = start;\n return false\n};\nfunction isRegExpIdentifierPart(ch) {\n return isIdentifierChar(ch, true) || ch === 0x24 /* $ */ || ch === 0x5F /* _ */ || ch === 0x200C /* */ || ch === 0x200D /* */\n}\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-AtomEscape\npp$8.regexp_eatAtomEscape = function(state) {\n if (\n this.regexp_eatBackReference(state) ||\n this.regexp_eatCharacterClassEscape(state) ||\n this.regexp_eatCharacterEscape(state) ||\n (state.switchN && this.regexp_eatKGroupName(state))\n ) {\n return true\n }\n if (state.switchU) {\n // Make the same message as V8.\n if (state.current() === 0x63 /* c */) {\n state.raise(\"Invalid unicode escape\");\n }\n state.raise(\"Invalid escape\");\n }\n return false\n};\npp$8.regexp_eatBackReference = function(state) {\n var start = state.pos;\n if (this.regexp_eatDecimalEscape(state)) {\n var n = state.lastIntValue;\n if (state.switchU) {\n // For SyntaxError in https://www.ecma-international.org/ecma-262/8.0/#sec-atomescape\n if (n > state.maxBackReference) {\n state.maxBackReference = n;\n }\n return true\n }\n if (n <= state.numCapturingParens) {\n return true\n }\n state.pos = start;\n }\n return false\n};\npp$8.regexp_eatKGroupName = function(state) {\n if (state.eat(0x6B /* k */)) {\n if (this.regexp_eatGroupName(state)) {\n state.backReferenceNames.push(state.lastStringValue);\n return true\n }\n state.raise(\"Invalid named reference\");\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-CharacterEscape\npp$8.regexp_eatCharacterEscape = function(state) {\n return (\n this.regexp_eatControlEscape(state) ||\n this.regexp_eatCControlLetter(state) ||\n this.regexp_eatZero(state) ||\n this.regexp_eatHexEscapeSequence(state) ||\n this.regexp_eatRegExpUnicodeEscapeSequence(state) ||\n (!state.switchU && this.regexp_eatLegacyOctalEscapeSequence(state)) ||\n this.regexp_eatIdentityEscape(state)\n )\n};\npp$8.regexp_eatCControlLetter = function(state) {\n var start = state.pos;\n if (state.eat(0x63 /* c */)) {\n if (this.regexp_eatControlLetter(state)) {\n return true\n }\n state.pos = start;\n }\n return false\n};\npp$8.regexp_eatZero = function(state) {\n if (state.current() === 0x30 /* 0 */ && !isDecimalDigit(state.lookahead())) {\n state.lastIntValue = 0;\n state.advance();\n return true\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-ControlEscape\npp$8.regexp_eatControlEscape = function(state) {\n var ch = state.current();\n if (ch === 0x74 /* t */) {\n state.lastIntValue = 0x09; /* \\t */\n state.advance();\n return true\n }\n if (ch === 0x6E /* n */) {\n state.lastIntValue = 0x0A; /* \\n */\n state.advance();\n return true\n }\n if (ch === 0x76 /* v */) {\n state.lastIntValue = 0x0B; /* \\v */\n state.advance();\n return true\n }\n if (ch === 0x66 /* f */) {\n state.lastIntValue = 0x0C; /* \\f */\n state.advance();\n return true\n }\n if (ch === 0x72 /* r */) {\n state.lastIntValue = 0x0D; /* \\r */\n state.advance();\n return true\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-ControlLetter\npp$8.regexp_eatControlLetter = function(state) {\n var ch = state.current();\n if (isControlLetter(ch)) {\n state.lastIntValue = ch % 0x20;\n state.advance();\n return true\n }\n return false\n};\nfunction isControlLetter(ch) {\n return (\n (ch >= 0x41 /* A */ && ch <= 0x5A /* Z */) ||\n (ch >= 0x61 /* a */ && ch <= 0x7A /* z */)\n )\n}\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-RegExpUnicodeEscapeSequence\npp$8.regexp_eatRegExpUnicodeEscapeSequence = function(state) {\n var start = state.pos;\n\n if (state.eat(0x75 /* u */)) {\n if (this.regexp_eatFixedHexDigits(state, 4)) {\n var lead = state.lastIntValue;\n if (state.switchU && lead >= 0xD800 && lead <= 0xDBFF) {\n var leadSurrogateEnd = state.pos;\n if (state.eat(0x5C /* \\ */) && state.eat(0x75 /* u */) && this.regexp_eatFixedHexDigits(state, 4)) {\n var trail = state.lastIntValue;\n if (trail >= 0xDC00 && trail <= 0xDFFF) {\n state.lastIntValue = (lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000;\n return true\n }\n }\n state.pos = leadSurrogateEnd;\n state.lastIntValue = lead;\n }\n return true\n }\n if (\n state.switchU &&\n state.eat(0x7B /* { */) &&\n this.regexp_eatHexDigits(state) &&\n state.eat(0x7D /* } */) &&\n isValidUnicode(state.lastIntValue)\n ) {\n return true\n }\n if (state.switchU) {\n state.raise(\"Invalid unicode escape\");\n }\n state.pos = start;\n }\n\n return false\n};\nfunction isValidUnicode(ch) {\n return ch >= 0 && ch <= 0x10FFFF\n}\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-IdentityEscape\npp$8.regexp_eatIdentityEscape = function(state) {\n if (state.switchU) {\n if (this.regexp_eatSyntaxCharacter(state)) {\n return true\n }\n if (state.eat(0x2F /* / */)) {\n state.lastIntValue = 0x2F; /* / */\n return true\n }\n return false\n }\n\n var ch = state.current();\n if (ch !== 0x63 /* c */ && (!state.switchN || ch !== 0x6B /* k */)) {\n state.lastIntValue = ch;\n state.advance();\n return true\n }\n\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-DecimalEscape\npp$8.regexp_eatDecimalEscape = function(state) {\n state.lastIntValue = 0;\n var ch = state.current();\n if (ch >= 0x31 /* 1 */ && ch <= 0x39 /* 9 */) {\n do {\n state.lastIntValue = 10 * state.lastIntValue + (ch - 0x30 /* 0 */);\n state.advance();\n } while ((ch = state.current()) >= 0x30 /* 0 */ && ch <= 0x39 /* 9 */)\n return true\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-CharacterClassEscape\npp$8.regexp_eatCharacterClassEscape = function(state) {\n var ch = state.current();\n\n if (isCharacterClassEscape(ch)) {\n state.lastIntValue = -1;\n state.advance();\n return true\n }\n\n if (\n state.switchU &&\n this.options.ecmaVersion >= 9 &&\n (ch === 0x50 /* P */ || ch === 0x70 /* p */)\n ) {\n state.lastIntValue = -1;\n state.advance();\n if (\n state.eat(0x7B /* { */) &&\n this.regexp_eatUnicodePropertyValueExpression(state) &&\n state.eat(0x7D /* } */)\n ) {\n return true\n }\n state.raise(\"Invalid property name\");\n }\n\n return false\n};\nfunction isCharacterClassEscape(ch) {\n return (\n ch === 0x64 /* d */ ||\n ch === 0x44 /* D */ ||\n ch === 0x73 /* s */ ||\n ch === 0x53 /* S */ ||\n ch === 0x77 /* w */ ||\n ch === 0x57 /* W */\n )\n}\n\n// UnicodePropertyValueExpression ::\n// UnicodePropertyName `=` UnicodePropertyValue\n// LoneUnicodePropertyNameOrValue\npp$8.regexp_eatUnicodePropertyValueExpression = function(state) {\n var start = state.pos;\n\n // UnicodePropertyName `=` UnicodePropertyValue\n if (this.regexp_eatUnicodePropertyName(state) && state.eat(0x3D /* = */)) {\n var name = state.lastStringValue;\n if (this.regexp_eatUnicodePropertyValue(state)) {\n var value = state.lastStringValue;\n this.regexp_validateUnicodePropertyNameAndValue(state, name, value);\n return true\n }\n }\n state.pos = start;\n\n // LoneUnicodePropertyNameOrValue\n if (this.regexp_eatLoneUnicodePropertyNameOrValue(state)) {\n var nameOrValue = state.lastStringValue;\n this.regexp_validateUnicodePropertyNameOrValue(state, nameOrValue);\n return true\n }\n return false\n};\npp$8.regexp_validateUnicodePropertyNameAndValue = function(state, name, value) {\n if (!has(state.unicodeProperties.nonBinary, name))\n { state.raise(\"Invalid property name\"); }\n if (!state.unicodeProperties.nonBinary[name].test(value))\n { state.raise(\"Invalid property value\"); }\n};\npp$8.regexp_validateUnicodePropertyNameOrValue = function(state, nameOrValue) {\n if (!state.unicodeProperties.binary.test(nameOrValue))\n { state.raise(\"Invalid property name\"); }\n};\n\n// UnicodePropertyName ::\n// UnicodePropertyNameCharacters\npp$8.regexp_eatUnicodePropertyName = function(state) {\n var ch = 0;\n state.lastStringValue = \"\";\n while (isUnicodePropertyNameCharacter(ch = state.current())) {\n state.lastStringValue += codePointToString(ch);\n state.advance();\n }\n return state.lastStringValue !== \"\"\n};\nfunction isUnicodePropertyNameCharacter(ch) {\n return isControlLetter(ch) || ch === 0x5F /* _ */\n}\n\n// UnicodePropertyValue ::\n// UnicodePropertyValueCharacters\npp$8.regexp_eatUnicodePropertyValue = function(state) {\n var ch = 0;\n state.lastStringValue = \"\";\n while (isUnicodePropertyValueCharacter(ch = state.current())) {\n state.lastStringValue += codePointToString(ch);\n state.advance();\n }\n return state.lastStringValue !== \"\"\n};\nfunction isUnicodePropertyValueCharacter(ch) {\n return isUnicodePropertyNameCharacter(ch) || isDecimalDigit(ch)\n}\n\n// LoneUnicodePropertyNameOrValue ::\n// UnicodePropertyValueCharacters\npp$8.regexp_eatLoneUnicodePropertyNameOrValue = function(state) {\n return this.regexp_eatUnicodePropertyValue(state)\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-CharacterClass\npp$8.regexp_eatCharacterClass = function(state) {\n if (state.eat(0x5B /* [ */)) {\n state.eat(0x5E /* ^ */);\n this.regexp_classRanges(state);\n if (state.eat(0x5D /* [ */)) {\n return true\n }\n // Unreachable since it threw \"unterminated regular expression\" error before.\n state.raise(\"Unterminated character class\");\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-ClassRanges\n// https://www.ecma-international.org/ecma-262/8.0/#prod-NonemptyClassRanges\n// https://www.ecma-international.org/ecma-262/8.0/#prod-NonemptyClassRangesNoDash\npp$8.regexp_classRanges = function(state) {\n while (this.regexp_eatClassAtom(state)) {\n var left = state.lastIntValue;\n if (state.eat(0x2D /* - */) && this.regexp_eatClassAtom(state)) {\n var right = state.lastIntValue;\n if (state.switchU && (left === -1 || right === -1)) {\n state.raise(\"Invalid character class\");\n }\n if (left !== -1 && right !== -1 && left > right) {\n state.raise(\"Range out of order in character class\");\n }\n }\n }\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-ClassAtom\n// https://www.ecma-international.org/ecma-262/8.0/#prod-ClassAtomNoDash\npp$8.regexp_eatClassAtom = function(state) {\n var start = state.pos;\n\n if (state.eat(0x5C /* \\ */)) {\n if (this.regexp_eatClassEscape(state)) {\n return true\n }\n if (state.switchU) {\n // Make the same message as V8.\n var ch$1 = state.current();\n if (ch$1 === 0x63 /* c */ || isOctalDigit(ch$1)) {\n state.raise(\"Invalid class escape\");\n }\n state.raise(\"Invalid escape\");\n }\n state.pos = start;\n }\n\n var ch = state.current();\n if (ch !== 0x5D /* [ */) {\n state.lastIntValue = ch;\n state.advance();\n return true\n }\n\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-ClassEscape\npp$8.regexp_eatClassEscape = function(state) {\n var start = state.pos;\n\n if (state.eat(0x62 /* b */)) {\n state.lastIntValue = 0x08; /* */\n return true\n }\n\n if (state.switchU && state.eat(0x2D /* - */)) {\n state.lastIntValue = 0x2D; /* - */\n return true\n }\n\n if (!state.switchU && state.eat(0x63 /* c */)) {\n if (this.regexp_eatClassControlLetter(state)) {\n return true\n }\n state.pos = start;\n }\n\n return (\n this.regexp_eatCharacterClassEscape(state) ||\n this.regexp_eatCharacterEscape(state)\n )\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-ClassControlLetter\npp$8.regexp_eatClassControlLetter = function(state) {\n var ch = state.current();\n if (isDecimalDigit(ch) || ch === 0x5F /* _ */) {\n state.lastIntValue = ch % 0x20;\n state.advance();\n return true\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-HexEscapeSequence\npp$8.regexp_eatHexEscapeSequence = function(state) {\n var start = state.pos;\n if (state.eat(0x78 /* x */)) {\n if (this.regexp_eatFixedHexDigits(state, 2)) {\n return true\n }\n if (state.switchU) {\n state.raise(\"Invalid escape\");\n }\n state.pos = start;\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-DecimalDigits\npp$8.regexp_eatDecimalDigits = function(state) {\n var start = state.pos;\n var ch = 0;\n state.lastIntValue = 0;\n while (isDecimalDigit(ch = state.current())) {\n state.lastIntValue = 10 * state.lastIntValue + (ch - 0x30 /* 0 */);\n state.advance();\n }\n return state.pos !== start\n};\nfunction isDecimalDigit(ch) {\n return ch >= 0x30 /* 0 */ && ch <= 0x39 /* 9 */\n}\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-HexDigits\npp$8.regexp_eatHexDigits = function(state) {\n var start = state.pos;\n var ch = 0;\n state.lastIntValue = 0;\n while (isHexDigit(ch = state.current())) {\n state.lastIntValue = 16 * state.lastIntValue + hexToInt(ch);\n state.advance();\n }\n return state.pos !== start\n};\nfunction isHexDigit(ch) {\n return (\n (ch >= 0x30 /* 0 */ && ch <= 0x39 /* 9 */) ||\n (ch >= 0x41 /* A */ && ch <= 0x46 /* F */) ||\n (ch >= 0x61 /* a */ && ch <= 0x66 /* f */)\n )\n}\nfunction hexToInt(ch) {\n if (ch >= 0x41 /* A */ && ch <= 0x46 /* F */) {\n return 10 + (ch - 0x41 /* A */)\n }\n if (ch >= 0x61 /* a */ && ch <= 0x66 /* f */) {\n return 10 + (ch - 0x61 /* a */)\n }\n return ch - 0x30 /* 0 */\n}\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-annexB-LegacyOctalEscapeSequence\n// Allows only 0-377(octal) i.e. 0-255(decimal).\npp$8.regexp_eatLegacyOctalEscapeSequence = function(state) {\n if (this.regexp_eatOctalDigit(state)) {\n var n1 = state.lastIntValue;\n if (this.regexp_eatOctalDigit(state)) {\n var n2 = state.lastIntValue;\n if (n1 <= 3 && this.regexp_eatOctalDigit(state)) {\n state.lastIntValue = n1 * 64 + n2 * 8 + state.lastIntValue;\n } else {\n state.lastIntValue = n1 * 8 + n2;\n }\n } else {\n state.lastIntValue = n1;\n }\n return true\n }\n return false\n};\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-OctalDigit\npp$8.regexp_eatOctalDigit = function(state) {\n var ch = state.current();\n if (isOctalDigit(ch)) {\n state.lastIntValue = ch - 0x30; /* 0 */\n state.advance();\n return true\n }\n state.lastIntValue = 0;\n return false\n};\nfunction isOctalDigit(ch) {\n return ch >= 0x30 /* 0 */ && ch <= 0x37 /* 7 */\n}\n\n// https://www.ecma-international.org/ecma-262/8.0/#prod-Hex4Digits\n// https://www.ecma-international.org/ecma-262/8.0/#prod-HexDigit\n// And HexDigit HexDigit in https://www.ecma-international.org/ecma-262/8.0/#prod-HexEscapeSequence\npp$8.regexp_eatFixedHexDigits = function(state, length) {\n var start = state.pos;\n state.lastIntValue = 0;\n for (var i = 0; i < length; ++i) {\n var ch = state.current();\n if (!isHexDigit(ch)) {\n state.pos = start;\n return false\n }\n state.lastIntValue = 16 * state.lastIntValue + hexToInt(ch);\n state.advance();\n }\n return true\n};\n\n// Object type used to represent tokens. Note that normally, tokens\n// simply exist as properties on the parser object. This is only\n// used for the onToken callback and the external tokenizer.\n\nvar Token = function Token(p) {\n this.type = p.type;\n this.value = p.value;\n this.start = p.start;\n this.end = p.end;\n if (p.options.locations)\n { this.loc = new SourceLocation(p, p.startLoc, p.endLoc); }\n if (p.options.ranges)\n { this.range = [p.start, p.end]; }\n};\n\n// ## Tokenizer\n\nvar pp$9 = Parser.prototype;\n\n// Move to the next token\n\npp$9.next = function() {\n if (this.options.onToken)\n { this.options.onToken(new Token(this)); }\n\n this.lastTokEnd = this.end;\n this.lastTokStart = this.start;\n this.lastTokEndLoc = this.endLoc;\n this.lastTokStartLoc = this.startLoc;\n this.nextToken();\n};\n\npp$9.getToken = function() {\n this.next();\n return new Token(this)\n};\n\n// If we're in an ES6 environment, make parsers iterable\nif (typeof Symbol !== \"undefined\")\n { pp$9[Symbol.iterator] = function() {\n var this$1 = this;\n\n return {\n next: function () {\n var token = this$1.getToken();\n return {\n done: token.type === types.eof,\n value: token\n }\n }\n }\n }; }\n\n// Toggle strict mode. Re-reads the next number or string to please\n// pedantic tests (`\"use strict\"; 010;` should fail).\n\npp$9.curContext = function() {\n return this.context[this.context.length - 1]\n};\n\n// Read a single token, updating the parser object's token-related\n// properties.\n\npp$9.nextToken = function() {\n var curContext = this.curContext();\n if (!curContext || !curContext.preserveSpace) { this.skipSpace(); }\n\n this.start = this.pos;\n if (this.options.locations) { this.startLoc = this.curPosition(); }\n if (this.pos >= this.input.length) { return this.finishToken(types.eof) }\n\n if (curContext.override) { return curContext.override(this) }\n else { this.readToken(this.fullCharCodeAtPos()); }\n};\n\npp$9.readToken = function(code) {\n // Identifier or keyword. '\\uXXXX' sequences are allowed in\n // identifiers, so '\\' also dispatches to that.\n if (isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\\' */)\n { return this.readWord() }\n\n return this.getTokenFromCode(code)\n};\n\npp$9.fullCharCodeAtPos = function() {\n var code = this.input.charCodeAt(this.pos);\n if (code <= 0xd7ff || code >= 0xe000) { return code }\n var next = this.input.charCodeAt(this.pos + 1);\n return (code << 10) + next - 0x35fdc00\n};\n\npp$9.skipBlockComment = function() {\n var startLoc = this.options.onComment && this.curPosition();\n var start = this.pos, end = this.input.indexOf(\"*/\", this.pos += 2);\n if (end === -1) { this.raise(this.pos - 2, \"Unterminated comment\"); }\n this.pos = end + 2;\n if (this.options.locations) {\n lineBreakG.lastIndex = start;\n var match;\n while ((match = lineBreakG.exec(this.input)) && match.index < this.pos) {\n ++this.curLine;\n this.lineStart = match.index + match[0].length;\n }\n }\n if (this.options.onComment)\n { this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos,\n startLoc, this.curPosition()); }\n};\n\npp$9.skipLineComment = function(startSkip) {\n var start = this.pos;\n var startLoc = this.options.onComment && this.curPosition();\n var ch = this.input.charCodeAt(this.pos += startSkip);\n while (this.pos < this.input.length && !isNewLine(ch)) {\n ch = this.input.charCodeAt(++this.pos);\n }\n if (this.options.onComment)\n { this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos,\n startLoc, this.curPosition()); }\n};\n\n// Called at the start of the parse and after every token. Skips\n// whitespace and comments, and.\n\npp$9.skipSpace = function() {\n loop: while (this.pos < this.input.length) {\n var ch = this.input.charCodeAt(this.pos);\n switch (ch) {\n case 32: case 160: // ' '\n ++this.pos;\n break\n case 13:\n if (this.input.charCodeAt(this.pos + 1) === 10) {\n ++this.pos;\n }\n case 10: case 8232: case 8233:\n ++this.pos;\n if (this.options.locations) {\n ++this.curLine;\n this.lineStart = this.pos;\n }\n break\n case 47: // '/'\n switch (this.input.charCodeAt(this.pos + 1)) {\n case 42: // '*'\n this.skipBlockComment();\n break\n case 47:\n this.skipLineComment(2);\n break\n default:\n break loop\n }\n break\n default:\n if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {\n ++this.pos;\n } else {\n break loop\n }\n }\n }\n};\n\n// Called at the end of every token. Sets `end`, `val`, and\n// maintains `context` and `exprAllowed`, and skips the space after\n// the token, so that the next one's `start` will point at the\n// right position.\n\npp$9.finishToken = function(type, val) {\n this.end = this.pos;\n if (this.options.locations) { this.endLoc = this.curPosition(); }\n var prevType = this.type;\n this.type = type;\n this.value = val;\n\n this.updateContext(prevType);\n};\n\n// ### Token reading\n\n// This is the function that is called to fetch the next token. It\n// is somewhat obscure, because it works in character codes rather\n// than characters, and because operator parsing has been inlined\n// into it.\n//\n// All in the name of speed.\n//\npp$9.readToken_dot = function() {\n var next = this.input.charCodeAt(this.pos + 1);\n if (next >= 48 && next <= 57) { return this.readNumber(true) }\n var next2 = this.input.charCodeAt(this.pos + 2);\n if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.'\n this.pos += 3;\n return this.finishToken(types.ellipsis)\n } else {\n ++this.pos;\n return this.finishToken(types.dot)\n }\n};\n\npp$9.readToken_slash = function() { // '/'\n var next = this.input.charCodeAt(this.pos + 1);\n if (this.exprAllowed) { ++this.pos; return this.readRegexp() }\n if (next === 61) { return this.finishOp(types.assign, 2) }\n return this.finishOp(types.slash, 1)\n};\n\npp$9.readToken_mult_modulo_exp = function(code) { // '%*'\n var next = this.input.charCodeAt(this.pos + 1);\n var size = 1;\n var tokentype = code === 42 ? types.star : types.modulo;\n\n // exponentiation operator ** and **=\n if (this.options.ecmaVersion >= 7 && code === 42 && next === 42) {\n ++size;\n tokentype = types.starstar;\n next = this.input.charCodeAt(this.pos + 2);\n }\n\n if (next === 61) { return this.finishOp(types.assign, size + 1) }\n return this.finishOp(tokentype, size)\n};\n\npp$9.readToken_pipe_amp = function(code) { // '|&'\n var next = this.input.charCodeAt(this.pos + 1);\n if (next === code) { return this.finishOp(code === 124 ? types.logicalOR : types.logicalAND, 2) }\n if (next === 61) { return this.finishOp(types.assign, 2) }\n return this.finishOp(code === 124 ? types.bitwiseOR : types.bitwiseAND, 1)\n};\n\npp$9.readToken_caret = function() { // '^'\n var next = this.input.charCodeAt(this.pos + 1);\n if (next === 61) { return this.finishOp(types.assign, 2) }\n return this.finishOp(types.bitwiseXOR, 1)\n};\n\npp$9.readToken_plus_min = function(code) { // '+-'\n var next = this.input.charCodeAt(this.pos + 1);\n if (next === code) {\n if (next === 45 && !this.inModule && this.input.charCodeAt(this.pos + 2) === 62 &&\n (this.lastTokEnd === 0 || lineBreak.test(this.input.slice(this.lastTokEnd, this.pos)))) {\n // A `-->` line comment\n this.skipLineComment(3);\n this.skipSpace();\n return this.nextToken()\n }\n return this.finishOp(types.incDec, 2)\n }\n if (next === 61) { return this.finishOp(types.assign, 2) }\n return this.finishOp(types.plusMin, 1)\n};\n\npp$9.readToken_lt_gt = function(code) { // '<>'\n var next = this.input.charCodeAt(this.pos + 1);\n var size = 1;\n if (next === code) {\n size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2;\n if (this.input.charCodeAt(this.pos + size) === 61) { return this.finishOp(types.assign, size + 1) }\n return this.finishOp(types.bitShift, size)\n }\n if (next === 33 && code === 60 && !this.inModule && this.input.charCodeAt(this.pos + 2) === 45 &&\n this.input.charCodeAt(this.pos + 3) === 45) {\n // `';\n}\n","//Types of elements found in the DOM\nmodule.exports = {\n\tText: \"text\", //Text\n\tDirective: \"directive\", //\n\tComment: \"comment\", //\n\tScript: \"script\", //');\n\n return svgString;\n};\n","/**\n * @fileOverview Convert 2.0 fonts to 3.0 fonts.\n */\n\n/**\n * Given an SVG, replace Scratch 2.0 fonts with new 3.0 fonts. Add defaults where there are none.\n * @param {SVGElement} svgTag The SVG dom object\n * @return {void}\n */\nconst convertFonts = function (svgTag) {\n // Collect all text elements into a list.\n const textElements = [];\n const collectText = domElement => {\n if (domElement.localName === 'text') {\n textElements.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectText(domElement.childNodes[i]);\n }\n };\n collectText(svgTag);\n // If there's an old font-family, switch to the new one.\n for (const textElement of textElements) {\n // If there's no font-family provided, provide one.\n if (!textElement.getAttribute('font-family') ||\n textElement.getAttribute('font-family') === 'Helvetica') {\n textElement.setAttribute('font-family', 'Sans Serif');\n } else if (textElement.getAttribute('font-family') === 'Mystery') {\n textElement.setAttribute('font-family', 'Curly');\n } else if (textElement.getAttribute('font-family') === 'Gloria') {\n textElement.setAttribute('font-family', 'Handwriting');\n } else if (textElement.getAttribute('font-family') === 'Donegal') {\n textElement.setAttribute('font-family', 'Serif');\n }\n }\n};\n\nmodule.exports = convertFonts;\n","/**\n * @fileOverview Import bitmap data into Scratch 3.0, resizing image as necessary.\n */\nconst {FONTS} = require('scratch-render-fonts');\n\n/**\n * Given SVG data, inline the fonts. This allows them to be rendered correctly when set\n * as the source of an HTMLImageElement. Here is a note from tmickel:\n * // Inject fonts that are needed.\n * // It would be nice if there were another way to get the SVG-in-canvas\n * // to render the correct font family, but I couldn't find any other way.\n * // Other things I tried:\n * // Just injecting the font-family into the document: no effect.\n * // External stylesheet linked to by SVG: no effect.\n * // Using a or to link to font-family\n * // injected into the document: no effect.\n * @param {string} svgString The string representation of the svg to modify\n * @return {string} The svg with any needed fonts inlined\n */\nconst inlineSvgFonts = function (svgString) {\n // Make it clear that this function only operates on strings.\n // If we don't explicitly throw this here, the function silently fails.\n if (typeof svgString !== 'string') {\n throw new Error('SVG to be inlined is not a string');\n }\n\n // Collect fonts that need injection.\n const fontsNeeded = new Set();\n const fontRegex = /font-family=\"([^\"]*)\"/g;\n let matches = fontRegex.exec(svgString);\n while (matches) {\n fontsNeeded.add(matches[1]);\n matches = fontRegex.exec(svgString);\n }\n if (fontsNeeded.size > 0) {\n let str = '';\n svgString = svgString.replace(/]*>/, `$&${str}`);\n return svgString;\n }\n return svgString;\n};\n\nmodule.exports = inlineSvgFonts;\n","const SVGRenderer = require('./svg-renderer');\nconst BitmapAdapter = require('./bitmap-adapter');\nconst inlineSvgFonts = require('./font-inliner');\nconst loadSvgString = require('./load-svg-string');\nconst serializeSvgToString = require('./serialize-svg-to-string');\nconst SvgElement = require('./svg-element');\nconst convertFonts = require('./font-converter');\n// /**\n// * Export for NPM & Node.js\n// * @type {RenderWebGL}\n// */\nmodule.exports = {\n BitmapAdapter: BitmapAdapter,\n convertFonts: convertFonts,\n inlineSvgFonts: inlineSvgFonts,\n loadSvgString: loadSvgString,\n serializeSvgToString: serializeSvgToString,\n SvgElement: SvgElement,\n SVGRenderer: SVGRenderer\n};\n","const DOMPurify = require('dompurify');\nconst SvgElement = require('./svg-element');\nconst convertFonts = require('./font-converter');\nconst fixupSvgString = require('./fixup-svg-string');\nconst transformStrokeWidths = require('./transform-applier');\n\n/**\n * @param {SVGElement} svgTag the tag to search within\n * @param {string} [tagName] svg tag to search for (or collect all elements if not given)\n * @return {Array} a list of elements with the given tagname\n */\nconst collectElements = (svgTag, tagName) => {\n const elts = [];\n const collectElementsInner = domElement => {\n if ((domElement.localName === tagName || typeof tagName === 'undefined') && domElement.getAttribute) {\n elts.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectElementsInner(domElement.childNodes[i]);\n }\n };\n collectElementsInner(svgTag);\n return elts;\n};\n\n/**\n * Fix SVGs to comply with SVG spec. Scratch 2 defaults to x2 = 0 when x2 is missing, but\n * SVG defaults to x2 = 1 when missing.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformGradients = svgTag => {\n const linearGradientElements = collectElements(svgTag, 'linearGradient');\n\n // For each gradient element, supply x2 if necessary.\n for (const gradientElement of linearGradientElements) {\n if (!gradientElement.getAttribute('x2')) {\n gradientElement.setAttribute('x2', '0');\n }\n }\n};\n\n/**\n * Fix SVGs to match appearance in Scratch 2, which used nearest neighbor scaling for bitmaps\n * within SVGs.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformImages = svgTag => {\n const imageElements = collectElements(svgTag, 'image');\n\n // For each image element, set image rendering to pixelated\n const pixelatedImages = 'image-rendering: optimizespeed; image-rendering: pixelated;';\n for (const elt of imageElements) {\n if (elt.getAttribute('style')) {\n elt.setAttribute('style',\n `${pixelatedImages} ${elt.getAttribute('style')}`);\n } else {\n elt.setAttribute('style', pixelatedImages);\n }\n }\n};\n\n/**\n * Transforms an SVG's text elements for Scratch 2.0 quirks.\n * These quirks include:\n * 1. `x` and `y` properties are removed/ignored.\n * 2. Alignment is set to `text-before-edge`.\n * 3. Line-breaks are converted to explicit elements.\n * 4. Any required fonts are injected.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformText = svgTag => {\n // Collect all text elements into a list.\n const textElements = [];\n const collectText = domElement => {\n if (domElement.localName === 'text') {\n textElements.push(domElement);\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectText(domElement.childNodes[i]);\n }\n };\n collectText(svgTag);\n convertFonts(svgTag);\n // For each text element, apply quirks.\n for (const textElement of textElements) {\n // Remove x and y attributes - they are not used in Scratch.\n textElement.removeAttribute('x');\n textElement.removeAttribute('y');\n // Set text-before-edge alignment:\n // Scratch renders all text like this.\n textElement.setAttribute('alignment-baseline', 'text-before-edge');\n textElement.setAttribute('xml:space', 'preserve');\n // If there's no font size provided, provide one.\n if (!textElement.getAttribute('font-size')) {\n textElement.setAttribute('font-size', '18');\n }\n let text = textElement.textContent;\n\n // Fix line breaks in text, which are not natively supported by SVG.\n // Only fix if text does not have child tspans.\n // @todo this will not work for font sizes with units such as em, percent\n // However, text made in scratch 2 should only ever export size 22 font.\n const fontSize = parseFloat(textElement.getAttribute('font-size'));\n const tx = 2;\n let ty = 0;\n let spacing = 1.2;\n // Try to match the position and spacing of Scratch 2.0's fonts.\n // Different fonts seem to use different line spacing.\n // Scratch 2 always uses alignment-baseline=text-before-edge\n // However, most SVG readers don't support this attribute\n // or don't support it alongside use of tspan, so the translations\n // here are to make up for that.\n if (textElement.getAttribute('font-family') === 'Handwriting') {\n spacing = 2;\n ty = -11 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Scratch') {\n spacing = 0.89;\n ty = -3 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Curly') {\n spacing = 1.38;\n ty = -6 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Marker') {\n spacing = 1.45;\n ty = -6 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Sans Serif') {\n spacing = 1.13;\n ty = -3 * fontSize / 22;\n } else if (textElement.getAttribute('font-family') === 'Serif') {\n spacing = 1.25;\n ty = -4 * fontSize / 22;\n }\n\n if (textElement.transform.baseVal.numberOfItems === 0) {\n const transform = svgTag.createSVGTransform();\n textElement.transform.baseVal.appendItem(transform);\n }\n\n // Right multiply matrix by a translation of (tx, ty)\n const mtx = textElement.transform.baseVal.getItem(0).matrix;\n mtx.e += (mtx.a * tx) + (mtx.c * ty);\n mtx.f += (mtx.b * tx) + (mtx.d * ty);\n\n if (text && textElement.childElementCount === 0) {\n textElement.textContent = '';\n const lines = text.split('\\n');\n text = '';\n for (const line of lines) {\n const tspanNode = SvgElement.create('tspan');\n tspanNode.setAttribute('x', '0');\n tspanNode.setAttribute('style', 'white-space: pre');\n tspanNode.setAttribute('dy', `${spacing}em`);\n tspanNode.textContent = line ? line : ' ';\n textElement.appendChild(tspanNode);\n }\n }\n }\n};\n\n/**\n * Find the largest stroke width in the svg. If a shape has no\n * `stroke` property, it has a stroke-width of 0. If it has a `stroke`,\n * it is by default a stroke-width of 1.\n * This is used to enlarge the computed bounding box, which doesn't take\n * stroke width into account.\n * @param {SVGSVGElement} rootNode The root SVG node to traverse.\n * @return {number} The largest stroke width in the SVG.\n */\nconst findLargestStrokeWidth = rootNode => {\n let largestStrokeWidth = 0;\n const collectStrokeWidths = domElement => {\n if (domElement.getAttribute) {\n if (domElement.getAttribute('stroke')) {\n largestStrokeWidth = Math.max(largestStrokeWidth, 1);\n }\n if (domElement.getAttribute('stroke-width')) {\n largestStrokeWidth = Math.max(\n largestStrokeWidth,\n Number(domElement.getAttribute('stroke-width')) || 0\n );\n }\n }\n for (let i = 0; i < domElement.childNodes.length; i++) {\n collectStrokeWidths(domElement.childNodes[i]);\n }\n };\n collectStrokeWidths(rootNode);\n return largestStrokeWidth;\n};\n\n/**\n * Transform the measurements of the SVG.\n * In Scratch 2.0, SVGs are drawn without respect to the width,\n * height, and viewBox attribute on the tag. The exporter\n * does output these properties - but they appear to be incorrect often.\n * To address the incorrect measurements, we append the DOM to the\n * document, and then use SVG's native `getBBox` to find the real\n * drawn dimensions. This ensures things drawn in negative dimensions,\n * outside the given viewBox, etc., are all eventually drawn to the canvas.\n * I tried to do this several other ways: stripping the width/height/viewBox\n * attributes and then drawing (Firefox won't draw anything),\n * or inflating them and then measuring a canvas. But this seems to be\n * a natural and performant way.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst transformMeasurements = svgTag => {\n // Append the SVG dom to the document.\n // This allows us to use `getBBox` on the page,\n // which returns the full bounding-box of all drawn SVG\n // elements, similar to how Scratch 2.0 did measurement.\n const svgSpot = document.createElement('span');\n // Since we're adding user-provided SVG to document.body,\n // sanitizing is required. This should not affect bounding box calculation.\n // outerHTML is attribute of Element (and not HTMLElement), so use it instead of\n // calling serializer or toString()\n // NOTE: svgTag remains untouched!\n const rawValue = svgTag.outerHTML;\n const sanitizedValue = DOMPurify.sanitize(rawValue, {\n // Use SVG profile (no HTML elements)\n USE_PROFILES: {svg: true},\n // Remove some tags that Scratch does not use.\n FORBID_TAGS: ['a', 'audio', 'canvas', 'video'],\n // Allow data URI in image tags (e.g. SVGs converted from bitmap)\n ADD_DATA_URI_TAGS: ['image']\n });\n let bbox;\n try {\n // Insert sanitized value.\n svgSpot.innerHTML = sanitizedValue;\n document.body.appendChild(svgSpot);\n // Take the bounding box. We have to get elements via svgSpot\n // because we added it via innerHTML.\n bbox = svgSpot.children[0].getBBox();\n } finally {\n // Always destroy the element, even if, for example, getBBox throws.\n document.body.removeChild(svgSpot);\n }\n\n // Enlarge the bbox from the largest found stroke width\n // This may have false-positives, but at least the bbox will always\n // contain the full graphic including strokes.\n // If the width or height is zero however, don't enlarge since\n // they won't have a stroke width that needs to be enlarged.\n let halfStrokeWidth;\n if (bbox.width === 0 || bbox.height === 0) {\n halfStrokeWidth = 0;\n } else {\n halfStrokeWidth = findLargestStrokeWidth(svgTag) / 2;\n }\n const width = bbox.width + (halfStrokeWidth * 2);\n const height = bbox.height + (halfStrokeWidth * 2);\n const x = bbox.x - halfStrokeWidth;\n const y = bbox.y - halfStrokeWidth;\n\n // Set the correct measurements on the SVG tag\n svgTag.setAttribute('width', width);\n svgTag.setAttribute('height', height);\n svgTag.setAttribute('viewBox',\n `${x} ${y} ${width} ${height}`);\n};\n\n/**\n * Find all instances of a URL-referenced `stroke` in the svg. In 2.0, all gradient strokes\n * have a round `stroke-linejoin` and `stroke-linecap`... for some reason.\n * @param {SVGSVGElement} svgTag the SVG tag to apply the transformation to\n */\nconst setGradientStrokeRoundedness = svgTag => {\n const elements = collectElements(svgTag);\n\n for (const elt of elements) {\n if (!elt.style) continue;\n const stroke = elt.style.stroke || elt.getAttribute('stroke');\n if (stroke && stroke.match(/^url\\(#.*\\)$/)) {\n elt.style['stroke-linejoin'] = 'round';\n elt.style['stroke-linecap'] = 'round';\n }\n }\n};\n\n/**\n * In-place, convert passed SVG to something consistent that will be rendered the way we want them to be.\n * @param {SVGSvgElement} svgTag root SVG node to operate upon\n * @param {boolean} [fromVersion2] True if we should perform conversion from version 2 to version 3 svg.\n */\nconst normalizeSvg = (svgTag, fromVersion2) => {\n if (fromVersion2) {\n // Fix gradients. Scratch 2 exports no x2 when x2 = 0, but\n // SVG default is that x2 is 1. This must be done before\n // transformStrokeWidths since transformStrokeWidths affects\n // gradients.\n transformGradients(svgTag);\n }\n transformStrokeWidths(svgTag, window);\n transformImages(svgTag);\n if (fromVersion2) {\n // Transform all text elements.\n transformText(svgTag);\n // Transform measurements.\n transformMeasurements(svgTag);\n // Fix stroke roundedness.\n setGradientStrokeRoundedness(svgTag);\n } else if (!svgTag.getAttribute('viewBox')) {\n // Renderer expects a view box.\n transformMeasurements(svgTag);\n } else if (!svgTag.getAttribute('width') || !svgTag.getAttribute('height')) {\n svgTag.setAttribute('width', svgTag.viewBox.baseVal.width);\n svgTag.setAttribute('height', svgTag.viewBox.baseVal.height);\n }\n};\n\n/**\n * Load an SVG string and normalize it. All the steps before drawing/measuring.\n * Currently, this will normalize stroke widths (see transform-applier.js) and render all embedded images pixelated.\n * The returned SVG will be guaranteed to always have a `width`, `height` and `viewBox`.\n * In addition, if the `fromVersion2` parameter is `true`, several \"quirks-mode\" transformations will be applied which\n * mimic Scratch 2.0's SVG rendering.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {boolean} [fromVersion2] True if we should perform conversion from version 2 to version 3 svg.\n * @return {SVGSVGElement} The normalized SVG element.\n */\nconst loadSvgString = (svgString, fromVersion2) => {\n // Parse string into SVG XML.\n const parser = new DOMParser();\n svgString = fixupSvgString(svgString);\n const svgDom = parser.parseFromString(svgString, 'text/xml');\n if (svgDom.childNodes.length < 1 ||\n svgDom.documentElement.localName !== 'svg') {\n throw new Error('Document does not appear to be SVG.');\n }\n const svgTag = svgDom.documentElement;\n normalizeSvg(svgTag, fromVersion2);\n return svgTag;\n};\n\nmodule.exports = loadSvgString;\n","const inlineSvgFonts = require('./font-inliner');\n\n/**\n * Serialize a given SVG DOM to a string.\n * @param {SVGSVGElement} svgTag The SVG element to serialize.\n * @param {?boolean} shouldInjectFonts True if fonts should be included in the SVG as\n * base64 data.\n * @returns {string} String representing current SVG data.\n */\nconst serializeSvgToString = (svgTag, shouldInjectFonts) => {\n const serializer = new XMLSerializer();\n let string = serializer.serializeToString(svgTag);\n if (shouldInjectFonts) {\n string = inlineSvgFonts(string);\n }\n return string;\n};\n\nmodule.exports = serializeSvgToString;\n","/* Adapted from\n * Paper.js - The Swiss Army Knife of Vector Graphics Scripting.\n * http://paperjs.org/\n *\n * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey\n * http://scratchdisk.com/ & http://jonathanpuckey.com/\n *\n * Distributed under the MIT license. See LICENSE file for details.\n *\n * All rights reserved.\n */\n\n/**\n * @name SvgElement\n * @namespace\n * @private\n */\nclass SvgElement {\n // SVG related namespaces\n static get svg () {\n return 'http://www.w3.org/2000/svg';\n }\n static get xmlns () {\n return 'http://www.w3.org/2000/xmlns';\n }\n static get xlink () {\n return 'http://www.w3.org/1999/xlink';\n }\n\n // Mapping of attribute names to required namespaces:\n static attributeNamespace () {\n return {\n 'href': SvgElement.xlink,\n 'xlink': SvgElement.xmlns,\n // Only the xmlns attribute needs the trailing slash. See #984\n 'xmlns': `${SvgElement.xmlns}/`,\n // IE needs the xmlns namespace when setting 'xmlns:xlink'. See #984\n 'xmlns:xlink': `${SvgElement.xmlns}/`\n };\n }\n\n static create (tag, attributes, formatter) {\n return SvgElement.set(document.createElementNS(SvgElement.svg, tag), attributes, formatter);\n }\n\n static get (node, name) {\n const namespace = SvgElement.attributeNamespace[name];\n const value = namespace ?\n node.getAttributeNS(namespace, name) :\n node.getAttribute(name);\n return value === 'null' ? null : value;\n }\n\n static set (node, attributes, formatter) {\n for (const name in attributes) {\n let value = attributes[name];\n const namespace = SvgElement.attributeNamespace[name];\n if (typeof value === 'number' && formatter) {\n value = formatter.number(value);\n }\n if (namespace) {\n node.setAttributeNS(namespace, name, value);\n } else {\n node.setAttribute(name, value);\n }\n }\n return node;\n }\n}\n\nmodule.exports = SvgElement;\n","const loadSvgString = require('./load-svg-string');\nconst serializeSvgToString = require('./serialize-svg-to-string');\n\n/**\n * Main quirks-mode SVG rendering code.\n * @deprecated Call into individual methods exported from this library instead.\n */\nclass SvgRenderer {\n /**\n * Create a quirks-mode SVG renderer for a particular canvas.\n * @param {HTMLCanvasElement} [canvas] An optional canvas element to draw to. If this is not provided, the renderer\n * will create a new canvas.\n * @constructor\n */\n constructor (canvas) {\n /**\n * The canvas that this SVG renderer will render to.\n * @type {HTMLCanvasElement}\n * @private\n */\n this._canvas = canvas || document.createElement('canvas');\n this._context = this._canvas.getContext('2d');\n\n /**\n * A measured SVG \"viewbox\"\n * @typedef {object} SvgRenderer#SvgMeasurements\n * @property {number} x - The left edge of the SVG viewbox.\n * @property {number} y - The top edge of the SVG viewbox.\n * @property {number} width - The width of the SVG viewbox.\n * @property {number} height - The height of the SVG viewbox.\n */\n\n /**\n * The measurement box of the currently loaded SVG.\n * @type {SvgRenderer#SvgMeasurements}\n * @private\n */\n this._measurements = {x: 0, y: 0, width: 0, height: 0};\n\n /**\n * The `` element with the contents of the currently loaded SVG.\n * @type {?HTMLImageElement}\n * @private\n */\n this._cachedImage = null;\n\n /**\n * True if this renderer's current SVG is loaded and can be rendered to the canvas.\n * @type {boolean}\n */\n this.loaded = false;\n }\n\n /**\n * @returns {!HTMLCanvasElement} this renderer's target canvas.\n */\n get canvas () {\n return this._canvas;\n }\n\n /**\n * @return {Array} the natural size, in Scratch units, of this SVG.\n */\n get size () {\n return [this._measurements.width, this._measurements.height];\n }\n\n /**\n * @return {Array} the offset (upper left corner) of the SVG's view box.\n */\n get viewOffset () {\n return [this._measurements.x, this._measurements.y];\n }\n\n /**\n * Load an SVG string and normalize it. All the steps before drawing/measuring.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {?boolean} fromVersion2 True if we should perform conversion from\n * version 2 to version 3 svg.\n */\n loadString (svgString, fromVersion2) {\n // New svg string invalidates the cached image\n this._cachedImage = null;\n const svgTag = loadSvgString(svgString, fromVersion2);\n\n this._svgTag = svgTag;\n this._measurements = {\n width: svgTag.viewBox.baseVal.width,\n height: svgTag.viewBox.baseVal.height,\n x: svgTag.viewBox.baseVal.x,\n y: svgTag.viewBox.baseVal.y\n };\n }\n\n /**\n * Load an SVG string, normalize it, and prepare it for (synchronous) rendering.\n * @param {!string} svgString String of SVG data to draw in quirks-mode.\n * @param {?boolean} fromVersion2 True if we should perform conversion from version 2 to version 3 svg.\n * @param {Function} [onFinish] - An optional callback to call when the SVG is loaded and can be rendered.\n */\n loadSVG (svgString, fromVersion2, onFinish) {\n this.loadString(svgString, fromVersion2);\n this._createSVGImage(onFinish);\n }\n\n /**\n * Creates an element for the currently loaded SVG string, then calls the callback once it's loaded.\n * @param {Function} [onFinish] - An optional callback to call when the has loaded.\n */\n _createSVGImage (onFinish) {\n if (this._cachedImage === null) this._cachedImage = new Image();\n const img = this._cachedImage;\n\n img.onload = () => {\n this.loaded = true;\n if (onFinish) onFinish();\n };\n const svgText = this.toString(true /* shouldInjectFonts */);\n img.src = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;\n this.loaded = false;\n }\n\n /**\n * Serialize the active SVG DOM to a string.\n * @param {?boolean} shouldInjectFonts True if fonts should be included in the SVG as\n * base64 data.\n * @returns {string} String representing current SVG data.\n * @deprecated Use the standalone `serializeSvgToString` export instead.\n */\n toString (shouldInjectFonts) {\n return serializeSvgToString(this._svgTag, shouldInjectFonts);\n }\n\n /**\n * Synchronously draw the loaded SVG to this renderer's `canvas`.\n * @param {number} [scale] - Optionally, also scale the image by this factor.\n */\n draw (scale) {\n if (!this.loaded) throw new Error('SVG image has not finished loading');\n this._drawFromImage(scale);\n }\n\n /**\n * Draw to the canvas from a loaded image element.\n * @param {number} [scale] - Optionally, also scale the image by this factor.\n **/\n _drawFromImage (scale) {\n if (this._cachedImage === null) return;\n\n const ratio = Number.isFinite(scale) ? scale : 1;\n const bbox = this._measurements;\n this._canvas.width = bbox.width * ratio;\n this._canvas.height = bbox.height * ratio;\n // Even if the canvas at the current scale has a nonzero size, the image's dimensions are floored pre-scaling.\n // e.g. if an image has a width of 0.4 and is being rendered at 3x scale, the canvas will have a width of 1, but\n // the image's width will be rounded down to 0 on some browsers (Firefox) prior to being drawn at that scale.\n if (\n this._canvas.width <= 0 ||\n this._canvas.height <= 0 ||\n this._cachedImage.naturalWidth <= 0 ||\n this._cachedImage.naturalHeight <= 0\n ) return;\n this._context.clearRect(0, 0, this._canvas.width, this._canvas.height);\n this._context.setTransform(ratio, 0, 0, ratio, 0, 0);\n this._context.drawImage(this._cachedImage, 0, 0);\n }\n}\n\nmodule.exports = SvgRenderer;\n","const Matrix = require('transformation-matrix');\nconst SvgElement = require('./svg-element');\nconst log = require('./util/log');\n\n/**\n * @fileOverview Apply transforms to match stroke width appearance in 2.0 and 3.0\n */\n\n// Adapted from paper.js's Path.applyTransform\nconst _parseTransform = function (domElement) {\n let matrix = Matrix.identity();\n const string = domElement.attributes && domElement.attributes.transform && domElement.attributes.transform.value;\n if (!string) return matrix;\n // https://www.w3.org/TR/SVG/types.html#DataTypeTransformList\n // Parse SVG transform string. First we split at /)\\s*/, to separate\n // commands\n const transforms = string.split(/\\)\\s*/g);\n for (const transform of transforms) {\n if (!transform) break;\n // Command come before the '(', values after\n const parts = transform.split(/\\(\\s*/);\n const command = parts[0].trim();\n const v = parts[1].split(/[\\s,]+/g);\n // Convert values to floats\n for (let j = 0; j < v.length; j++) {\n v[j] = parseFloat(v[j]);\n }\n switch (command) {\n case 'matrix':\n matrix = Matrix.compose(matrix, {a: v[0], b: v[1], c: v[2], d: v[3], e: v[4], f: v[5]});\n break;\n case 'rotate':\n matrix = Matrix.compose(matrix, Matrix.rotateDEG(v[0], v[1] || 0, v[2] || 0));\n break;\n case 'translate':\n matrix = Matrix.compose(matrix, Matrix.translate(v[0], v[1] || 0));\n break;\n case 'scale':\n matrix = Matrix.compose(matrix, Matrix.scale(v[0], v[1] || v[0]));\n break;\n case 'skewX':\n matrix = Matrix.compose(matrix, Matrix.skewDEG(v[0], 0));\n break;\n case 'skewY':\n matrix = Matrix.compose(matrix, Matrix.skewDEG(0, v[0]));\n break;\n default:\n log.error(`Couldn't parse: ${command}`);\n }\n }\n return matrix;\n};\n\n// Adapted from paper.js's Matrix.decompose\n// Given a matrix, return the x and y scale factors of the matrix\nconst _getScaleFactor = function (matrix) {\n const a = matrix.a;\n const b = matrix.b;\n const c = matrix.c;\n const d = matrix.d;\n const det = (a * d) - (b * c);\n\n if (a !== 0 || b !== 0) {\n const r = Math.sqrt((a * a) + (b * b));\n return {x: r, y: det / r};\n }\n if (c !== 0 || d !== 0) {\n const s = Math.sqrt((c * c) + (d * d));\n return {x: det / s, y: s};\n }\n // a = b = c = d = 0\n return {x: 0, y: 0};\n};\n\n// Returns null if matrix is not invertible. Otherwise returns given ellipse\n// transformed by transform, an object {radiusX, radiusY, rotation}.\nconst _calculateTransformedEllipse = function (radiusX, radiusY, theta, transform) {\n theta = -theta * Math.PI / 180;\n const a = transform.a;\n const b = -transform.c;\n const c = -transform.b;\n const d = transform.d;\n // Since other parameters determine the translation of the ellipse in SVG, we do not need to worry\n // about what e and f are.\n const det = (a * d) - (b * c);\n // Non-invertible matrix\n if (det === 0) return null;\n\n // rotA, rotB, and rotC represent Ax^2 + Bxy + Cy^2 = 1 coefficients for a rotated ellipse formula\n const sinT = Math.sin(theta);\n const cosT = Math.cos(theta);\n const sin2T = Math.sin(2 * theta);\n const rotA = (cosT * cosT / radiusX / radiusX) + (sinT * sinT / radiusY / radiusY);\n const rotB = (sin2T / radiusX / radiusX) - (sin2T / radiusY / radiusY);\n const rotC = (sinT * sinT / radiusX / radiusX) + (cosT * cosT / radiusY / radiusY);\n\n // Calculate the ellipse formula of the transformed ellipse\n // A, B, and C represent Ax^2 + Bxy + Cy^2 = 1 / det / det coefficients in a transformed ellipse formula\n // scaled by inverse det squared (to preserve accuracy)\n const A = ((rotA * d * d) - (rotB * d * c) + (rotC * c * c));\n const B = ((-2 * rotA * b * d) + (rotB * a * d) + (rotB * b * c) - (2 * rotC * a * c));\n const C = ((rotA * b * b) - (rotB * a * b) + (rotC * a * a));\n\n // Derive new radii and theta from the transformed ellipse formula\n const newRadiusXOverDet = Math.sqrt(2) *\n Math.sqrt(\n (A + C - Math.sqrt((A * A) + (B * B) - (2 * A * C) + (C * C))) /\n ((-B * B) + (4 * A * C))\n );\n const newRadiusYOverDet = 1 / Math.sqrt(A + C - (1 / newRadiusXOverDet / newRadiusXOverDet));\n let temp = (A - (1 / newRadiusXOverDet / newRadiusXOverDet)) /\n ((1 / newRadiusYOverDet / newRadiusYOverDet) - (1 / newRadiusXOverDet / newRadiusXOverDet));\n if (temp < 0 && Math.abs(temp) < 1e-8) temp = 0; // Fix floating point issue\n temp = Math.sqrt(temp);\n if (Math.abs(1 - temp) < 1e-8) temp = 1; // Fix floating point issue\n // Solve for which of the two possible thetas is correct\n let newTheta = Math.asin(temp);\n temp = (B / (\n (1 / newRadiusXOverDet / newRadiusXOverDet) -\n (1 / newRadiusYOverDet / newRadiusYOverDet)));\n const newTheta2 = -newTheta;\n if (Math.abs(Math.sin(2 * newTheta2) - temp) <\n Math.abs(Math.sin(2 * newTheta) - temp)) {\n newTheta = newTheta2;\n }\n\n return {\n radiusX: newRadiusXOverDet * det,\n radiusY: newRadiusYOverDet * det,\n rotation: -newTheta * 180 / Math.PI\n };\n};\n\n// Adapted from paper.js's PathItem.setPathData\nconst _transformPath = function (pathString, transform) {\n if (!transform || Matrix.toString(transform) === Matrix.toString(Matrix.identity())) return pathString;\n // First split the path data into parts of command-coordinates pairs\n // Commands are any of these characters: mzlhvcsqta\n const parts = pathString && pathString.match(/[mlhvcsqtaz][^mlhvcsqtaz]*/ig);\n let coords;\n let relative = false;\n let previous;\n let control;\n let current = {x: 0, y: 0};\n let start = {x: 0, y: 0};\n let result = '';\n\n const getCoord = function (index, coord) {\n let val = +coords[index];\n if (relative) {\n val += current[coord];\n }\n return val;\n };\n\n const getPoint = function (index) {\n return {x: getCoord(index, 'x'), y: getCoord(index + 1, 'y')};\n };\n\n const roundTo4Places = function (num) {\n return Number(num.toFixed(4));\n };\n\n // Returns the transformed point as a string\n const getString = function (point) {\n const transformed = Matrix.applyToPoint(transform, point);\n return `${roundTo4Places(transformed.x)} ${roundTo4Places(transformed.y)} `;\n };\n\n for (let i = 0, l = parts && parts.length; i < l; i++) {\n const part = parts[i];\n const command = part[0];\n const lower = command.toLowerCase();\n // Match all coordinate values\n coords = part.match(/[+-]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][+-]?\\d+)?/g);\n const length = coords && coords.length;\n relative = command === lower;\n // Fix issues with z in the middle of SVG path data, not followed by\n // a m command, see paper.js#413:\n if (previous === 'z' && !/[mz]/.test(lower)) {\n result += `M ${current.x} ${current.y} `;\n }\n switch (lower) {\n case 'm': // Move to\n case 'l': // Line to\n {\n let move = lower === 'm';\n for (let j = 0; j < length; j += 2) {\n result += move ? 'M ' : 'L ';\n current = getPoint(j);\n result += getString(current);\n if (move) {\n start = current;\n move = false;\n }\n }\n control = current;\n break;\n }\n case 'h': // Horizontal line\n case 'v': // Vertical line\n {\n const coord = lower === 'h' ? 'x' : 'y';\n current = {x: current.x, y: current.y}; // Clone as we're going to modify it.\n for (let j = 0; j < length; j++) {\n current[coord] = getCoord(j, coord);\n result += `L ${getString(current)}`;\n }\n control = current;\n break;\n }\n case 'c':\n // Cubic Bezier curve\n for (let j = 0; j < length; j += 6) {\n const handle1 = getPoint(j);\n control = getPoint(j + 2);\n current = getPoint(j + 4);\n result += `C ${getString(handle1)}${getString(control)}${getString(current)}`;\n }\n break;\n case 's':\n // Smooth cubic Bezier curve\n for (let j = 0; j < length; j += 4) {\n const handle1 = /[cs]/.test(previous) ?\n {x: (current.x * 2) - control.x, y: (current.y * 2) - control.y} :\n current;\n control = getPoint(j);\n current = getPoint(j + 2);\n\n result += `C ${getString(handle1)}${getString(control)}${getString(current)}`;\n previous = lower;\n }\n break;\n case 'q':\n // Quadratic Bezier curve\n for (let j = 0; j < length; j += 4) {\n control = getPoint(j);\n current = getPoint(j + 2);\n result += `Q ${getString(control)}${getString(current)}`;\n }\n break;\n case 't':\n // Smooth quadratic Bezier curve\n for (let j = 0; j < length; j += 2) {\n control = /[qt]/.test(previous) ?\n {x: (current.x * 2) - control.x, y: (current.y * 2) - control.y} :\n current;\n current = getPoint(j);\n\n result += `Q ${getString(control)}${getString(current)}`;\n previous = lower;\n }\n break;\n case 'a':\n // Elliptical arc curve\n for (let j = 0; j < length; j += 7) {\n current = getPoint(j + 5);\n const rx = +coords[j];\n const ry = +coords[j + 1];\n const rotation = +coords[j + 2];\n const largeArcFlag = +coords[j + 3];\n let clockwiseFlag = +coords[j + 4];\n const newEllipse = _calculateTransformedEllipse(rx, ry, rotation, transform);\n const matrixScale = _getScaleFactor(transform);\n if (newEllipse) {\n if ((matrixScale.x > 0 && matrixScale.y < 0) ||\n (matrixScale.x < 0 && matrixScale.y > 0)) {\n clockwiseFlag = clockwiseFlag ^ 1;\n }\n result += `A ${roundTo4Places(Math.abs(newEllipse.radiusX))} ` +\n `${roundTo4Places(Math.abs(newEllipse.radiusY))} ` +\n `${roundTo4Places(newEllipse.rotation)} ${largeArcFlag} ` +\n `${clockwiseFlag} ${getString(current)}`;\n } else {\n result += `L ${getString(current)}`;\n }\n }\n break;\n case 'z':\n // Close path\n result += `Z `;\n // Correctly handle relative m commands, see paper.js#1101:\n current = start;\n break;\n }\n previous = lower;\n }\n return result;\n};\n\nconst GRAPHICS_ELEMENTS = ['circle', 'ellipse', 'image', 'line', 'path', 'polygon', 'polyline', 'rect', 'text', 'use'];\nconst CONTAINER_ELEMENTS = ['a', 'defs', 'g', 'marker', 'glyph', 'missing-glyph', 'pattern', 'svg', 'switch', 'symbol'];\nconst _isContainerElement = function (element) {\n return element.tagName && CONTAINER_ELEMENTS.includes(element.tagName.toLowerCase());\n};\nconst _isGraphicsElement = function (element) {\n return element.tagName && GRAPHICS_ELEMENTS.includes(element.tagName.toLowerCase());\n};\nconst _isPathWithTransformAndStroke = function (element, strokeWidth) {\n if (!element.attributes) return false;\n strokeWidth = element.attributes['stroke-width'] ?\n Number(element.attributes['stroke-width'].value) : Number(strokeWidth);\n return strokeWidth &&\n element.tagName && element.tagName.toLowerCase() === 'path' &&\n element.attributes.d && element.attributes.d.value;\n};\nconst _quadraticMean = function (a, b) {\n return Math.sqrt(((a * a) + (b * b)) / 2);\n};\n\nconst _createGradient = function (gradientId, svgTag, bbox, matrix) {\n // Adapted from Paper.js's SvgImport.getValue\n const getValue = function (node, name, isString, allowNull, allowPercent, defaultValue) {\n // Interpret value as number. Never return NaN, but 0 instead.\n // If the value is a sequence of numbers, parseFloat will\n // return the first occurring number, which is enough for now.\n let value = SvgElement.get(node, name);\n let res;\n if (value === null) {\n if (defaultValue) {\n res = defaultValue;\n if (/%\\s*$/.test(res)) {\n value = defaultValue;\n res = parseFloat(value);\n }\n } else if (allowNull) {\n res = null;\n } else if (isString) {\n res = '';\n } else {\n res = 0;\n }\n } else if (isString) {\n res = value;\n } else {\n res = parseFloat(value);\n }\n // Support for dimensions in percentage of the root size. If root-size\n // is not set (e.g. during ), just scale the percentage value to\n // 0..1, as required by gradients with gradientUnits=\"objectBoundingBox\"\n if (/%\\s*$/.test(value)) {\n const size = allowPercent ? 1 : bbox[/x|^width/.test(name) ? 'width' : 'height'];\n return res / 100 * size;\n }\n return res;\n };\n const getPoint = function (node, x, y, allowNull, allowPercent, defaultX, defaultY) {\n x = getValue(node, x || 'x', false, allowNull, allowPercent, defaultX);\n y = getValue(node, y || 'y', false, allowNull, allowPercent, defaultY);\n return allowNull && (x === null || y === null) ? null : {x, y};\n };\n\n let defs = svgTag.getElementsByTagName('defs');\n if (defs.length === 0) {\n defs = SvgElement.create('defs');\n svgTag.appendChild(defs);\n } else {\n defs = defs[0];\n }\n\n // Clone the old gradient. We'll make a new one, since the gradient might be reused elsewhere\n // with different transform matrix\n const oldGradient = svgTag.getElementById(gradientId);\n if (!oldGradient) return;\n\n const radial = oldGradient.tagName.toLowerCase() === 'radialgradient';\n const newGradient = svgTag.getElementById(gradientId).cloneNode(true /* deep */);\n\n // Give the new gradient a new ID\n let matrixString = Matrix.toString(matrix);\n matrixString = matrixString.substring(8, matrixString.length - 1);\n const newGradientId = `${gradientId}-${matrixString}`;\n newGradient.setAttribute('id', newGradientId);\n\n // This gradient already exists and was transformed before. Just reuse the already-transformed one.\n if (svgTag.getElementById(newGradientId)) {\n // This is the same code as in the end of the function, but I don't feel like wrapping the next 80 lines\n // in an `if (!svgTag.getElementById(newGradientId))` block\n return `url(#${newGradientId})`;\n }\n\n const scaleToBounds = getValue(newGradient, 'gradientUnits', true) !==\n 'userSpaceOnUse';\n let origin;\n let destination;\n let radius;\n let focal;\n if (radial) {\n origin = getPoint(newGradient, 'cx', 'cy', false, scaleToBounds, '50%', '50%');\n radius = getValue(newGradient, 'r', false, false, scaleToBounds, '50%');\n focal = getPoint(newGradient, 'fx', 'fy', true, scaleToBounds);\n } else {\n origin = getPoint(newGradient, 'x1', 'y1', false, scaleToBounds);\n destination = getPoint(newGradient, 'x2', 'y2', false, scaleToBounds, '1');\n if (origin.x === destination.x && origin.y === destination.y) {\n // If it's degenerate, use the color of the last stop, as described by\n // https://www.w3.org/TR/SVG/pservers.html#LinearGradientNotes\n const stops = newGradient.getElementsByTagName('stop');\n if (!stops.length || !stops[stops.length - 1].attributes ||\n !stops[stops.length - 1].attributes['stop-color']) {\n return null;\n }\n return stops[stops.length - 1].attributes['stop-color'].value;\n }\n }\n\n // Transform points\n // Emulate SVG's gradientUnits=\"objectBoundingBox\"\n if (scaleToBounds) {\n const boundsMatrix = Matrix.compose(Matrix.translate(bbox.x, bbox.y), Matrix.scale(bbox.width, bbox.height));\n origin = Matrix.applyToPoint(boundsMatrix, origin);\n if (destination) destination = Matrix.applyToPoint(boundsMatrix, destination);\n if (radius) {\n radius = _quadraticMean(bbox.width, bbox.height) * radius;\n }\n if (focal) focal = Matrix.applyToPoint(boundsMatrix, focal);\n }\n\n if (radial) {\n origin = Matrix.applyToPoint(matrix, origin);\n const matrixScale = _getScaleFactor(matrix);\n radius = _quadraticMean(matrixScale.x, matrixScale.y) * radius;\n if (focal) focal = Matrix.applyToPoint(matrix, focal);\n } else {\n const dot = (a, b) => (a.x * b.x) + (a.y * b.y);\n const multiply = (coefficient, v) => ({x: coefficient * v.x, y: coefficient * v.y});\n const add = (a, b) => ({x: a.x + b.x, y: a.y + b.y});\n const subtract = (a, b) => ({x: a.x - b.x, y: a.y - b.y});\n\n // The line through origin and gradientPerpendicular is the line at which the gradient starts\n let gradientPerpendicular = Math.abs(origin.x - destination.x) < 1e-8 ?\n add(origin, {x: 1, y: (origin.x - destination.x) / (destination.y - origin.y)}) :\n add(origin, {x: (destination.y - origin.y) / (origin.x - destination.x), y: 1});\n\n // Transform points\n gradientPerpendicular = Matrix.applyToPoint(matrix, gradientPerpendicular);\n origin = Matrix.applyToPoint(matrix, origin);\n destination = Matrix.applyToPoint(matrix, destination);\n\n // Calculate the direction that the gradient has changed to\n const originToPerpendicular = subtract(gradientPerpendicular, origin);\n const originToDestination = subtract(destination, origin);\n const gradientDirection = Math.abs(originToPerpendicular.x) < 1e-8 ?\n {x: 1, y: -originToPerpendicular.x / originToPerpendicular.y} :\n {x: -originToPerpendicular.y / originToPerpendicular.x, y: 1};\n\n // Set the destination so that the gradient moves in the correct direction, by projecting the destination vector\n // onto the gradient direction vector\n const projectionCoeff = dot(originToDestination, gradientDirection) / dot(gradientDirection, gradientDirection);\n const projection = multiply(projectionCoeff, gradientDirection);\n destination = {x: origin.x + projection.x, y: origin.y + projection.y};\n }\n\n // Put values back into svg\n if (radial) {\n newGradient.setAttribute('cx', Number(origin.x.toFixed(4)));\n newGradient.setAttribute('cy', Number(origin.y.toFixed(4)));\n newGradient.setAttribute('r', Number(radius.toFixed(4)));\n if (focal) {\n newGradient.setAttribute('fx', Number(focal.x.toFixed(4)));\n newGradient.setAttribute('fy', Number(focal.y.toFixed(4)));\n }\n } else {\n newGradient.setAttribute('x1', Number(origin.x.toFixed(4)));\n newGradient.setAttribute('y1', Number(origin.y.toFixed(4)));\n newGradient.setAttribute('x2', Number(destination.x.toFixed(4)));\n newGradient.setAttribute('y2', Number(destination.y.toFixed(4)));\n }\n newGradient.setAttribute('gradientUnits', 'userSpaceOnUse');\n defs.appendChild(newGradient);\n\n return `url(#${newGradientId})`;\n};\n\n// Adapted from paper.js's SvgImport.getDefinition\nconst _parseUrl = (value, windowRef) => {\n // When url() comes from a style property, '#'' seems to be missing on\n // WebKit. We also get variations of quotes or no quotes, single or\n // double, so handle it all with one regular expression:\n const match = value && value.match(/\\((?:[\"'#]*)([^\"')]+)/);\n const name = match && match[1];\n const res = name && windowRef ?\n // This is required by Firefox, which can produce absolute\n // urls for local gradients, see paperjs#1001:\n name.replace(`${windowRef.location.href.split('#')[0]}#`, '') :\n name;\n return res;\n};\n\n/**\n * Scratch 2.0 displays stroke widths in a \"normalized\" way, that is,\n * if a shape with a stroke width has a transform applied, it will be\n * rendered with a stroke that is the same width all the way around,\n * instead of stretched looking.\n *\n * The vector paint editor also prefers to normalize the stroke width,\n * rather than keep track of transforms at the group level, as this\n * simplifies editing (e.g. stroke width 3 always means the same thickness)\n *\n * This function performs that normalization process, pushing transforms\n * on groups down to the leaf level and averaging out the stroke width\n * around the shapes. Note that this doens't just change stroke widths, it\n * changes path data and attributes throughout the SVG.\n *\n * @param {SVGElement} svgTag The SVG dom object\n * @param {Window} windowRef The window to use. Need to pass in for\n * tests to work, as they get angry at even the mention of window.\n * @param {object} bboxForTesting The bounds to use. Need to pass in for\n * tests only, because getBBox doesn't work in Node. This should\n * be the bounds of the svgTag without including stroke width or transforms.\n * @return {void}\n */\nconst transformStrokeWidths = function (svgTag, windowRef, bboxForTesting) {\n const inherited = Matrix.identity();\n\n const applyTransforms = (element, matrix, strokeWidth, fill, stroke) => {\n if (_isContainerElement(element)) {\n // Push fills and stroke width down to leaves\n if (element.attributes['stroke-width']) {\n strokeWidth = element.attributes['stroke-width'].value;\n }\n if (element.attributes) {\n if (element.attributes.fill) fill = element.attributes.fill.value;\n if (element.attributes.stroke) stroke = element.attributes.stroke.value;\n }\n\n // If any child nodes don't take attributes, leave the attributes\n // at the parent level.\n for (let i = 0; i < element.childNodes.length; i++) {\n applyTransforms(\n element.childNodes[i],\n Matrix.compose(matrix, _parseTransform(element)),\n strokeWidth,\n fill,\n stroke\n );\n }\n element.removeAttribute('transform');\n element.removeAttribute('stroke-width');\n element.removeAttribute('fill');\n element.removeAttribute('stroke');\n } else if (_isPathWithTransformAndStroke(element, strokeWidth)) {\n if (element.attributes['stroke-width']) {\n strokeWidth = element.attributes['stroke-width'].value;\n }\n if (element.attributes.fill) fill = element.attributes.fill.value;\n if (element.attributes.stroke) stroke = element.attributes.stroke.value;\n matrix = Matrix.compose(matrix, _parseTransform(element));\n if (Matrix.toString(matrix) === Matrix.toString(Matrix.identity())) {\n element.removeAttribute('transform');\n element.setAttribute('stroke-width', strokeWidth);\n if (fill) element.setAttribute('fill', fill);\n if (stroke) element.setAttribute('stroke', stroke);\n return;\n }\n\n // Transform gradient\n const fillGradientId = _parseUrl(fill, windowRef);\n const strokeGradientId = _parseUrl(stroke, windowRef);\n\n if (fillGradientId || strokeGradientId) {\n const doc = windowRef.document;\n // Need path bounds to transform gradient\n const svgSpot = doc.createElement('span');\n let bbox;\n if (bboxForTesting) {\n bbox = bboxForTesting;\n } else {\n try {\n doc.body.appendChild(svgSpot);\n const svg = SvgElement.set(doc.createElementNS(SvgElement.svg, 'svg'));\n const path = SvgElement.set(doc.createElementNS(SvgElement.svg, 'path'));\n path.setAttribute('d', element.attributes.d.value);\n svg.appendChild(path);\n svgSpot.appendChild(svg);\n // Take the bounding box.\n bbox = svg.getBBox();\n } finally {\n // Always destroy the element, even if, for example, getBBox throws.\n doc.body.removeChild(svgSpot);\n }\n }\n\n if (fillGradientId) {\n const newFillRef = _createGradient(fillGradientId, svgTag, bbox, matrix);\n if (newFillRef) fill = newFillRef;\n }\n\n if (strokeGradientId) {\n const newStrokeRef = _createGradient(strokeGradientId, svgTag, bbox, matrix);\n if (newStrokeRef) stroke = newStrokeRef;\n }\n }\n\n // Transform path data\n element.setAttribute('d', _transformPath(element.attributes.d.value, matrix));\n element.removeAttribute('transform');\n\n // Transform stroke width\n const matrixScale = _getScaleFactor(matrix);\n element.setAttribute('stroke-width', _quadraticMean(matrixScale.x, matrixScale.y) * strokeWidth);\n if (fill) element.setAttribute('fill', fill);\n if (stroke) element.setAttribute('stroke', stroke);\n } else if (_isGraphicsElement(element)) {\n // Push stroke width, fill, and stroke down to leaves\n if (strokeWidth && !element.attributes['stroke-width']) {\n element.setAttribute('stroke-width', strokeWidth);\n }\n if (fill && !element.attributes.fill) {\n element.setAttribute('fill', fill);\n }\n if (stroke && !element.attributes.stroke) {\n element.setAttribute('stroke', stroke);\n }\n\n // Push transform down to leaves\n matrix = Matrix.compose(matrix, _parseTransform(element));\n if (Matrix.toString(matrix) === Matrix.toString(Matrix.identity())) {\n element.removeAttribute('transform');\n } else {\n element.setAttribute('transform', Matrix.toString(matrix));\n }\n }\n };\n applyTransforms(svgTag, inherited, 1 /* default SVG stroke width */);\n};\n\nmodule.exports = transformStrokeWidths;\n","const minilog = require('minilog');\nminilog.enable();\n\nmodule.exports = minilog('scratch-svg-render');\n","module.exports = CollectingHandler;\n\nfunction CollectingHandler(cbs){\n\tthis._cbs = cbs || {};\n\tthis.events = [];\n}\n\nvar EVENTS = require(\"./\").EVENTS;\nObject.keys(EVENTS).forEach(function(name){\n\tif(EVENTS[name] === 0){\n\t\tname = \"on\" + name;\n\t\tCollectingHandler.prototype[name] = function(){\n\t\t\tthis.events.push([name]);\n\t\t\tif(this._cbs[name]) this._cbs[name]();\n\t\t};\n\t} else if(EVENTS[name] === 1){\n\t\tname = \"on\" + name;\n\t\tCollectingHandler.prototype[name] = function(a){\n\t\t\tthis.events.push([name, a]);\n\t\t\tif(this._cbs[name]) this._cbs[name](a);\n\t\t};\n\t} else if(EVENTS[name] === 2){\n\t\tname = \"on\" + name;\n\t\tCollectingHandler.prototype[name] = function(a, b){\n\t\t\tthis.events.push([name, a, b]);\n\t\t\tif(this._cbs[name]) this._cbs[name](a, b);\n\t\t};\n\t} else {\n\t\tthrow Error(\"wrong number of arguments\");\n\t}\n});\n\nCollectingHandler.prototype.onreset = function(){\n\tthis.events = [];\n\tif(this._cbs.onreset) this._cbs.onreset();\n};\n\nCollectingHandler.prototype.restart = function(){\n\tif(this._cbs.onreset) this._cbs.onreset();\n\n\tfor(var i = 0, len = this.events.length; i < len; i++){\n\t\tif(this._cbs[this.events[i][0]]){\n\n\t\t\tvar num = this.events[i].length;\n\n\t\t\tif(num === 1){\n\t\t\t\tthis._cbs[this.events[i][0]]();\n\t\t\t} else if(num === 2){\n\t\t\t\tthis._cbs[this.events[i][0]](this.events[i][1]);\n\t\t\t} else {\n\t\t\t\tthis._cbs[this.events[i][0]](this.events[i][1], this.events[i][2]);\n\t\t\t}\n\t\t}\n\t}\n};\n","var index = require(\"./index.js\");\nvar DomHandler = index.DomHandler;\nvar DomUtils = index.DomUtils;\n\n//TODO: make this a streamable handler\nfunction FeedHandler(callback, options){\n\tthis.init(callback, options);\n}\n\nrequire(\"inherits\")(FeedHandler, DomHandler);\n\nFeedHandler.prototype.init = DomHandler;\n\nfunction getElements(what, where){\n\treturn DomUtils.getElementsByTagName(what, where, true);\n}\nfunction getOneElement(what, where){\n\treturn DomUtils.getElementsByTagName(what, where, true, 1)[0];\n}\nfunction fetch(what, where, recurse){\n\treturn DomUtils.getText(\n\t\tDomUtils.getElementsByTagName(what, where, recurse, 1)\n\t).trim();\n}\n\nfunction addConditionally(obj, prop, what, where, recurse){\n\tvar tmp = fetch(what, where, recurse);\n\tif(tmp) obj[prop] = tmp;\n}\n\nvar isValidFeed = function(value){\n\treturn value === \"rss\" || value === \"feed\" || value === \"rdf:RDF\";\n};\n\nFeedHandler.prototype.onend = function(){\n\tvar feed = {},\n\t feedRoot = getOneElement(isValidFeed, this.dom),\n\t tmp, childs;\n\n\tif(feedRoot){\n\t\tif(feedRoot.name === \"feed\"){\n\t\t\tchilds = feedRoot.children;\n\n\t\t\tfeed.type = \"atom\";\n\t\t\taddConditionally(feed, \"id\", \"id\", childs);\n\t\t\taddConditionally(feed, \"title\", \"title\", childs);\n\t\t\tif((tmp = getOneElement(\"link\", childs)) && (tmp = tmp.attribs) && (tmp = tmp.href)) feed.link = tmp;\n\t\t\taddConditionally(feed, \"description\", \"subtitle\", childs);\n\t\t\tif((tmp = fetch(\"updated\", childs))) feed.updated = new Date(tmp);\n\t\t\taddConditionally(feed, \"author\", \"email\", childs, true);\n\n\t\t\tfeed.items = getElements(\"entry\", childs).map(function(item){\n\t\t\t\tvar entry = {}, tmp;\n\n\t\t\t\titem = item.children;\n\n\t\t\t\taddConditionally(entry, \"id\", \"id\", item);\n\t\t\t\taddConditionally(entry, \"title\", \"title\", item);\n\t\t\t\tif((tmp = getOneElement(\"link\", item)) && (tmp = tmp.attribs) && (tmp = tmp.href)) entry.link = tmp;\n\t\t\t\tif((tmp = fetch(\"summary\", item) || fetch(\"content\", item))) entry.description = tmp;\n\t\t\t\tif((tmp = fetch(\"updated\", item))) entry.pubDate = new Date(tmp);\n\t\t\t\treturn entry;\n\t\t\t});\n\t\t} else {\n\t\t\tchilds = getOneElement(\"channel\", feedRoot.children).children;\n\n\t\t\tfeed.type = feedRoot.name.substr(0, 3);\n\t\t\tfeed.id = \"\";\n\t\t\taddConditionally(feed, \"title\", \"title\", childs);\n\t\t\taddConditionally(feed, \"link\", \"link\", childs);\n\t\t\taddConditionally(feed, \"description\", \"description\", childs);\n\t\t\tif((tmp = fetch(\"lastBuildDate\", childs))) feed.updated = new Date(tmp);\n\t\t\taddConditionally(feed, \"author\", \"managingEditor\", childs, true);\n\n\t\t\tfeed.items = getElements(\"item\", feedRoot.children).map(function(item){\n\t\t\t\tvar entry = {}, tmp;\n\n\t\t\t\titem = item.children;\n\n\t\t\t\taddConditionally(entry, \"id\", \"guid\", item);\n\t\t\t\taddConditionally(entry, \"title\", \"title\", item);\n\t\t\t\taddConditionally(entry, \"link\", \"link\", item);\n\t\t\t\taddConditionally(entry, \"description\", \"description\", item);\n\t\t\t\tif((tmp = fetch(\"pubDate\", item))) entry.pubDate = new Date(tmp);\n\t\t\t\treturn entry;\n\t\t\t});\n\t\t}\n\t}\n\tthis.dom = feed;\n\tDomHandler.prototype._handleCallback.call(\n\t\tthis, feedRoot ? null : Error(\"couldn't find root of feed\")\n\t);\n};\n\nmodule.exports = FeedHandler;\n","var Tokenizer = require(\"./Tokenizer.js\");\n\n/*\n\tOptions:\n\n\txmlMode: Disables the special behavior for script/style tags (false by default)\n\tlowerCaseAttributeNames: call .toLowerCase for each attribute name (true if xmlMode is `false`)\n\tlowerCaseTags: call .toLowerCase for each tag name (true if xmlMode is `false`)\n*/\n\n/*\n\tCallbacks:\n\n\toncdataend,\n\toncdatastart,\n\tonclosetag,\n\toncomment,\n\toncommentend,\n\tonerror,\n\tonopentag,\n\tonprocessinginstruction,\n\tonreset,\n\tontext\n*/\n\nvar formTags = {\n\tinput: true,\n\toption: true,\n\toptgroup: true,\n\tselect: true,\n\tbutton: true,\n\tdatalist: true,\n\ttextarea: true\n};\n\nvar openImpliesClose = {\n\ttr : { tr:true, th:true, td:true },\n\tth : { th:true },\n\ttd : { thead:true, th:true, td:true },\n\tbody : { head:true, link:true, script:true },\n\tli : { li:true },\n\tp : { p:true },\n\th1 : { p:true },\n\th2 : { p:true },\n\th3 : { p:true },\n\th4 : { p:true },\n\th5 : { p:true },\n\th6 : { p:true },\n\tselect : formTags,\n\tinput : formTags,\n\toutput : formTags,\n\tbutton : formTags,\n\tdatalist: formTags,\n\ttextarea: formTags,\n\toption : { option:true },\n\toptgroup: { optgroup:true }\n};\n\nvar voidElements = {\n\t__proto__: null,\n\tarea: true,\n\tbase: true,\n\tbasefont: true,\n\tbr: true,\n\tcol: true,\n\tcommand: true,\n\tembed: true,\n\tframe: true,\n\thr: true,\n\timg: true,\n\tinput: true,\n\tisindex: true,\n\tkeygen: true,\n\tlink: true,\n\tmeta: true,\n\tparam: true,\n\tsource: true,\n\ttrack: true,\n\twbr: true,\n};\n\nvar foreignContextElements = {\n\t__proto__: null,\n\tmath: true,\n\tsvg: true\n}\nvar htmlIntegrationElements = {\n\t__proto__: null,\n\tmi: true,\n\tmo: true,\n\tmn: true,\n\tms: true,\n\tmtext: true,\n\t\"annotation-xml\": true,\n\tforeignObject: true,\n\tdesc: true,\n\ttitle: true\n}\n\nvar re_nameEnd = /\\s|\\//;\n\nfunction Parser(cbs, options){\n\tthis._options = options || {};\n\tthis._cbs = cbs || {};\n\n\tthis._tagname = \"\";\n\tthis._attribname = \"\";\n\tthis._attribvalue = \"\";\n\tthis._attribs = null;\n\tthis._stack = [];\n\tthis._foreignContext = [];\n\n\tthis.startIndex = 0;\n\tthis.endIndex = null;\n\n\tthis._lowerCaseTagNames = \"lowerCaseTags\" in this._options ?\n\t\t!!this._options.lowerCaseTags :\n\t\t!this._options.xmlMode;\n\tthis._lowerCaseAttributeNames = \"lowerCaseAttributeNames\" in this._options ?\n\t\t!!this._options.lowerCaseAttributeNames :\n\t\t!this._options.xmlMode;\n\n\tif(this._options.Tokenizer) {\n\t\tTokenizer = this._options.Tokenizer;\n\t}\n\tthis._tokenizer = new Tokenizer(this._options, this);\n\n\tif(this._cbs.onparserinit) this._cbs.onparserinit(this);\n}\n\nrequire(\"inherits\")(Parser, require(\"events\").EventEmitter);\n\nParser.prototype._updatePosition = function(initialOffset){\n\tif(this.endIndex === null){\n\t\tif(this._tokenizer._sectionStart <= initialOffset){\n\t\t\tthis.startIndex = 0;\n\t\t} else {\n\t\t\tthis.startIndex = this._tokenizer._sectionStart - initialOffset;\n\t\t}\n\t}\n\telse this.startIndex = this.endIndex + 1;\n\tthis.endIndex = this._tokenizer.getAbsoluteIndex();\n};\n\n//Tokenizer event handlers\nParser.prototype.ontext = function(data){\n\tthis._updatePosition(1);\n\tthis.endIndex--;\n\n\tif(this._cbs.ontext) this._cbs.ontext(data);\n};\n\nParser.prototype.onopentagname = function(name){\n\tif(this._lowerCaseTagNames){\n\t\tname = name.toLowerCase();\n\t}\n\n\tthis._tagname = name;\n\n\tif(!this._options.xmlMode && name in openImpliesClose) {\n\t\tfor(\n\t\t\tvar el;\n\t\t\t(el = this._stack[this._stack.length - 1]) in openImpliesClose[name];\n\t\t\tthis.onclosetag(el)\n\t\t);\n\t}\n\n\tif(this._options.xmlMode || !(name in voidElements)){\n\t\tthis._stack.push(name);\n\t\tif(name in foreignContextElements) this._foreignContext.push(true);\n\t\telse if(name in htmlIntegrationElements) this._foreignContext.push(false);\n\t}\n\n\tif(this._cbs.onopentagname) this._cbs.onopentagname(name);\n\tif(this._cbs.onopentag) this._attribs = {};\n};\n\nParser.prototype.onopentagend = function(){\n\tthis._updatePosition(1);\n\n\tif(this._attribs){\n\t\tif(this._cbs.onopentag) this._cbs.onopentag(this._tagname, this._attribs);\n\t\tthis._attribs = null;\n\t}\n\n\tif(!this._options.xmlMode && this._cbs.onclosetag && this._tagname in voidElements){\n\t\tthis._cbs.onclosetag(this._tagname);\n\t}\n\n\tthis._tagname = \"\";\n};\n\nParser.prototype.onclosetag = function(name){\n\tthis._updatePosition(1);\n\n\tif(this._lowerCaseTagNames){\n\t\tname = name.toLowerCase();\n\t}\n\n\tif(this._stack.length && (!(name in voidElements) || this._options.xmlMode)){\n\t\tvar pos = this._stack.lastIndexOf(name);\n\t\tif(pos !== -1){\n\t\t\tif(this._cbs.onclosetag){\n\t\t\t\tpos = this._stack.length - pos;\n\t\t\t\twhile(pos--) this._cbs.onclosetag(this._stack.pop());\n\t\t\t}\n\t\t\telse this._stack.length = pos;\n\t\t} else if(name === \"p\" && !this._options.xmlMode){\n\t\t\tthis.onopentagname(name);\n\t\t\tthis._closeCurrentTag();\n\t\t}\n\t} else if(!this._options.xmlMode && (name === \"br\" || name === \"p\")){\n\t\tthis.onopentagname(name);\n\t\tthis._closeCurrentTag();\n\t}\n};\n\nParser.prototype.onselfclosingtag = function(){\n\tif(this._options.xmlMode || this._options.recognizeSelfClosing\n\t\t|| this._foreignContext[this._foreignContext.length - 1]){\n\t\tthis._closeCurrentTag();\n\t} else {\n\t\tthis.onopentagend();\n\t}\n};\n\nParser.prototype._closeCurrentTag = function(){\n\tvar name = this._tagname;\n\n\tthis.onopentagend();\n\n\t//self-closing tags will be on the top of the stack\n\t//(cheaper check than in onclosetag)\n\tif(this._stack[this._stack.length - 1] === name){\n\t\tif(this._cbs.onclosetag){\n\t\t\tthis._cbs.onclosetag(name);\n\t\t}\n\t\tthis._stack.pop();\n\t\tif((name in foreignContextElements) || (name in htmlIntegrationElements)){\n\t\t\tthis._foreignContext.pop();\n\t\t}\n\t}\n};\n\nParser.prototype.onattribname = function(name){\n\tif(this._lowerCaseAttributeNames){\n\t\tname = name.toLowerCase();\n\t}\n\tthis._attribname = name;\n};\n\nParser.prototype.onattribdata = function(value){\n\tthis._attribvalue += value;\n};\n\nParser.prototype.onattribend = function(){\n\tif(this._cbs.onattribute) this._cbs.onattribute(this._attribname, this._attribvalue);\n\tif(\n\t\tthis._attribs &&\n\t\t!Object.prototype.hasOwnProperty.call(this._attribs, this._attribname)\n\t){\n\t\tthis._attribs[this._attribname] = this._attribvalue;\n\t}\n\tthis._attribname = \"\";\n\tthis._attribvalue = \"\";\n};\n\nParser.prototype._getInstructionName = function(value){\n\tvar idx = value.search(re_nameEnd),\n\t name = idx < 0 ? value : value.substr(0, idx);\n\n\tif(this._lowerCaseTagNames){\n\t\tname = name.toLowerCase();\n\t}\n\n\treturn name;\n};\n\nParser.prototype.ondeclaration = function(value){\n\tif(this._cbs.onprocessinginstruction){\n\t\tvar name = this._getInstructionName(value);\n\t\tthis._cbs.onprocessinginstruction(\"!\" + name, \"!\" + value);\n\t}\n};\n\nParser.prototype.onprocessinginstruction = function(value){\n\tif(this._cbs.onprocessinginstruction){\n\t\tvar name = this._getInstructionName(value);\n\t\tthis._cbs.onprocessinginstruction(\"?\" + name, \"?\" + value);\n\t}\n};\n\nParser.prototype.oncomment = function(value){\n\tthis._updatePosition(4);\n\n\tif(this._cbs.oncomment) this._cbs.oncomment(value);\n\tif(this._cbs.oncommentend) this._cbs.oncommentend();\n};\n\nParser.prototype.oncdata = function(value){\n\tthis._updatePosition(1);\n\n\tif(this._options.xmlMode || this._options.recognizeCDATA){\n\t\tif(this._cbs.oncdatastart) this._cbs.oncdatastart();\n\t\tif(this._cbs.ontext) this._cbs.ontext(value);\n\t\tif(this._cbs.oncdataend) this._cbs.oncdataend();\n\t} else {\n\t\tthis.oncomment(\"[CDATA[\" + value + \"]]\");\n\t}\n};\n\nParser.prototype.onerror = function(err){\n\tif(this._cbs.onerror) this._cbs.onerror(err);\n};\n\nParser.prototype.onend = function(){\n\tif(this._cbs.onclosetag){\n\t\tfor(\n\t\t\tvar i = this._stack.length;\n\t\t\ti > 0;\n\t\t\tthis._cbs.onclosetag(this._stack[--i])\n\t\t);\n\t}\n\tif(this._cbs.onend) this._cbs.onend();\n};\n\n\n//Resets the parser to a blank state, ready to parse a new HTML document\nParser.prototype.reset = function(){\n\tif(this._cbs.onreset) this._cbs.onreset();\n\tthis._tokenizer.reset();\n\n\tthis._tagname = \"\";\n\tthis._attribname = \"\";\n\tthis._attribs = null;\n\tthis._stack = [];\n\n\tif(this._cbs.onparserinit) this._cbs.onparserinit(this);\n};\n\n//Parses a complete HTML document and pushes it to the handler\nParser.prototype.parseComplete = function(data){\n\tthis.reset();\n\tthis.end(data);\n};\n\nParser.prototype.write = function(chunk){\n\tthis._tokenizer.write(chunk);\n};\n\nParser.prototype.end = function(chunk){\n\tthis._tokenizer.end(chunk);\n};\n\nParser.prototype.pause = function(){\n\tthis._tokenizer.pause();\n};\n\nParser.prototype.resume = function(){\n\tthis._tokenizer.resume();\n};\n\n//alias for backwards compat\nParser.prototype.parseChunk = Parser.prototype.write;\nParser.prototype.done = Parser.prototype.end;\n\nmodule.exports = Parser;\n","module.exports = ProxyHandler;\n\nfunction ProxyHandler(cbs){\n\tthis._cbs = cbs || {};\n}\n\nvar EVENTS = require(\"./\").EVENTS;\nObject.keys(EVENTS).forEach(function(name){\n\tif(EVENTS[name] === 0){\n\t\tname = \"on\" + name;\n\t\tProxyHandler.prototype[name] = function(){\n\t\t\tif(this._cbs[name]) this._cbs[name]();\n\t\t};\n\t} else if(EVENTS[name] === 1){\n\t\tname = \"on\" + name;\n\t\tProxyHandler.prototype[name] = function(a){\n\t\t\tif(this._cbs[name]) this._cbs[name](a);\n\t\t};\n\t} else if(EVENTS[name] === 2){\n\t\tname = \"on\" + name;\n\t\tProxyHandler.prototype[name] = function(a, b){\n\t\t\tif(this._cbs[name]) this._cbs[name](a, b);\n\t\t};\n\t} else {\n\t\tthrow Error(\"wrong number of arguments\");\n\t}\n});","module.exports = Stream;\n\nvar Parser = require(\"./WritableStream.js\");\n\nfunction Stream(options){\n\tParser.call(this, new Cbs(this), options);\n}\n\nrequire(\"inherits\")(Stream, Parser);\n\nStream.prototype.readable = true;\n\nfunction Cbs(scope){\n\tthis.scope = scope;\n}\n\nvar EVENTS = require(\"../\").EVENTS;\n\nObject.keys(EVENTS).forEach(function(name){\n\tif(EVENTS[name] === 0){\n\t\tCbs.prototype[\"on\" + name] = function(){\n\t\t\tthis.scope.emit(name);\n\t\t};\n\t} else if(EVENTS[name] === 1){\n\t\tCbs.prototype[\"on\" + name] = function(a){\n\t\t\tthis.scope.emit(name, a);\n\t\t};\n\t} else if(EVENTS[name] === 2){\n\t\tCbs.prototype[\"on\" + name] = function(a, b){\n\t\t\tthis.scope.emit(name, a, b);\n\t\t};\n\t} else {\n\t\tthrow Error(\"wrong number of arguments!\");\n\t}\n});","module.exports = Tokenizer;\n\nvar decodeCodePoint = require(\"entities/lib/decode_codepoint.js\");\nvar entityMap = require(\"entities/maps/entities.json\");\nvar legacyMap = require(\"entities/maps/legacy.json\");\nvar xmlMap = require(\"entities/maps/xml.json\");\n\nvar i = 0;\n\nvar TEXT = i++;\nvar BEFORE_TAG_NAME = i++; //after <\nvar IN_TAG_NAME = i++;\nvar IN_SELF_CLOSING_TAG = i++;\nvar BEFORE_CLOSING_TAG_NAME = i++;\nvar IN_CLOSING_TAG_NAME = i++;\nvar AFTER_CLOSING_TAG_NAME = i++;\n\n//attributes\nvar BEFORE_ATTRIBUTE_NAME = i++;\nvar IN_ATTRIBUTE_NAME = i++;\nvar AFTER_ATTRIBUTE_NAME = i++;\nvar BEFORE_ATTRIBUTE_VALUE = i++;\nvar IN_ATTRIBUTE_VALUE_DQ = i++; // \"\nvar IN_ATTRIBUTE_VALUE_SQ = i++; // '\nvar IN_ATTRIBUTE_VALUE_NQ = i++;\n\n//declarations\nvar BEFORE_DECLARATION = i++; // !\nvar IN_DECLARATION = i++;\n\n//processing instructions\nvar IN_PROCESSING_INSTRUCTION = i++; // ?\n\n//comments\nvar BEFORE_COMMENT = i++;\nvar IN_COMMENT = i++;\nvar AFTER_COMMENT_1 = i++;\nvar AFTER_COMMENT_2 = i++;\n\n//cdata\nvar BEFORE_CDATA_1 = i++; // [\nvar BEFORE_CDATA_2 = i++; // C\nvar BEFORE_CDATA_3 = i++; // D\nvar BEFORE_CDATA_4 = i++; // A\nvar BEFORE_CDATA_5 = i++; // T\nvar BEFORE_CDATA_6 = i++; // A\nvar IN_CDATA = i++; // [\nvar AFTER_CDATA_1 = i++; // ]\nvar AFTER_CDATA_2 = i++; // ]\n\n//special tags\nvar BEFORE_SPECIAL = i++; //S\nvar BEFORE_SPECIAL_END = i++; //S\n\nvar BEFORE_SCRIPT_1 = i++; //C\nvar BEFORE_SCRIPT_2 = i++; //R\nvar BEFORE_SCRIPT_3 = i++; //I\nvar BEFORE_SCRIPT_4 = i++; //P\nvar BEFORE_SCRIPT_5 = i++; //T\nvar AFTER_SCRIPT_1 = i++; //C\nvar AFTER_SCRIPT_2 = i++; //R\nvar AFTER_SCRIPT_3 = i++; //I\nvar AFTER_SCRIPT_4 = i++; //P\nvar AFTER_SCRIPT_5 = i++; //T\n\nvar BEFORE_STYLE_1 = i++; //T\nvar BEFORE_STYLE_2 = i++; //Y\nvar BEFORE_STYLE_3 = i++; //L\nvar BEFORE_STYLE_4 = i++; //E\nvar AFTER_STYLE_1 = i++; //T\nvar AFTER_STYLE_2 = i++; //Y\nvar AFTER_STYLE_3 = i++; //L\nvar AFTER_STYLE_4 = i++; //E\n\nvar BEFORE_ENTITY = i++; //&\nvar BEFORE_NUMERIC_ENTITY = i++; //#\nvar IN_NAMED_ENTITY = i++;\nvar IN_NUMERIC_ENTITY = i++;\nvar IN_HEX_ENTITY = i++; //X\n\nvar j = 0;\n\nvar SPECIAL_NONE = j++;\nvar SPECIAL_SCRIPT = j++;\nvar SPECIAL_STYLE = j++;\n\nfunction whitespace(c){\n\treturn c === \" \" || c === \"\\n\" || c === \"\\t\" || c === \"\\f\" || c === \"\\r\";\n}\n\nfunction ifElseState(upper, SUCCESS, FAILURE){\n\tvar lower = upper.toLowerCase();\n\n\tif(upper === lower){\n\t\treturn function(c){\n\t\t\tif(c === lower){\n\t\t\t\tthis._state = SUCCESS;\n\t\t\t} else {\n\t\t\t\tthis._state = FAILURE;\n\t\t\t\tthis._index--;\n\t\t\t}\n\t\t};\n\t} else {\n\t\treturn function(c){\n\t\t\tif(c === lower || c === upper){\n\t\t\t\tthis._state = SUCCESS;\n\t\t\t} else {\n\t\t\t\tthis._state = FAILURE;\n\t\t\t\tthis._index--;\n\t\t\t}\n\t\t};\n\t}\n}\n\nfunction consumeSpecialNameChar(upper, NEXT_STATE){\n\tvar lower = upper.toLowerCase();\n\n\treturn function(c){\n\t\tif(c === lower || c === upper){\n\t\t\tthis._state = NEXT_STATE;\n\t\t} else {\n\t\t\tthis._state = IN_TAG_NAME;\n\t\t\tthis._index--; //consume the token again\n\t\t}\n\t};\n}\n\nfunction Tokenizer(options, cbs){\n\tthis._state = TEXT;\n\tthis._buffer = \"\";\n\tthis._sectionStart = 0;\n\tthis._index = 0;\n\tthis._bufferOffset = 0; //chars removed from _buffer\n\tthis._baseState = TEXT;\n\tthis._special = SPECIAL_NONE;\n\tthis._cbs = cbs;\n\tthis._running = true;\n\tthis._ended = false;\n\tthis._xmlMode = !!(options && options.xmlMode);\n\tthis._decodeEntities = !!(options && options.decodeEntities);\n}\n\nTokenizer.prototype._stateText = function(c){\n\tif(c === \"<\"){\n\t\tif(this._index > this._sectionStart){\n\t\t\tthis._cbs.ontext(this._getSection());\n\t\t}\n\t\tthis._state = BEFORE_TAG_NAME;\n\t\tthis._sectionStart = this._index;\n\t} else if(this._decodeEntities && this._special === SPECIAL_NONE && c === \"&\"){\n\t\tif(this._index > this._sectionStart){\n\t\t\tthis._cbs.ontext(this._getSection());\n\t\t}\n\t\tthis._baseState = TEXT;\n\t\tthis._state = BEFORE_ENTITY;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateBeforeTagName = function(c){\n\tif(c === \"/\"){\n\t\tthis._state = BEFORE_CLOSING_TAG_NAME;\n\t} else if(c === \"<\"){\n\t\tthis._cbs.ontext(this._getSection());\n\t\tthis._sectionStart = this._index;\n\t} else if(c === \">\" || this._special !== SPECIAL_NONE || whitespace(c)) {\n\t\tthis._state = TEXT;\n\t} else if(c === \"!\"){\n\t\tthis._state = BEFORE_DECLARATION;\n\t\tthis._sectionStart = this._index + 1;\n\t} else if(c === \"?\"){\n\t\tthis._state = IN_PROCESSING_INSTRUCTION;\n\t\tthis._sectionStart = this._index + 1;\n\t} else {\n\t\tthis._state = (!this._xmlMode && (c === \"s\" || c === \"S\")) ?\n\t\t\tBEFORE_SPECIAL : IN_TAG_NAME;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateInTagName = function(c){\n\tif(c === \"/\" || c === \">\" || whitespace(c)){\n\t\tthis._emitToken(\"onopentagname\");\n\t\tthis._state = BEFORE_ATTRIBUTE_NAME;\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._stateBeforeCloseingTagName = function(c){\n\tif(whitespace(c));\n\telse if(c === \">\"){\n\t\tthis._state = TEXT;\n\t} else if(this._special !== SPECIAL_NONE){\n\t\tif(c === \"s\" || c === \"S\"){\n\t\t\tthis._state = BEFORE_SPECIAL_END;\n\t\t} else {\n\t\t\tthis._state = TEXT;\n\t\t\tthis._index--;\n\t\t}\n\t} else {\n\t\tthis._state = IN_CLOSING_TAG_NAME;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateInCloseingTagName = function(c){\n\tif(c === \">\" || whitespace(c)){\n\t\tthis._emitToken(\"onclosetag\");\n\t\tthis._state = AFTER_CLOSING_TAG_NAME;\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._stateAfterCloseingTagName = function(c){\n\t//skip everything until \">\"\n\tif(c === \">\"){\n\t\tthis._state = TEXT;\n\t\tthis._sectionStart = this._index + 1;\n\t}\n};\n\nTokenizer.prototype._stateBeforeAttributeName = function(c){\n\tif(c === \">\"){\n\t\tthis._cbs.onopentagend();\n\t\tthis._state = TEXT;\n\t\tthis._sectionStart = this._index + 1;\n\t} else if(c === \"/\"){\n\t\tthis._state = IN_SELF_CLOSING_TAG;\n\t} else if(!whitespace(c)){\n\t\tthis._state = IN_ATTRIBUTE_NAME;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateInSelfClosingTag = function(c){\n\tif(c === \">\"){\n\t\tthis._cbs.onselfclosingtag();\n\t\tthis._state = TEXT;\n\t\tthis._sectionStart = this._index + 1;\n\t} else if(!whitespace(c)){\n\t\tthis._state = BEFORE_ATTRIBUTE_NAME;\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._stateInAttributeName = function(c){\n\tif(c === \"=\" || c === \"/\" || c === \">\" || whitespace(c)){\n\t\tthis._cbs.onattribname(this._getSection());\n\t\tthis._sectionStart = -1;\n\t\tthis._state = AFTER_ATTRIBUTE_NAME;\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._stateAfterAttributeName = function(c){\n\tif(c === \"=\"){\n\t\tthis._state = BEFORE_ATTRIBUTE_VALUE;\n\t} else if(c === \"/\" || c === \">\"){\n\t\tthis._cbs.onattribend();\n\t\tthis._state = BEFORE_ATTRIBUTE_NAME;\n\t\tthis._index--;\n\t} else if(!whitespace(c)){\n\t\tthis._cbs.onattribend();\n\t\tthis._state = IN_ATTRIBUTE_NAME;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateBeforeAttributeValue = function(c){\n\tif(c === \"\\\"\"){\n\t\tthis._state = IN_ATTRIBUTE_VALUE_DQ;\n\t\tthis._sectionStart = this._index + 1;\n\t} else if(c === \"'\"){\n\t\tthis._state = IN_ATTRIBUTE_VALUE_SQ;\n\t\tthis._sectionStart = this._index + 1;\n\t} else if(!whitespace(c)){\n\t\tthis._state = IN_ATTRIBUTE_VALUE_NQ;\n\t\tthis._sectionStart = this._index;\n\t\tthis._index--; //reconsume token\n\t}\n};\n\nTokenizer.prototype._stateInAttributeValueDoubleQuotes = function(c){\n\tif(c === \"\\\"\"){\n\t\tthis._emitToken(\"onattribdata\");\n\t\tthis._cbs.onattribend();\n\t\tthis._state = BEFORE_ATTRIBUTE_NAME;\n\t} else if(this._decodeEntities && c === \"&\"){\n\t\tthis._emitToken(\"onattribdata\");\n\t\tthis._baseState = this._state;\n\t\tthis._state = BEFORE_ENTITY;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateInAttributeValueSingleQuotes = function(c){\n\tif(c === \"'\"){\n\t\tthis._emitToken(\"onattribdata\");\n\t\tthis._cbs.onattribend();\n\t\tthis._state = BEFORE_ATTRIBUTE_NAME;\n\t} else if(this._decodeEntities && c === \"&\"){\n\t\tthis._emitToken(\"onattribdata\");\n\t\tthis._baseState = this._state;\n\t\tthis._state = BEFORE_ENTITY;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateInAttributeValueNoQuotes = function(c){\n\tif(whitespace(c) || c === \">\"){\n\t\tthis._emitToken(\"onattribdata\");\n\t\tthis._cbs.onattribend();\n\t\tthis._state = BEFORE_ATTRIBUTE_NAME;\n\t\tthis._index--;\n\t} else if(this._decodeEntities && c === \"&\"){\n\t\tthis._emitToken(\"onattribdata\");\n\t\tthis._baseState = this._state;\n\t\tthis._state = BEFORE_ENTITY;\n\t\tthis._sectionStart = this._index;\n\t}\n};\n\nTokenizer.prototype._stateBeforeDeclaration = function(c){\n\tthis._state = c === \"[\" ? BEFORE_CDATA_1 :\n\t\tc === \"-\" ? BEFORE_COMMENT :\n\t\t\tIN_DECLARATION;\n};\n\nTokenizer.prototype._stateInDeclaration = function(c){\n\tif(c === \">\"){\n\t\tthis._cbs.ondeclaration(this._getSection());\n\t\tthis._state = TEXT;\n\t\tthis._sectionStart = this._index + 1;\n\t}\n};\n\nTokenizer.prototype._stateInProcessingInstruction = function(c){\n\tif(c === \">\"){\n\t\tthis._cbs.onprocessinginstruction(this._getSection());\n\t\tthis._state = TEXT;\n\t\tthis._sectionStart = this._index + 1;\n\t}\n};\n\nTokenizer.prototype._stateBeforeComment = function(c){\n\tif(c === \"-\"){\n\t\tthis._state = IN_COMMENT;\n\t\tthis._sectionStart = this._index + 1;\n\t} else {\n\t\tthis._state = IN_DECLARATION;\n\t}\n};\n\nTokenizer.prototype._stateInComment = function(c){\n\tif(c === \"-\") this._state = AFTER_COMMENT_1;\n};\n\nTokenizer.prototype._stateAfterComment1 = function(c){\n\tif(c === \"-\"){\n\t\tthis._state = AFTER_COMMENT_2;\n\t} else {\n\t\tthis._state = IN_COMMENT;\n\t}\n};\n\nTokenizer.prototype._stateAfterComment2 = function(c){\n\tif(c === \">\"){\n\t\t//remove 2 trailing chars\n\t\tthis._cbs.oncomment(this._buffer.substring(this._sectionStart, this._index - 2));\n\t\tthis._state = TEXT;\n\t\tthis._sectionStart = this._index + 1;\n\t} else if(c !== \"-\"){\n\t\tthis._state = IN_COMMENT;\n\t}\n\t// else: stay in AFTER_COMMENT_2 (`--->`)\n};\n\nTokenizer.prototype._stateBeforeCdata1 = ifElseState(\"C\", BEFORE_CDATA_2, IN_DECLARATION);\nTokenizer.prototype._stateBeforeCdata2 = ifElseState(\"D\", BEFORE_CDATA_3, IN_DECLARATION);\nTokenizer.prototype._stateBeforeCdata3 = ifElseState(\"A\", BEFORE_CDATA_4, IN_DECLARATION);\nTokenizer.prototype._stateBeforeCdata4 = ifElseState(\"T\", BEFORE_CDATA_5, IN_DECLARATION);\nTokenizer.prototype._stateBeforeCdata5 = ifElseState(\"A\", BEFORE_CDATA_6, IN_DECLARATION);\n\nTokenizer.prototype._stateBeforeCdata6 = function(c){\n\tif(c === \"[\"){\n\t\tthis._state = IN_CDATA;\n\t\tthis._sectionStart = this._index + 1;\n\t} else {\n\t\tthis._state = IN_DECLARATION;\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._stateInCdata = function(c){\n\tif(c === \"]\") this._state = AFTER_CDATA_1;\n};\n\nTokenizer.prototype._stateAfterCdata1 = function(c){\n\tif(c === \"]\") this._state = AFTER_CDATA_2;\n\telse this._state = IN_CDATA;\n};\n\nTokenizer.prototype._stateAfterCdata2 = function(c){\n\tif(c === \">\"){\n\t\t//remove 2 trailing chars\n\t\tthis._cbs.oncdata(this._buffer.substring(this._sectionStart, this._index - 2));\n\t\tthis._state = TEXT;\n\t\tthis._sectionStart = this._index + 1;\n\t} else if(c !== \"]\") {\n\t\tthis._state = IN_CDATA;\n\t}\n\t//else: stay in AFTER_CDATA_2 (`]]]>`)\n};\n\nTokenizer.prototype._stateBeforeSpecial = function(c){\n\tif(c === \"c\" || c === \"C\"){\n\t\tthis._state = BEFORE_SCRIPT_1;\n\t} else if(c === \"t\" || c === \"T\"){\n\t\tthis._state = BEFORE_STYLE_1;\n\t} else {\n\t\tthis._state = IN_TAG_NAME;\n\t\tthis._index--; //consume the token again\n\t}\n};\n\nTokenizer.prototype._stateBeforeSpecialEnd = function(c){\n\tif(this._special === SPECIAL_SCRIPT && (c === \"c\" || c === \"C\")){\n\t\tthis._state = AFTER_SCRIPT_1;\n\t} else if(this._special === SPECIAL_STYLE && (c === \"t\" || c === \"T\")){\n\t\tthis._state = AFTER_STYLE_1;\n\t}\n\telse this._state = TEXT;\n};\n\nTokenizer.prototype._stateBeforeScript1 = consumeSpecialNameChar(\"R\", BEFORE_SCRIPT_2);\nTokenizer.prototype._stateBeforeScript2 = consumeSpecialNameChar(\"I\", BEFORE_SCRIPT_3);\nTokenizer.prototype._stateBeforeScript3 = consumeSpecialNameChar(\"P\", BEFORE_SCRIPT_4);\nTokenizer.prototype._stateBeforeScript4 = consumeSpecialNameChar(\"T\", BEFORE_SCRIPT_5);\n\nTokenizer.prototype._stateBeforeScript5 = function(c){\n\tif(c === \"/\" || c === \">\" || whitespace(c)){\n\t\tthis._special = SPECIAL_SCRIPT;\n\t}\n\tthis._state = IN_TAG_NAME;\n\tthis._index--; //consume the token again\n};\n\nTokenizer.prototype._stateAfterScript1 = ifElseState(\"R\", AFTER_SCRIPT_2, TEXT);\nTokenizer.prototype._stateAfterScript2 = ifElseState(\"I\", AFTER_SCRIPT_3, TEXT);\nTokenizer.prototype._stateAfterScript3 = ifElseState(\"P\", AFTER_SCRIPT_4, TEXT);\nTokenizer.prototype._stateAfterScript4 = ifElseState(\"T\", AFTER_SCRIPT_5, TEXT);\n\nTokenizer.prototype._stateAfterScript5 = function(c){\n\tif(c === \">\" || whitespace(c)){\n\t\tthis._special = SPECIAL_NONE;\n\t\tthis._state = IN_CLOSING_TAG_NAME;\n\t\tthis._sectionStart = this._index - 6;\n\t\tthis._index--; //reconsume the token\n\t}\n\telse this._state = TEXT;\n};\n\nTokenizer.prototype._stateBeforeStyle1 = consumeSpecialNameChar(\"Y\", BEFORE_STYLE_2);\nTokenizer.prototype._stateBeforeStyle2 = consumeSpecialNameChar(\"L\", BEFORE_STYLE_3);\nTokenizer.prototype._stateBeforeStyle3 = consumeSpecialNameChar(\"E\", BEFORE_STYLE_4);\n\nTokenizer.prototype._stateBeforeStyle4 = function(c){\n\tif(c === \"/\" || c === \">\" || whitespace(c)){\n\t\tthis._special = SPECIAL_STYLE;\n\t}\n\tthis._state = IN_TAG_NAME;\n\tthis._index--; //consume the token again\n};\n\nTokenizer.prototype._stateAfterStyle1 = ifElseState(\"Y\", AFTER_STYLE_2, TEXT);\nTokenizer.prototype._stateAfterStyle2 = ifElseState(\"L\", AFTER_STYLE_3, TEXT);\nTokenizer.prototype._stateAfterStyle3 = ifElseState(\"E\", AFTER_STYLE_4, TEXT);\n\nTokenizer.prototype._stateAfterStyle4 = function(c){\n\tif(c === \">\" || whitespace(c)){\n\t\tthis._special = SPECIAL_NONE;\n\t\tthis._state = IN_CLOSING_TAG_NAME;\n\t\tthis._sectionStart = this._index - 5;\n\t\tthis._index--; //reconsume the token\n\t}\n\telse this._state = TEXT;\n};\n\nTokenizer.prototype._stateBeforeEntity = ifElseState(\"#\", BEFORE_NUMERIC_ENTITY, IN_NAMED_ENTITY);\nTokenizer.prototype._stateBeforeNumericEntity = ifElseState(\"X\", IN_HEX_ENTITY, IN_NUMERIC_ENTITY);\n\n//for entities terminated with a semicolon\nTokenizer.prototype._parseNamedEntityStrict = function(){\n\t//offset = 1\n\tif(this._sectionStart + 1 < this._index){\n\t\tvar entity = this._buffer.substring(this._sectionStart + 1, this._index),\n\t\t map = this._xmlMode ? xmlMap : entityMap;\n\n\t\tif(map.hasOwnProperty(entity)){\n\t\t\tthis._emitPartial(map[entity]);\n\t\t\tthis._sectionStart = this._index + 1;\n\t\t}\n\t}\n};\n\n\n//parses legacy entities (without trailing semicolon)\nTokenizer.prototype._parseLegacyEntity = function(){\n\tvar start = this._sectionStart + 1,\n\t limit = this._index - start;\n\n\tif(limit > 6) limit = 6; //the max length of legacy entities is 6\n\n\twhile(limit >= 2){ //the min length of legacy entities is 2\n\t\tvar entity = this._buffer.substr(start, limit);\n\n\t\tif(legacyMap.hasOwnProperty(entity)){\n\t\t\tthis._emitPartial(legacyMap[entity]);\n\t\t\tthis._sectionStart += limit + 1;\n\t\t\treturn;\n\t\t} else {\n\t\t\tlimit--;\n\t\t}\n\t}\n};\n\nTokenizer.prototype._stateInNamedEntity = function(c){\n\tif(c === \";\"){\n\t\tthis._parseNamedEntityStrict();\n\t\tif(this._sectionStart + 1 < this._index && !this._xmlMode){\n\t\t\tthis._parseLegacyEntity();\n\t\t}\n\t\tthis._state = this._baseState;\n\t} else if((c < \"a\" || c > \"z\") && (c < \"A\" || c > \"Z\") && (c < \"0\" || c > \"9\")){\n\t\tif(this._xmlMode);\n\t\telse if(this._sectionStart + 1 === this._index);\n\t\telse if(this._baseState !== TEXT){\n\t\t\tif(c !== \"=\"){\n\t\t\t\tthis._parseNamedEntityStrict();\n\t\t\t}\n\t\t} else {\n\t\t\tthis._parseLegacyEntity();\n\t\t}\n\n\t\tthis._state = this._baseState;\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._decodeNumericEntity = function(offset, base){\n\tvar sectionStart = this._sectionStart + offset;\n\n\tif(sectionStart !== this._index){\n\t\t//parse entity\n\t\tvar entity = this._buffer.substring(sectionStart, this._index);\n\t\tvar parsed = parseInt(entity, base);\n\n\t\tthis._emitPartial(decodeCodePoint(parsed));\n\t\tthis._sectionStart = this._index;\n\t} else {\n\t\tthis._sectionStart--;\n\t}\n\n\tthis._state = this._baseState;\n};\n\nTokenizer.prototype._stateInNumericEntity = function(c){\n\tif(c === \";\"){\n\t\tthis._decodeNumericEntity(2, 10);\n\t\tthis._sectionStart++;\n\t} else if(c < \"0\" || c > \"9\"){\n\t\tif(!this._xmlMode){\n\t\t\tthis._decodeNumericEntity(2, 10);\n\t\t} else {\n\t\t\tthis._state = this._baseState;\n\t\t}\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._stateInHexEntity = function(c){\n\tif(c === \";\"){\n\t\tthis._decodeNumericEntity(3, 16);\n\t\tthis._sectionStart++;\n\t} else if((c < \"a\" || c > \"f\") && (c < \"A\" || c > \"F\") && (c < \"0\" || c > \"9\")){\n\t\tif(!this._xmlMode){\n\t\t\tthis._decodeNumericEntity(3, 16);\n\t\t} else {\n\t\t\tthis._state = this._baseState;\n\t\t}\n\t\tthis._index--;\n\t}\n};\n\nTokenizer.prototype._cleanup = function (){\n\tif(this._sectionStart < 0){\n\t\tthis._buffer = \"\";\n\t\tthis._bufferOffset += this._index;\n\t\tthis._index = 0;\n\t} else if(this._running){\n\t\tif(this._state === TEXT){\n\t\t\tif(this._sectionStart !== this._index){\n\t\t\t\tthis._cbs.ontext(this._buffer.substr(this._sectionStart));\n\t\t\t}\n\t\t\tthis._buffer = \"\";\n\t\t\tthis._bufferOffset += this._index;\n\t\t\tthis._index = 0;\n\t\t} else if(this._sectionStart === this._index){\n\t\t\t//the section just started\n\t\t\tthis._buffer = \"\";\n\t\t\tthis._bufferOffset += this._index;\n\t\t\tthis._index = 0;\n\t\t} else {\n\t\t\t//remove everything unnecessary\n\t\t\tthis._buffer = this._buffer.substr(this._sectionStart);\n\t\t\tthis._index -= this._sectionStart;\n\t\t\tthis._bufferOffset += this._sectionStart;\n\t\t}\n\n\t\tthis._sectionStart = 0;\n\t}\n};\n\n//TODO make events conditional\nTokenizer.prototype.write = function(chunk){\n\tif(this._ended) this._cbs.onerror(Error(\".write() after done!\"));\n\n\tthis._buffer += chunk;\n\tthis._parse();\n};\n\nTokenizer.prototype._parse = function(){\n\twhile(this._index < this._buffer.length && this._running){\n\t\tvar c = this._buffer.charAt(this._index);\n\t\tif(this._state === TEXT) {\n\t\t\tthis._stateText(c);\n\t\t} else if(this._state === BEFORE_TAG_NAME){\n\t\t\tthis._stateBeforeTagName(c);\n\t\t} else if(this._state === IN_TAG_NAME) {\n\t\t\tthis._stateInTagName(c);\n\t\t} else if(this._state === BEFORE_CLOSING_TAG_NAME){\n\t\t\tthis._stateBeforeCloseingTagName(c);\n\t\t} else if(this._state === IN_CLOSING_TAG_NAME){\n\t\t\tthis._stateInCloseingTagName(c);\n\t\t} else if(this._state === AFTER_CLOSING_TAG_NAME){\n\t\t\tthis._stateAfterCloseingTagName(c);\n\t\t} else if(this._state === IN_SELF_CLOSING_TAG){\n\t\t\tthis._stateInSelfClosingTag(c);\n\t\t}\n\n\t\t/*\n\t\t*\tattributes\n\t\t*/\n\t\telse if(this._state === BEFORE_ATTRIBUTE_NAME){\n\t\t\tthis._stateBeforeAttributeName(c);\n\t\t} else if(this._state === IN_ATTRIBUTE_NAME){\n\t\t\tthis._stateInAttributeName(c);\n\t\t} else if(this._state === AFTER_ATTRIBUTE_NAME){\n\t\t\tthis._stateAfterAttributeName(c);\n\t\t} else if(this._state === BEFORE_ATTRIBUTE_VALUE){\n\t\t\tthis._stateBeforeAttributeValue(c);\n\t\t} else if(this._state === IN_ATTRIBUTE_VALUE_DQ){\n\t\t\tthis._stateInAttributeValueDoubleQuotes(c);\n\t\t} else if(this._state === IN_ATTRIBUTE_VALUE_SQ){\n\t\t\tthis._stateInAttributeValueSingleQuotes(c);\n\t\t} else if(this._state === IN_ATTRIBUTE_VALUE_NQ){\n\t\t\tthis._stateInAttributeValueNoQuotes(c);\n\t\t}\n\n\t\t/*\n\t\t*\tdeclarations\n\t\t*/\n\t\telse if(this._state === BEFORE_DECLARATION){\n\t\t\tthis._stateBeforeDeclaration(c);\n\t\t} else if(this._state === IN_DECLARATION){\n\t\t\tthis._stateInDeclaration(c);\n\t\t}\n\n\t\t/*\n\t\t*\tprocessing instructions\n\t\t*/\n\t\telse if(this._state === IN_PROCESSING_INSTRUCTION){\n\t\t\tthis._stateInProcessingInstruction(c);\n\t\t}\n\n\t\t/*\n\t\t*\tcomments\n\t\t*/\n\t\telse if(this._state === BEFORE_COMMENT){\n\t\t\tthis._stateBeforeComment(c);\n\t\t} else if(this._state === IN_COMMENT){\n\t\t\tthis._stateInComment(c);\n\t\t} else if(this._state === AFTER_COMMENT_1){\n\t\t\tthis._stateAfterComment1(c);\n\t\t} else if(this._state === AFTER_COMMENT_2){\n\t\t\tthis._stateAfterComment2(c);\n\t\t}\n\n\t\t/*\n\t\t*\tcdata\n\t\t*/\n\t\telse if(this._state === BEFORE_CDATA_1){\n\t\t\tthis._stateBeforeCdata1(c);\n\t\t} else if(this._state === BEFORE_CDATA_2){\n\t\t\tthis._stateBeforeCdata2(c);\n\t\t} else if(this._state === BEFORE_CDATA_3){\n\t\t\tthis._stateBeforeCdata3(c);\n\t\t} else if(this._state === BEFORE_CDATA_4){\n\t\t\tthis._stateBeforeCdata4(c);\n\t\t} else if(this._state === BEFORE_CDATA_5){\n\t\t\tthis._stateBeforeCdata5(c);\n\t\t} else if(this._state === BEFORE_CDATA_6){\n\t\t\tthis._stateBeforeCdata6(c);\n\t\t} else if(this._state === IN_CDATA){\n\t\t\tthis._stateInCdata(c);\n\t\t} else if(this._state === AFTER_CDATA_1){\n\t\t\tthis._stateAfterCdata1(c);\n\t\t} else if(this._state === AFTER_CDATA_2){\n\t\t\tthis._stateAfterCdata2(c);\n\t\t}\n\n\t\t/*\n\t\t* special tags\n\t\t*/\n\t\telse if(this._state === BEFORE_SPECIAL){\n\t\t\tthis._stateBeforeSpecial(c);\n\t\t} else if(this._state === BEFORE_SPECIAL_END){\n\t\t\tthis._stateBeforeSpecialEnd(c);\n\t\t}\n\n\t\t/*\n\t\t* script\n\t\t*/\n\t\telse if(this._state === BEFORE_SCRIPT_1){\n\t\t\tthis._stateBeforeScript1(c);\n\t\t} else if(this._state === BEFORE_SCRIPT_2){\n\t\t\tthis._stateBeforeScript2(c);\n\t\t} else if(this._state === BEFORE_SCRIPT_3){\n\t\t\tthis._stateBeforeScript3(c);\n\t\t} else if(this._state === BEFORE_SCRIPT_4){\n\t\t\tthis._stateBeforeScript4(c);\n\t\t} else if(this._state === BEFORE_SCRIPT_5){\n\t\t\tthis._stateBeforeScript5(c);\n\t\t}\n\n\t\telse if(this._state === AFTER_SCRIPT_1){\n\t\t\tthis._stateAfterScript1(c);\n\t\t} else if(this._state === AFTER_SCRIPT_2){\n\t\t\tthis._stateAfterScript2(c);\n\t\t} else if(this._state === AFTER_SCRIPT_3){\n\t\t\tthis._stateAfterScript3(c);\n\t\t} else if(this._state === AFTER_SCRIPT_4){\n\t\t\tthis._stateAfterScript4(c);\n\t\t} else if(this._state === AFTER_SCRIPT_5){\n\t\t\tthis._stateAfterScript5(c);\n\t\t}\n\n\t\t/*\n\t\t* style\n\t\t*/\n\t\telse if(this._state === BEFORE_STYLE_1){\n\t\t\tthis._stateBeforeStyle1(c);\n\t\t} else if(this._state === BEFORE_STYLE_2){\n\t\t\tthis._stateBeforeStyle2(c);\n\t\t} else if(this._state === BEFORE_STYLE_3){\n\t\t\tthis._stateBeforeStyle3(c);\n\t\t} else if(this._state === BEFORE_STYLE_4){\n\t\t\tthis._stateBeforeStyle4(c);\n\t\t}\n\n\t\telse if(this._state === AFTER_STYLE_1){\n\t\t\tthis._stateAfterStyle1(c);\n\t\t} else if(this._state === AFTER_STYLE_2){\n\t\t\tthis._stateAfterStyle2(c);\n\t\t} else if(this._state === AFTER_STYLE_3){\n\t\t\tthis._stateAfterStyle3(c);\n\t\t} else if(this._state === AFTER_STYLE_4){\n\t\t\tthis._stateAfterStyle4(c);\n\t\t}\n\n\t\t/*\n\t\t* entities\n\t\t*/\n\t\telse if(this._state === BEFORE_ENTITY){\n\t\t\tthis._stateBeforeEntity(c);\n\t\t} else if(this._state === BEFORE_NUMERIC_ENTITY){\n\t\t\tthis._stateBeforeNumericEntity(c);\n\t\t} else if(this._state === IN_NAMED_ENTITY){\n\t\t\tthis._stateInNamedEntity(c);\n\t\t} else if(this._state === IN_NUMERIC_ENTITY){\n\t\t\tthis._stateInNumericEntity(c);\n\t\t} else if(this._state === IN_HEX_ENTITY){\n\t\t\tthis._stateInHexEntity(c);\n\t\t}\n\n\t\telse {\n\t\t\tthis._cbs.onerror(Error(\"unknown _state\"), this._state);\n\t\t}\n\n\t\tthis._index++;\n\t}\n\n\tthis._cleanup();\n};\n\nTokenizer.prototype.pause = function(){\n\tthis._running = false;\n};\nTokenizer.prototype.resume = function(){\n\tthis._running = true;\n\n\tif(this._index < this._buffer.length){\n\t\tthis._parse();\n\t}\n\tif(this._ended){\n\t\tthis._finish();\n\t}\n};\n\nTokenizer.prototype.end = function(chunk){\n\tif(this._ended) this._cbs.onerror(Error(\".end() after done!\"));\n\tif(chunk) this.write(chunk);\n\n\tthis._ended = true;\n\n\tif(this._running) this._finish();\n};\n\nTokenizer.prototype._finish = function(){\n\t//if there is remaining data, emit it in a reasonable way\n\tif(this._sectionStart < this._index){\n\t\tthis._handleTrailingData();\n\t}\n\n\tthis._cbs.onend();\n};\n\nTokenizer.prototype._handleTrailingData = function(){\n\tvar data = this._buffer.substr(this._sectionStart);\n\n\tif(this._state === IN_CDATA || this._state === AFTER_CDATA_1 || this._state === AFTER_CDATA_2){\n\t\tthis._cbs.oncdata(data);\n\t} else if(this._state === IN_COMMENT || this._state === AFTER_COMMENT_1 || this._state === AFTER_COMMENT_2){\n\t\tthis._cbs.oncomment(data);\n\t} else if(this._state === IN_NAMED_ENTITY && !this._xmlMode){\n\t\tthis._parseLegacyEntity();\n\t\tif(this._sectionStart < this._index){\n\t\t\tthis._state = this._baseState;\n\t\t\tthis._handleTrailingData();\n\t\t}\n\t} else if(this._state === IN_NUMERIC_ENTITY && !this._xmlMode){\n\t\tthis._decodeNumericEntity(2, 10);\n\t\tif(this._sectionStart < this._index){\n\t\t\tthis._state = this._baseState;\n\t\t\tthis._handleTrailingData();\n\t\t}\n\t} else if(this._state === IN_HEX_ENTITY && !this._xmlMode){\n\t\tthis._decodeNumericEntity(3, 16);\n\t\tif(this._sectionStart < this._index){\n\t\t\tthis._state = this._baseState;\n\t\t\tthis._handleTrailingData();\n\t\t}\n\t} else if(\n\t\tthis._state !== IN_TAG_NAME &&\n\t\tthis._state !== BEFORE_ATTRIBUTE_NAME &&\n\t\tthis._state !== BEFORE_ATTRIBUTE_VALUE &&\n\t\tthis._state !== AFTER_ATTRIBUTE_NAME &&\n\t\tthis._state !== IN_ATTRIBUTE_NAME &&\n\t\tthis._state !== IN_ATTRIBUTE_VALUE_SQ &&\n\t\tthis._state !== IN_ATTRIBUTE_VALUE_DQ &&\n\t\tthis._state !== IN_ATTRIBUTE_VALUE_NQ &&\n\t\tthis._state !== IN_CLOSING_TAG_NAME\n\t){\n\t\tthis._cbs.ontext(data);\n\t}\n\t//else, ignore remaining data\n\t//TODO add a way to remove current tag\n};\n\nTokenizer.prototype.reset = function(){\n\tTokenizer.call(this, {xmlMode: this._xmlMode, decodeEntities: this._decodeEntities}, this._cbs);\n};\n\nTokenizer.prototype.getAbsoluteIndex = function(){\n\treturn this._bufferOffset + this._index;\n};\n\nTokenizer.prototype._getSection = function(){\n\treturn this._buffer.substring(this._sectionStart, this._index);\n};\n\nTokenizer.prototype._emitToken = function(name){\n\tthis._cbs[name](this._getSection());\n\tthis._sectionStart = -1;\n};\n\nTokenizer.prototype._emitPartial = function(value){\n\tif(this._baseState !== TEXT){\n\t\tthis._cbs.onattribdata(value); //TODO implement the new event\n\t} else {\n\t\tthis._cbs.ontext(value);\n\t}\n};\n","module.exports = Stream;\n\nvar Parser = require(\"./Parser.js\");\nvar WritableStream = require(\"readable-stream\").Writable;\nvar StringDecoder = require(\"string_decoder\").StringDecoder;\nvar Buffer = require(\"buffer\").Buffer;\n\nfunction Stream(cbs, options){\n\tvar parser = this._parser = new Parser(cbs, options);\n\tvar decoder = this._decoder = new StringDecoder();\n\n\tWritableStream.call(this, {decodeStrings: false});\n\n\tthis.once(\"finish\", function(){\n\t\tparser.end(decoder.end());\n\t});\n}\n\nrequire(\"inherits\")(Stream, WritableStream);\n\nWritableStream.prototype._write = function(chunk, encoding, cb){\n\tif(chunk instanceof Buffer) chunk = this._decoder.write(chunk);\n\tthis._parser.write(chunk);\n\tcb();\n};\n","var Parser = require(\"./Parser.js\");\nvar DomHandler = require(\"domhandler\");\n\nfunction defineProp(name, value){\n\tdelete module.exports[name];\n\tmodule.exports[name] = value;\n\treturn value;\n}\n\nmodule.exports = {\n\tParser: Parser,\n\tTokenizer: require(\"./Tokenizer.js\"),\n\tElementType: require(\"domelementtype\"),\n\tDomHandler: DomHandler,\n\tget FeedHandler(){\n\t\treturn defineProp(\"FeedHandler\", require(\"./FeedHandler.js\"));\n\t},\n\tget Stream(){\n\t\treturn defineProp(\"Stream\", require(\"./Stream.js\"));\n\t},\n\tget WritableStream(){\n\t\treturn defineProp(\"WritableStream\", require(\"./WritableStream.js\"));\n\t},\n\tget ProxyHandler(){\n\t\treturn defineProp(\"ProxyHandler\", require(\"./ProxyHandler.js\"));\n\t},\n\tget DomUtils(){\n\t\treturn defineProp(\"DomUtils\", require(\"domutils\"));\n\t},\n\tget CollectingHandler(){\n\t\treturn defineProp(\"CollectingHandler\", require(\"./CollectingHandler.js\"));\n\t},\n\t// For legacy support\n\tDefaultHandler: DomHandler,\n\tget RssHandler(){\n\t\treturn defineProp(\"RssHandler\", this.FeedHandler);\n\t},\n\t//helper methods\n\tparseDOM: function(data, options){\n\t\tvar handler = new DomHandler(options);\n\t\tnew Parser(handler, options).end(data);\n\t\treturn handler.dom;\n\t},\n\tparseFeed: function(feed, options){\n\t\tvar handler = new module.exports.FeedHandler(options);\n\t\tnew Parser(handler, options).end(feed);\n\t\treturn handler.dom;\n\t},\n\tcreateDomStream: function(cb, options, elementCb){\n\t\tvar handler = new DomHandler(cb, options, elementCb);\n\t\treturn new Parser(handler, options);\n\t},\n\t// List of all events that the parser emits\n\tEVENTS: { /* Format: eventname: number of arguments */\n\t\tattribute: 2,\n\t\tcdatastart: 0,\n\t\tcdataend: 0,\n\t\ttext: 1,\n\t\tprocessinginstruction: 2,\n\t\tcomment: 1,\n\t\tcommentend: 0,\n\t\tclosetag: 1,\n\t\topentag: 2,\n\t\topentagname: 1,\n\t\terror: 1,\n\t\tend: 0\n\t}\n};\n","/**\n * Copyright (c) 2014-2015, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\n(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (global.Immutable = factory());\n}(this, function () { 'use strict';var SLICE$0 = Array.prototype.slice;\n\n function createClass(ctor, superClass) {\n if (superClass) {\n ctor.prototype = Object.create(superClass.prototype);\n }\n ctor.prototype.constructor = ctor;\n }\n\n function Iterable(value) {\n return isIterable(value) ? value : Seq(value);\n }\n\n\n createClass(KeyedIterable, Iterable);\n function KeyedIterable(value) {\n return isKeyed(value) ? value : KeyedSeq(value);\n }\n\n\n createClass(IndexedIterable, Iterable);\n function IndexedIterable(value) {\n return isIndexed(value) ? value : IndexedSeq(value);\n }\n\n\n createClass(SetIterable, Iterable);\n function SetIterable(value) {\n return isIterable(value) && !isAssociative(value) ? value : SetSeq(value);\n }\n\n\n\n function isIterable(maybeIterable) {\n return !!(maybeIterable && maybeIterable[IS_ITERABLE_SENTINEL]);\n }\n\n function isKeyed(maybeKeyed) {\n return !!(maybeKeyed && maybeKeyed[IS_KEYED_SENTINEL]);\n }\n\n function isIndexed(maybeIndexed) {\n return !!(maybeIndexed && maybeIndexed[IS_INDEXED_SENTINEL]);\n }\n\n function isAssociative(maybeAssociative) {\n return isKeyed(maybeAssociative) || isIndexed(maybeAssociative);\n }\n\n function isOrdered(maybeOrdered) {\n return !!(maybeOrdered && maybeOrdered[IS_ORDERED_SENTINEL]);\n }\n\n Iterable.isIterable = isIterable;\n Iterable.isKeyed = isKeyed;\n Iterable.isIndexed = isIndexed;\n Iterable.isAssociative = isAssociative;\n Iterable.isOrdered = isOrdered;\n\n Iterable.Keyed = KeyedIterable;\n Iterable.Indexed = IndexedIterable;\n Iterable.Set = SetIterable;\n\n\n var IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@';\n var IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@';\n var IS_INDEXED_SENTINEL = '@@__IMMUTABLE_INDEXED__@@';\n var IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@';\n\n // Used for setting prototype methods that IE8 chokes on.\n var DELETE = 'delete';\n\n // Constants describing the size of trie nodes.\n var SHIFT = 5; // Resulted in best performance after ______?\n var SIZE = 1 << SHIFT;\n var MASK = SIZE - 1;\n\n // A consistent shared value representing \"not set\" which equals nothing other\n // than itself, and nothing that could be provided externally.\n var NOT_SET = {};\n\n // Boolean references, Rough equivalent of `bool &`.\n var CHANGE_LENGTH = { value: false };\n var DID_ALTER = { value: false };\n\n function MakeRef(ref) {\n ref.value = false;\n return ref;\n }\n\n function SetRef(ref) {\n ref && (ref.value = true);\n }\n\n // A function which returns a value representing an \"owner\" for transient writes\n // to tries. The return value will only ever equal itself, and will not equal\n // the return of any subsequent call of this function.\n function OwnerID() {}\n\n // http://jsperf.com/copy-array-inline\n function arrCopy(arr, offset) {\n offset = offset || 0;\n var len = Math.max(0, arr.length - offset);\n var newArr = new Array(len);\n for (var ii = 0; ii < len; ii++) {\n newArr[ii] = arr[ii + offset];\n }\n return newArr;\n }\n\n function ensureSize(iter) {\n if (iter.size === undefined) {\n iter.size = iter.__iterate(returnTrue);\n }\n return iter.size;\n }\n\n function wrapIndex(iter, index) {\n // This implements \"is array index\" which the ECMAString spec defines as:\n //\n // A String property name P is an array index if and only if\n // ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal\n // to 2^32−1.\n //\n // http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects\n if (typeof index !== 'number') {\n var uint32Index = index >>> 0; // N >>> 0 is shorthand for ToUint32\n if ('' + uint32Index !== index || uint32Index === 4294967295) {\n return NaN;\n }\n index = uint32Index;\n }\n return index < 0 ? ensureSize(iter) + index : index;\n }\n\n function returnTrue() {\n return true;\n }\n\n function wholeSlice(begin, end, size) {\n return (begin === 0 || (size !== undefined && begin <= -size)) &&\n (end === undefined || (size !== undefined && end >= size));\n }\n\n function resolveBegin(begin, size) {\n return resolveIndex(begin, size, 0);\n }\n\n function resolveEnd(end, size) {\n return resolveIndex(end, size, size);\n }\n\n function resolveIndex(index, size, defaultIndex) {\n return index === undefined ?\n defaultIndex :\n index < 0 ?\n Math.max(0, size + index) :\n size === undefined ?\n index :\n Math.min(size, index);\n }\n\n /* global Symbol */\n\n var ITERATE_KEYS = 0;\n var ITERATE_VALUES = 1;\n var ITERATE_ENTRIES = 2;\n\n var REAL_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;\n var FAUX_ITERATOR_SYMBOL = '@@iterator';\n\n var ITERATOR_SYMBOL = REAL_ITERATOR_SYMBOL || FAUX_ITERATOR_SYMBOL;\n\n\n function Iterator(next) {\n this.next = next;\n }\n\n Iterator.prototype.toString = function() {\n return '[Iterator]';\n };\n\n\n Iterator.KEYS = ITERATE_KEYS;\n Iterator.VALUES = ITERATE_VALUES;\n Iterator.ENTRIES = ITERATE_ENTRIES;\n\n Iterator.prototype.inspect =\n Iterator.prototype.toSource = function () { return this.toString(); }\n Iterator.prototype[ITERATOR_SYMBOL] = function () {\n return this;\n };\n\n\n function iteratorValue(type, k, v, iteratorResult) {\n var value = type === 0 ? k : type === 1 ? v : [k, v];\n iteratorResult ? (iteratorResult.value = value) : (iteratorResult = {\n value: value, done: false\n });\n return iteratorResult;\n }\n\n function iteratorDone() {\n return { value: undefined, done: true };\n }\n\n function hasIterator(maybeIterable) {\n return !!getIteratorFn(maybeIterable);\n }\n\n function isIterator(maybeIterator) {\n return maybeIterator && typeof maybeIterator.next === 'function';\n }\n\n function getIterator(iterable) {\n var iteratorFn = getIteratorFn(iterable);\n return iteratorFn && iteratorFn.call(iterable);\n }\n\n function getIteratorFn(iterable) {\n var iteratorFn = iterable && (\n (REAL_ITERATOR_SYMBOL && iterable[REAL_ITERATOR_SYMBOL]) ||\n iterable[FAUX_ITERATOR_SYMBOL]\n );\n if (typeof iteratorFn === 'function') {\n return iteratorFn;\n }\n }\n\n function isArrayLike(value) {\n return value && typeof value.length === 'number';\n }\n\n createClass(Seq, Iterable);\n function Seq(value) {\n return value === null || value === undefined ? emptySequence() :\n isIterable(value) ? value.toSeq() : seqFromValue(value);\n }\n\n Seq.of = function(/*...values*/) {\n return Seq(arguments);\n };\n\n Seq.prototype.toSeq = function() {\n return this;\n };\n\n Seq.prototype.toString = function() {\n return this.__toString('Seq {', '}');\n };\n\n Seq.prototype.cacheResult = function() {\n if (!this._cache && this.__iterateUncached) {\n this._cache = this.entrySeq().toArray();\n this.size = this._cache.length;\n }\n return this;\n };\n\n // abstract __iterateUncached(fn, reverse)\n\n Seq.prototype.__iterate = function(fn, reverse) {\n return seqIterate(this, fn, reverse, true);\n };\n\n // abstract __iteratorUncached(type, reverse)\n\n Seq.prototype.__iterator = function(type, reverse) {\n return seqIterator(this, type, reverse, true);\n };\n\n\n\n createClass(KeyedSeq, Seq);\n function KeyedSeq(value) {\n return value === null || value === undefined ?\n emptySequence().toKeyedSeq() :\n isIterable(value) ?\n (isKeyed(value) ? value.toSeq() : value.fromEntrySeq()) :\n keyedSeqFromValue(value);\n }\n\n KeyedSeq.prototype.toKeyedSeq = function() {\n return this;\n };\n\n\n\n createClass(IndexedSeq, Seq);\n function IndexedSeq(value) {\n return value === null || value === undefined ? emptySequence() :\n !isIterable(value) ? indexedSeqFromValue(value) :\n isKeyed(value) ? value.entrySeq() : value.toIndexedSeq();\n }\n\n IndexedSeq.of = function(/*...values*/) {\n return IndexedSeq(arguments);\n };\n\n IndexedSeq.prototype.toIndexedSeq = function() {\n return this;\n };\n\n IndexedSeq.prototype.toString = function() {\n return this.__toString('Seq [', ']');\n };\n\n IndexedSeq.prototype.__iterate = function(fn, reverse) {\n return seqIterate(this, fn, reverse, false);\n };\n\n IndexedSeq.prototype.__iterator = function(type, reverse) {\n return seqIterator(this, type, reverse, false);\n };\n\n\n\n createClass(SetSeq, Seq);\n function SetSeq(value) {\n return (\n value === null || value === undefined ? emptySequence() :\n !isIterable(value) ? indexedSeqFromValue(value) :\n isKeyed(value) ? value.entrySeq() : value\n ).toSetSeq();\n }\n\n SetSeq.of = function(/*...values*/) {\n return SetSeq(arguments);\n };\n\n SetSeq.prototype.toSetSeq = function() {\n return this;\n };\n\n\n\n Seq.isSeq = isSeq;\n Seq.Keyed = KeyedSeq;\n Seq.Set = SetSeq;\n Seq.Indexed = IndexedSeq;\n\n var IS_SEQ_SENTINEL = '@@__IMMUTABLE_SEQ__@@';\n\n Seq.prototype[IS_SEQ_SENTINEL] = true;\n\n\n\n createClass(ArraySeq, IndexedSeq);\n function ArraySeq(array) {\n this._array = array;\n this.size = array.length;\n }\n\n ArraySeq.prototype.get = function(index, notSetValue) {\n return this.has(index) ? this._array[wrapIndex(this, index)] : notSetValue;\n };\n\n ArraySeq.prototype.__iterate = function(fn, reverse) {\n var array = this._array;\n var maxIndex = array.length - 1;\n for (var ii = 0; ii <= maxIndex; ii++) {\n if (fn(array[reverse ? maxIndex - ii : ii], ii, this) === false) {\n return ii + 1;\n }\n }\n return ii;\n };\n\n ArraySeq.prototype.__iterator = function(type, reverse) {\n var array = this._array;\n var maxIndex = array.length - 1;\n var ii = 0;\n return new Iterator(function() \n {return ii > maxIndex ?\n iteratorDone() :\n iteratorValue(type, ii, array[reverse ? maxIndex - ii++ : ii++])}\n );\n };\n\n\n\n createClass(ObjectSeq, KeyedSeq);\n function ObjectSeq(object) {\n var keys = Object.keys(object);\n this._object = object;\n this._keys = keys;\n this.size = keys.length;\n }\n\n ObjectSeq.prototype.get = function(key, notSetValue) {\n if (notSetValue !== undefined && !this.has(key)) {\n return notSetValue;\n }\n return this._object[key];\n };\n\n ObjectSeq.prototype.has = function(key) {\n return this._object.hasOwnProperty(key);\n };\n\n ObjectSeq.prototype.__iterate = function(fn, reverse) {\n var object = this._object;\n var keys = this._keys;\n var maxIndex = keys.length - 1;\n for (var ii = 0; ii <= maxIndex; ii++) {\n var key = keys[reverse ? maxIndex - ii : ii];\n if (fn(object[key], key, this) === false) {\n return ii + 1;\n }\n }\n return ii;\n };\n\n ObjectSeq.prototype.__iterator = function(type, reverse) {\n var object = this._object;\n var keys = this._keys;\n var maxIndex = keys.length - 1;\n var ii = 0;\n return new Iterator(function() {\n var key = keys[reverse ? maxIndex - ii : ii];\n return ii++ > maxIndex ?\n iteratorDone() :\n iteratorValue(type, key, object[key]);\n });\n };\n\n ObjectSeq.prototype[IS_ORDERED_SENTINEL] = true;\n\n\n createClass(IterableSeq, IndexedSeq);\n function IterableSeq(iterable) {\n this._iterable = iterable;\n this.size = iterable.length || iterable.size;\n }\n\n IterableSeq.prototype.__iterateUncached = function(fn, reverse) {\n if (reverse) {\n return this.cacheResult().__iterate(fn, reverse);\n }\n var iterable = this._iterable;\n var iterator = getIterator(iterable);\n var iterations = 0;\n if (isIterator(iterator)) {\n var step;\n while (!(step = iterator.next()).done) {\n if (fn(step.value, iterations++, this) === false) {\n break;\n }\n }\n }\n return iterations;\n };\n\n IterableSeq.prototype.__iteratorUncached = function(type, reverse) {\n if (reverse) {\n return this.cacheResult().__iterator(type, reverse);\n }\n var iterable = this._iterable;\n var iterator = getIterator(iterable);\n if (!isIterator(iterator)) {\n return new Iterator(iteratorDone);\n }\n var iterations = 0;\n return new Iterator(function() {\n var step = iterator.next();\n return step.done ? step : iteratorValue(type, iterations++, step.value);\n });\n };\n\n\n\n createClass(IteratorSeq, IndexedSeq);\n function IteratorSeq(iterator) {\n this._iterator = iterator;\n this._iteratorCache = [];\n }\n\n IteratorSeq.prototype.__iterateUncached = function(fn, reverse) {\n if (reverse) {\n return this.cacheResult().__iterate(fn, reverse);\n }\n var iterator = this._iterator;\n var cache = this._iteratorCache;\n var iterations = 0;\n while (iterations < cache.length) {\n if (fn(cache[iterations], iterations++, this) === false) {\n return iterations;\n }\n }\n var step;\n while (!(step = iterator.next()).done) {\n var val = step.value;\n cache[iterations] = val;\n if (fn(val, iterations++, this) === false) {\n break;\n }\n }\n return iterations;\n };\n\n IteratorSeq.prototype.__iteratorUncached = function(type, reverse) {\n if (reverse) {\n return this.cacheResult().__iterator(type, reverse);\n }\n var iterator = this._iterator;\n var cache = this._iteratorCache;\n var iterations = 0;\n return new Iterator(function() {\n if (iterations >= cache.length) {\n var step = iterator.next();\n if (step.done) {\n return step;\n }\n cache[iterations] = step.value;\n }\n return iteratorValue(type, iterations, cache[iterations++]);\n });\n };\n\n\n\n\n // # pragma Helper functions\n\n function isSeq(maybeSeq) {\n return !!(maybeSeq && maybeSeq[IS_SEQ_SENTINEL]);\n }\n\n var EMPTY_SEQ;\n\n function emptySequence() {\n return EMPTY_SEQ || (EMPTY_SEQ = new ArraySeq([]));\n }\n\n function keyedSeqFromValue(value) {\n var seq =\n Array.isArray(value) ? new ArraySeq(value).fromEntrySeq() :\n isIterator(value) ? new IteratorSeq(value).fromEntrySeq() :\n hasIterator(value) ? new IterableSeq(value).fromEntrySeq() :\n typeof value === 'object' ? new ObjectSeq(value) :\n undefined;\n if (!seq) {\n throw new TypeError(\n 'Expected Array or iterable object of [k, v] entries, '+\n 'or keyed object: ' + value\n );\n }\n return seq;\n }\n\n function indexedSeqFromValue(value) {\n var seq = maybeIndexedSeqFromValue(value);\n if (!seq) {\n throw new TypeError(\n 'Expected Array or iterable object of values: ' + value\n );\n }\n return seq;\n }\n\n function seqFromValue(value) {\n var seq = maybeIndexedSeqFromValue(value) ||\n (typeof value === 'object' && new ObjectSeq(value));\n if (!seq) {\n throw new TypeError(\n 'Expected Array or iterable object of values, or keyed object: ' + value\n );\n }\n return seq;\n }\n\n function maybeIndexedSeqFromValue(value) {\n return (\n isArrayLike(value) ? new ArraySeq(value) :\n isIterator(value) ? new IteratorSeq(value) :\n hasIterator(value) ? new IterableSeq(value) :\n undefined\n );\n }\n\n function seqIterate(seq, fn, reverse, useKeys) {\n var cache = seq._cache;\n if (cache) {\n var maxIndex = cache.length - 1;\n for (var ii = 0; ii <= maxIndex; ii++) {\n var entry = cache[reverse ? maxIndex - ii : ii];\n if (fn(entry[1], useKeys ? entry[0] : ii, seq) === false) {\n return ii + 1;\n }\n }\n return ii;\n }\n return seq.__iterateUncached(fn, reverse);\n }\n\n function seqIterator(seq, type, reverse, useKeys) {\n var cache = seq._cache;\n if (cache) {\n var maxIndex = cache.length - 1;\n var ii = 0;\n return new Iterator(function() {\n var entry = cache[reverse ? maxIndex - ii : ii];\n return ii++ > maxIndex ?\n iteratorDone() :\n iteratorValue(type, useKeys ? entry[0] : ii - 1, entry[1]);\n });\n }\n return seq.__iteratorUncached(type, reverse);\n }\n\n function fromJS(json, converter) {\n return converter ?\n fromJSWith(converter, json, '', {'': json}) :\n fromJSDefault(json);\n }\n\n function fromJSWith(converter, json, key, parentJSON) {\n if (Array.isArray(json)) {\n return converter.call(parentJSON, key, IndexedSeq(json).map(function(v, k) {return fromJSWith(converter, v, k, json)}));\n }\n if (isPlainObj(json)) {\n return converter.call(parentJSON, key, KeyedSeq(json).map(function(v, k) {return fromJSWith(converter, v, k, json)}));\n }\n return json;\n }\n\n function fromJSDefault(json) {\n if (Array.isArray(json)) {\n return IndexedSeq(json).map(fromJSDefault).toList();\n }\n if (isPlainObj(json)) {\n return KeyedSeq(json).map(fromJSDefault).toMap();\n }\n return json;\n }\n\n function isPlainObj(value) {\n return value && (value.constructor === Object || value.constructor === undefined);\n }\n\n /**\n * An extension of the \"same-value\" algorithm as [described for use by ES6 Map\n * and Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Key_equality)\n *\n * NaN is considered the same as NaN, however -0 and 0 are considered the same\n * value, which is different from the algorithm described by\n * [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).\n *\n * This is extended further to allow Objects to describe the values they\n * represent, by way of `valueOf` or `equals` (and `hashCode`).\n *\n * Note: because of this extension, the key equality of Immutable.Map and the\n * value equality of Immutable.Set will differ from ES6 Map and Set.\n *\n * ### Defining custom values\n *\n * The easiest way to describe the value an object represents is by implementing\n * `valueOf`. For example, `Date` represents a value by returning a unix\n * timestamp for `valueOf`:\n *\n * var date1 = new Date(1234567890000); // Fri Feb 13 2009 ...\n * var date2 = new Date(1234567890000);\n * date1.valueOf(); // 1234567890000\n * assert( date1 !== date2 );\n * assert( Immutable.is( date1, date2 ) );\n *\n * Note: overriding `valueOf` may have other implications if you use this object\n * where JavaScript expects a primitive, such as implicit string coercion.\n *\n * For more complex types, especially collections, implementing `valueOf` may\n * not be performant. An alternative is to implement `equals` and `hashCode`.\n *\n * `equals` takes another object, presumably of similar type, and returns true\n * if the it is equal. Equality is symmetrical, so the same result should be\n * returned if this and the argument are flipped.\n *\n * assert( a.equals(b) === b.equals(a) );\n *\n * `hashCode` returns a 32bit integer number representing the object which will\n * be used to determine how to store the value object in a Map or Set. You must\n * provide both or neither methods, one must not exist without the other.\n *\n * Also, an important relationship between these methods must be upheld: if two\n * values are equal, they *must* return the same hashCode. If the values are not\n * equal, they might have the same hashCode; this is called a hash collision,\n * and while undesirable for performance reasons, it is acceptable.\n *\n * if (a.equals(b)) {\n * assert( a.hashCode() === b.hashCode() );\n * }\n *\n * All Immutable collections implement `equals` and `hashCode`.\n *\n */\n function is(valueA, valueB) {\n if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) {\n return true;\n }\n if (!valueA || !valueB) {\n return false;\n }\n if (typeof valueA.valueOf === 'function' &&\n typeof valueB.valueOf === 'function') {\n valueA = valueA.valueOf();\n valueB = valueB.valueOf();\n if (valueA === valueB || (valueA !== valueA && valueB !== valueB)) {\n return true;\n }\n if (!valueA || !valueB) {\n return false;\n }\n }\n if (typeof valueA.equals === 'function' &&\n typeof valueB.equals === 'function' &&\n valueA.equals(valueB)) {\n return true;\n }\n return false;\n }\n\n function deepEqual(a, b) {\n if (a === b) {\n return true;\n }\n\n if (\n !isIterable(b) ||\n a.size !== undefined && b.size !== undefined && a.size !== b.size ||\n a.__hash !== undefined && b.__hash !== undefined && a.__hash !== b.__hash ||\n isKeyed(a) !== isKeyed(b) ||\n isIndexed(a) !== isIndexed(b) ||\n isOrdered(a) !== isOrdered(b)\n ) {\n return false;\n }\n\n if (a.size === 0 && b.size === 0) {\n return true;\n }\n\n var notAssociative = !isAssociative(a);\n\n if (isOrdered(a)) {\n var entries = a.entries();\n return b.every(function(v, k) {\n var entry = entries.next().value;\n return entry && is(entry[1], v) && (notAssociative || is(entry[0], k));\n }) && entries.next().done;\n }\n\n var flipped = false;\n\n if (a.size === undefined) {\n if (b.size === undefined) {\n if (typeof a.cacheResult === 'function') {\n a.cacheResult();\n }\n } else {\n flipped = true;\n var _ = a;\n a = b;\n b = _;\n }\n }\n\n var allEqual = true;\n var bSize = b.__iterate(function(v, k) {\n if (notAssociative ? !a.has(v) :\n flipped ? !is(v, a.get(k, NOT_SET)) : !is(a.get(k, NOT_SET), v)) {\n allEqual = false;\n return false;\n }\n });\n\n return allEqual && a.size === bSize;\n }\n\n createClass(Repeat, IndexedSeq);\n\n function Repeat(value, times) {\n if (!(this instanceof Repeat)) {\n return new Repeat(value, times);\n }\n this._value = value;\n this.size = times === undefined ? Infinity : Math.max(0, times);\n if (this.size === 0) {\n if (EMPTY_REPEAT) {\n return EMPTY_REPEAT;\n }\n EMPTY_REPEAT = this;\n }\n }\n\n Repeat.prototype.toString = function() {\n if (this.size === 0) {\n return 'Repeat []';\n }\n return 'Repeat [ ' + this._value + ' ' + this.size + ' times ]';\n };\n\n Repeat.prototype.get = function(index, notSetValue) {\n return this.has(index) ? this._value : notSetValue;\n };\n\n Repeat.prototype.includes = function(searchValue) {\n return is(this._value, searchValue);\n };\n\n Repeat.prototype.slice = function(begin, end) {\n var size = this.size;\n return wholeSlice(begin, end, size) ? this :\n new Repeat(this._value, resolveEnd(end, size) - resolveBegin(begin, size));\n };\n\n Repeat.prototype.reverse = function() {\n return this;\n };\n\n Repeat.prototype.indexOf = function(searchValue) {\n if (is(this._value, searchValue)) {\n return 0;\n }\n return -1;\n };\n\n Repeat.prototype.lastIndexOf = function(searchValue) {\n if (is(this._value, searchValue)) {\n return this.size;\n }\n return -1;\n };\n\n Repeat.prototype.__iterate = function(fn, reverse) {\n for (var ii = 0; ii < this.size; ii++) {\n if (fn(this._value, ii, this) === false) {\n return ii + 1;\n }\n }\n return ii;\n };\n\n Repeat.prototype.__iterator = function(type, reverse) {var this$0 = this;\n var ii = 0;\n return new Iterator(function() \n {return ii < this$0.size ? iteratorValue(type, ii++, this$0._value) : iteratorDone()}\n );\n };\n\n Repeat.prototype.equals = function(other) {\n return other instanceof Repeat ?\n is(this._value, other._value) :\n deepEqual(other);\n };\n\n\n var EMPTY_REPEAT;\n\n function invariant(condition, error) {\n if (!condition) throw new Error(error);\n }\n\n createClass(Range, IndexedSeq);\n\n function Range(start, end, step) {\n if (!(this instanceof Range)) {\n return new Range(start, end, step);\n }\n invariant(step !== 0, 'Cannot step a Range by 0');\n start = start || 0;\n if (end === undefined) {\n end = Infinity;\n }\n step = step === undefined ? 1 : Math.abs(step);\n if (end < start) {\n step = -step;\n }\n this._start = start;\n this._end = end;\n this._step = step;\n this.size = Math.max(0, Math.ceil((end - start) / step - 1) + 1);\n if (this.size === 0) {\n if (EMPTY_RANGE) {\n return EMPTY_RANGE;\n }\n EMPTY_RANGE = this;\n }\n }\n\n Range.prototype.toString = function() {\n if (this.size === 0) {\n return 'Range []';\n }\n return 'Range [ ' +\n this._start + '...' + this._end +\n (this._step !== 1 ? ' by ' + this._step : '') +\n ' ]';\n };\n\n Range.prototype.get = function(index, notSetValue) {\n return this.has(index) ?\n this._start + wrapIndex(this, index) * this._step :\n notSetValue;\n };\n\n Range.prototype.includes = function(searchValue) {\n var possibleIndex = (searchValue - this._start) / this._step;\n return possibleIndex >= 0 &&\n possibleIndex < this.size &&\n possibleIndex === Math.floor(possibleIndex);\n };\n\n Range.prototype.slice = function(begin, end) {\n if (wholeSlice(begin, end, this.size)) {\n return this;\n }\n begin = resolveBegin(begin, this.size);\n end = resolveEnd(end, this.size);\n if (end <= begin) {\n return new Range(0, 0);\n }\n return new Range(this.get(begin, this._end), this.get(end, this._end), this._step);\n };\n\n Range.prototype.indexOf = function(searchValue) {\n var offsetValue = searchValue - this._start;\n if (offsetValue % this._step === 0) {\n var index = offsetValue / this._step;\n if (index >= 0 && index < this.size) {\n return index\n }\n }\n return -1;\n };\n\n Range.prototype.lastIndexOf = function(searchValue) {\n return this.indexOf(searchValue);\n };\n\n Range.prototype.__iterate = function(fn, reverse) {\n var maxIndex = this.size - 1;\n var step = this._step;\n var value = reverse ? this._start + maxIndex * step : this._start;\n for (var ii = 0; ii <= maxIndex; ii++) {\n if (fn(value, ii, this) === false) {\n return ii + 1;\n }\n value += reverse ? -step : step;\n }\n return ii;\n };\n\n Range.prototype.__iterator = function(type, reverse) {\n var maxIndex = this.size - 1;\n var step = this._step;\n var value = reverse ? this._start + maxIndex * step : this._start;\n var ii = 0;\n return new Iterator(function() {\n var v = value;\n value += reverse ? -step : step;\n return ii > maxIndex ? iteratorDone() : iteratorValue(type, ii++, v);\n });\n };\n\n Range.prototype.equals = function(other) {\n return other instanceof Range ?\n this._start === other._start &&\n this._end === other._end &&\n this._step === other._step :\n deepEqual(this, other);\n };\n\n\n var EMPTY_RANGE;\n\n createClass(Collection, Iterable);\n function Collection() {\n throw TypeError('Abstract');\n }\n\n\n createClass(KeyedCollection, Collection);function KeyedCollection() {}\n\n createClass(IndexedCollection, Collection);function IndexedCollection() {}\n\n createClass(SetCollection, Collection);function SetCollection() {}\n\n\n Collection.Keyed = KeyedCollection;\n Collection.Indexed = IndexedCollection;\n Collection.Set = SetCollection;\n\n var imul =\n typeof Math.imul === 'function' && Math.imul(0xffffffff, 2) === -2 ?\n Math.imul :\n function imul(a, b) {\n a = a | 0; // int\n b = b | 0; // int\n var c = a & 0xffff;\n var d = b & 0xffff;\n // Shift by 0 fixes the sign on the high part.\n return (c * d) + ((((a >>> 16) * d + c * (b >>> 16)) << 16) >>> 0) | 0; // int\n };\n\n // v8 has an optimization for storing 31-bit signed numbers.\n // Values which have either 00 or 11 as the high order bits qualify.\n // This function drops the highest order bit in a signed number, maintaining\n // the sign bit.\n function smi(i32) {\n return ((i32 >>> 1) & 0x40000000) | (i32 & 0xBFFFFFFF);\n }\n\n function hash(o) {\n if (o === false || o === null || o === undefined) {\n return 0;\n }\n if (typeof o.valueOf === 'function') {\n o = o.valueOf();\n if (o === false || o === null || o === undefined) {\n return 0;\n }\n }\n if (o === true) {\n return 1;\n }\n var type = typeof o;\n if (type === 'number') {\n if (o !== o || o === Infinity) {\n return 0;\n }\n var h = o | 0;\n if (h !== o) {\n h ^= o * 0xFFFFFFFF;\n }\n while (o > 0xFFFFFFFF) {\n o /= 0xFFFFFFFF;\n h ^= o;\n }\n return smi(h);\n }\n if (type === 'string') {\n return o.length > STRING_HASH_CACHE_MIN_STRLEN ? cachedHashString(o) : hashString(o);\n }\n if (typeof o.hashCode === 'function') {\n return o.hashCode();\n }\n if (type === 'object') {\n return hashJSObj(o);\n }\n if (typeof o.toString === 'function') {\n return hashString(o.toString());\n }\n throw new Error('Value type ' + type + ' cannot be hashed.');\n }\n\n function cachedHashString(string) {\n var hash = stringHashCache[string];\n if (hash === undefined) {\n hash = hashString(string);\n if (STRING_HASH_CACHE_SIZE === STRING_HASH_CACHE_MAX_SIZE) {\n STRING_HASH_CACHE_SIZE = 0;\n stringHashCache = {};\n }\n STRING_HASH_CACHE_SIZE++;\n stringHashCache[string] = hash;\n }\n return hash;\n }\n\n // http://jsperf.com/hashing-strings\n function hashString(string) {\n // This is the hash from JVM\n // The hash code for a string is computed as\n // s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],\n // where s[i] is the ith character of the string and n is the length of\n // the string. We \"mod\" the result to make it between 0 (inclusive) and 2^31\n // (exclusive) by dropping high bits.\n var hash = 0;\n for (var ii = 0; ii < string.length; ii++) {\n hash = 31 * hash + string.charCodeAt(ii) | 0;\n }\n return smi(hash);\n }\n\n function hashJSObj(obj) {\n var hash;\n if (usingWeakMap) {\n hash = weakMap.get(obj);\n if (hash !== undefined) {\n return hash;\n }\n }\n\n hash = obj[UID_HASH_KEY];\n if (hash !== undefined) {\n return hash;\n }\n\n if (!canDefineProperty) {\n hash = obj.propertyIsEnumerable && obj.propertyIsEnumerable[UID_HASH_KEY];\n if (hash !== undefined) {\n return hash;\n }\n\n hash = getIENodeHash(obj);\n if (hash !== undefined) {\n return hash;\n }\n }\n\n hash = ++objHashUID;\n if (objHashUID & 0x40000000) {\n objHashUID = 0;\n }\n\n if (usingWeakMap) {\n weakMap.set(obj, hash);\n } else if (isExtensible !== undefined && isExtensible(obj) === false) {\n throw new Error('Non-extensible objects are not allowed as keys.');\n } else if (canDefineProperty) {\n Object.defineProperty(obj, UID_HASH_KEY, {\n 'enumerable': false,\n 'configurable': false,\n 'writable': false,\n 'value': hash\n });\n } else if (obj.propertyIsEnumerable !== undefined &&\n obj.propertyIsEnumerable === obj.constructor.prototype.propertyIsEnumerable) {\n // Since we can't define a non-enumerable property on the object\n // we'll hijack one of the less-used non-enumerable properties to\n // save our hash on it. Since this is a function it will not show up in\n // `JSON.stringify` which is what we want.\n obj.propertyIsEnumerable = function() {\n return this.constructor.prototype.propertyIsEnumerable.apply(this, arguments);\n };\n obj.propertyIsEnumerable[UID_HASH_KEY] = hash;\n } else if (obj.nodeType !== undefined) {\n // At this point we couldn't get the IE `uniqueID` to use as a hash\n // and we couldn't use a non-enumerable property to exploit the\n // dontEnum bug so we simply add the `UID_HASH_KEY` on the node\n // itself.\n obj[UID_HASH_KEY] = hash;\n } else {\n throw new Error('Unable to set a non-enumerable property on object.');\n }\n\n return hash;\n }\n\n // Get references to ES5 object methods.\n var isExtensible = Object.isExtensible;\n\n // True if Object.defineProperty works as expected. IE8 fails this test.\n var canDefineProperty = (function() {\n try {\n Object.defineProperty({}, '@', {});\n return true;\n } catch (e) {\n return false;\n }\n }());\n\n // IE has a `uniqueID` property on DOM nodes. We can construct the hash from it\n // and avoid memory leaks from the IE cloneNode bug.\n function getIENodeHash(node) {\n if (node && node.nodeType > 0) {\n switch (node.nodeType) {\n case 1: // Element\n return node.uniqueID;\n case 9: // Document\n return node.documentElement && node.documentElement.uniqueID;\n }\n }\n }\n\n // If possible, use a WeakMap.\n var usingWeakMap = typeof WeakMap === 'function';\n var weakMap;\n if (usingWeakMap) {\n weakMap = new WeakMap();\n }\n\n var objHashUID = 0;\n\n var UID_HASH_KEY = '__immutablehash__';\n if (typeof Symbol === 'function') {\n UID_HASH_KEY = Symbol(UID_HASH_KEY);\n }\n\n var STRING_HASH_CACHE_MIN_STRLEN = 16;\n var STRING_HASH_CACHE_MAX_SIZE = 255;\n var STRING_HASH_CACHE_SIZE = 0;\n var stringHashCache = {};\n\n function assertNotInfinite(size) {\n invariant(\n size !== Infinity,\n 'Cannot perform this action with an infinite size.'\n );\n }\n\n createClass(Map, KeyedCollection);\n\n // @pragma Construction\n\n function Map(value) {\n return value === null || value === undefined ? emptyMap() :\n isMap(value) && !isOrdered(value) ? value :\n emptyMap().withMutations(function(map ) {\n var iter = KeyedIterable(value);\n assertNotInfinite(iter.size);\n iter.forEach(function(v, k) {return map.set(k, v)});\n });\n }\n\n Map.of = function() {var keyValues = SLICE$0.call(arguments, 0);\n return emptyMap().withMutations(function(map ) {\n for (var i = 0; i < keyValues.length; i += 2) {\n if (i + 1 >= keyValues.length) {\n throw new Error('Missing value for key: ' + keyValues[i]);\n }\n map.set(keyValues[i], keyValues[i + 1]);\n }\n });\n };\n\n Map.prototype.toString = function() {\n return this.__toString('Map {', '}');\n };\n\n // @pragma Access\n\n Map.prototype.get = function(k, notSetValue) {\n return this._root ?\n this._root.get(0, undefined, k, notSetValue) :\n notSetValue;\n };\n\n // @pragma Modification\n\n Map.prototype.set = function(k, v) {\n return updateMap(this, k, v);\n };\n\n Map.prototype.setIn = function(keyPath, v) {\n return this.updateIn(keyPath, NOT_SET, function() {return v});\n };\n\n Map.prototype.remove = function(k) {\n return updateMap(this, k, NOT_SET);\n };\n\n Map.prototype.deleteIn = function(keyPath) {\n return this.updateIn(keyPath, function() {return NOT_SET});\n };\n\n Map.prototype.update = function(k, notSetValue, updater) {\n return arguments.length === 1 ?\n k(this) :\n this.updateIn([k], notSetValue, updater);\n };\n\n Map.prototype.updateIn = function(keyPath, notSetValue, updater) {\n if (!updater) {\n updater = notSetValue;\n notSetValue = undefined;\n }\n var updatedValue = updateInDeepMap(\n this,\n forceIterator(keyPath),\n notSetValue,\n updater\n );\n return updatedValue === NOT_SET ? undefined : updatedValue;\n };\n\n Map.prototype.clear = function() {\n if (this.size === 0) {\n return this;\n }\n if (this.__ownerID) {\n this.size = 0;\n this._root = null;\n this.__hash = undefined;\n this.__altered = true;\n return this;\n }\n return emptyMap();\n };\n\n // @pragma Composition\n\n Map.prototype.merge = function(/*...iters*/) {\n return mergeIntoMapWith(this, undefined, arguments);\n };\n\n Map.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1);\n return mergeIntoMapWith(this, merger, iters);\n };\n\n Map.prototype.mergeIn = function(keyPath) {var iters = SLICE$0.call(arguments, 1);\n return this.updateIn(\n keyPath,\n emptyMap(),\n function(m ) {return typeof m.merge === 'function' ?\n m.merge.apply(m, iters) :\n iters[iters.length - 1]}\n );\n };\n\n Map.prototype.mergeDeep = function(/*...iters*/) {\n return mergeIntoMapWith(this, deepMerger, arguments);\n };\n\n Map.prototype.mergeDeepWith = function(merger) {var iters = SLICE$0.call(arguments, 1);\n return mergeIntoMapWith(this, deepMergerWith(merger), iters);\n };\n\n Map.prototype.mergeDeepIn = function(keyPath) {var iters = SLICE$0.call(arguments, 1);\n return this.updateIn(\n keyPath,\n emptyMap(),\n function(m ) {return typeof m.mergeDeep === 'function' ?\n m.mergeDeep.apply(m, iters) :\n iters[iters.length - 1]}\n );\n };\n\n Map.prototype.sort = function(comparator) {\n // Late binding\n return OrderedMap(sortFactory(this, comparator));\n };\n\n Map.prototype.sortBy = function(mapper, comparator) {\n // Late binding\n return OrderedMap(sortFactory(this, comparator, mapper));\n };\n\n // @pragma Mutability\n\n Map.prototype.withMutations = function(fn) {\n var mutable = this.asMutable();\n fn(mutable);\n return mutable.wasAltered() ? mutable.__ensureOwner(this.__ownerID) : this;\n };\n\n Map.prototype.asMutable = function() {\n return this.__ownerID ? this : this.__ensureOwner(new OwnerID());\n };\n\n Map.prototype.asImmutable = function() {\n return this.__ensureOwner();\n };\n\n Map.prototype.wasAltered = function() {\n return this.__altered;\n };\n\n Map.prototype.__iterator = function(type, reverse) {\n return new MapIterator(this, type, reverse);\n };\n\n Map.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n var iterations = 0;\n this._root && this._root.iterate(function(entry ) {\n iterations++;\n return fn(entry[1], entry[0], this$0);\n }, reverse);\n return iterations;\n };\n\n Map.prototype.__ensureOwner = function(ownerID) {\n if (ownerID === this.__ownerID) {\n return this;\n }\n if (!ownerID) {\n this.__ownerID = ownerID;\n this.__altered = false;\n return this;\n }\n return makeMap(this.size, this._root, ownerID, this.__hash);\n };\n\n\n function isMap(maybeMap) {\n return !!(maybeMap && maybeMap[IS_MAP_SENTINEL]);\n }\n\n Map.isMap = isMap;\n\n var IS_MAP_SENTINEL = '@@__IMMUTABLE_MAP__@@';\n\n var MapPrototype = Map.prototype;\n MapPrototype[IS_MAP_SENTINEL] = true;\n MapPrototype[DELETE] = MapPrototype.remove;\n MapPrototype.removeIn = MapPrototype.deleteIn;\n\n\n // #pragma Trie Nodes\n\n\n\n function ArrayMapNode(ownerID, entries) {\n this.ownerID = ownerID;\n this.entries = entries;\n }\n\n ArrayMapNode.prototype.get = function(shift, keyHash, key, notSetValue) {\n var entries = this.entries;\n for (var ii = 0, len = entries.length; ii < len; ii++) {\n if (is(key, entries[ii][0])) {\n return entries[ii][1];\n }\n }\n return notSetValue;\n };\n\n ArrayMapNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {\n var removed = value === NOT_SET;\n\n var entries = this.entries;\n var idx = 0;\n for (var len = entries.length; idx < len; idx++) {\n if (is(key, entries[idx][0])) {\n break;\n }\n }\n var exists = idx < len;\n\n if (exists ? entries[idx][1] === value : removed) {\n return this;\n }\n\n SetRef(didAlter);\n (removed || !exists) && SetRef(didChangeSize);\n\n if (removed && entries.length === 1) {\n return; // undefined\n }\n\n if (!exists && !removed && entries.length >= MAX_ARRAY_MAP_SIZE) {\n return createNodes(ownerID, entries, key, value);\n }\n\n var isEditable = ownerID && ownerID === this.ownerID;\n var newEntries = isEditable ? entries : arrCopy(entries);\n\n if (exists) {\n if (removed) {\n idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop());\n } else {\n newEntries[idx] = [key, value];\n }\n } else {\n newEntries.push([key, value]);\n }\n\n if (isEditable) {\n this.entries = newEntries;\n return this;\n }\n\n return new ArrayMapNode(ownerID, newEntries);\n };\n\n\n\n\n function BitmapIndexedNode(ownerID, bitmap, nodes) {\n this.ownerID = ownerID;\n this.bitmap = bitmap;\n this.nodes = nodes;\n }\n\n BitmapIndexedNode.prototype.get = function(shift, keyHash, key, notSetValue) {\n if (keyHash === undefined) {\n keyHash = hash(key);\n }\n var bit = (1 << ((shift === 0 ? keyHash : keyHash >>> shift) & MASK));\n var bitmap = this.bitmap;\n return (bitmap & bit) === 0 ? notSetValue :\n this.nodes[popCount(bitmap & (bit - 1))].get(shift + SHIFT, keyHash, key, notSetValue);\n };\n\n BitmapIndexedNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {\n if (keyHash === undefined) {\n keyHash = hash(key);\n }\n var keyHashFrag = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;\n var bit = 1 << keyHashFrag;\n var bitmap = this.bitmap;\n var exists = (bitmap & bit) !== 0;\n\n if (!exists && value === NOT_SET) {\n return this;\n }\n\n var idx = popCount(bitmap & (bit - 1));\n var nodes = this.nodes;\n var node = exists ? nodes[idx] : undefined;\n var newNode = updateNode(node, ownerID, shift + SHIFT, keyHash, key, value, didChangeSize, didAlter);\n\n if (newNode === node) {\n return this;\n }\n\n if (!exists && newNode && nodes.length >= MAX_BITMAP_INDEXED_SIZE) {\n return expandNodes(ownerID, nodes, bitmap, keyHashFrag, newNode);\n }\n\n if (exists && !newNode && nodes.length === 2 && isLeafNode(nodes[idx ^ 1])) {\n return nodes[idx ^ 1];\n }\n\n if (exists && newNode && nodes.length === 1 && isLeafNode(newNode)) {\n return newNode;\n }\n\n var isEditable = ownerID && ownerID === this.ownerID;\n var newBitmap = exists ? newNode ? bitmap : bitmap ^ bit : bitmap | bit;\n var newNodes = exists ? newNode ?\n setIn(nodes, idx, newNode, isEditable) :\n spliceOut(nodes, idx, isEditable) :\n spliceIn(nodes, idx, newNode, isEditable);\n\n if (isEditable) {\n this.bitmap = newBitmap;\n this.nodes = newNodes;\n return this;\n }\n\n return new BitmapIndexedNode(ownerID, newBitmap, newNodes);\n };\n\n\n\n\n function HashArrayMapNode(ownerID, count, nodes) {\n this.ownerID = ownerID;\n this.count = count;\n this.nodes = nodes;\n }\n\n HashArrayMapNode.prototype.get = function(shift, keyHash, key, notSetValue) {\n if (keyHash === undefined) {\n keyHash = hash(key);\n }\n var idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;\n var node = this.nodes[idx];\n return node ? node.get(shift + SHIFT, keyHash, key, notSetValue) : notSetValue;\n };\n\n HashArrayMapNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {\n if (keyHash === undefined) {\n keyHash = hash(key);\n }\n var idx = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;\n var removed = value === NOT_SET;\n var nodes = this.nodes;\n var node = nodes[idx];\n\n if (removed && !node) {\n return this;\n }\n\n var newNode = updateNode(node, ownerID, shift + SHIFT, keyHash, key, value, didChangeSize, didAlter);\n if (newNode === node) {\n return this;\n }\n\n var newCount = this.count;\n if (!node) {\n newCount++;\n } else if (!newNode) {\n newCount--;\n if (newCount < MIN_HASH_ARRAY_MAP_SIZE) {\n return packNodes(ownerID, nodes, newCount, idx);\n }\n }\n\n var isEditable = ownerID && ownerID === this.ownerID;\n var newNodes = setIn(nodes, idx, newNode, isEditable);\n\n if (isEditable) {\n this.count = newCount;\n this.nodes = newNodes;\n return this;\n }\n\n return new HashArrayMapNode(ownerID, newCount, newNodes);\n };\n\n\n\n\n function HashCollisionNode(ownerID, keyHash, entries) {\n this.ownerID = ownerID;\n this.keyHash = keyHash;\n this.entries = entries;\n }\n\n HashCollisionNode.prototype.get = function(shift, keyHash, key, notSetValue) {\n var entries = this.entries;\n for (var ii = 0, len = entries.length; ii < len; ii++) {\n if (is(key, entries[ii][0])) {\n return entries[ii][1];\n }\n }\n return notSetValue;\n };\n\n HashCollisionNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {\n if (keyHash === undefined) {\n keyHash = hash(key);\n }\n\n var removed = value === NOT_SET;\n\n if (keyHash !== this.keyHash) {\n if (removed) {\n return this;\n }\n SetRef(didAlter);\n SetRef(didChangeSize);\n return mergeIntoNode(this, ownerID, shift, keyHash, [key, value]);\n }\n\n var entries = this.entries;\n var idx = 0;\n for (var len = entries.length; idx < len; idx++) {\n if (is(key, entries[idx][0])) {\n break;\n }\n }\n var exists = idx < len;\n\n if (exists ? entries[idx][1] === value : removed) {\n return this;\n }\n\n SetRef(didAlter);\n (removed || !exists) && SetRef(didChangeSize);\n\n if (removed && len === 2) {\n return new ValueNode(ownerID, this.keyHash, entries[idx ^ 1]);\n }\n\n var isEditable = ownerID && ownerID === this.ownerID;\n var newEntries = isEditable ? entries : arrCopy(entries);\n\n if (exists) {\n if (removed) {\n idx === len - 1 ? newEntries.pop() : (newEntries[idx] = newEntries.pop());\n } else {\n newEntries[idx] = [key, value];\n }\n } else {\n newEntries.push([key, value]);\n }\n\n if (isEditable) {\n this.entries = newEntries;\n return this;\n }\n\n return new HashCollisionNode(ownerID, this.keyHash, newEntries);\n };\n\n\n\n\n function ValueNode(ownerID, keyHash, entry) {\n this.ownerID = ownerID;\n this.keyHash = keyHash;\n this.entry = entry;\n }\n\n ValueNode.prototype.get = function(shift, keyHash, key, notSetValue) {\n return is(key, this.entry[0]) ? this.entry[1] : notSetValue;\n };\n\n ValueNode.prototype.update = function(ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {\n var removed = value === NOT_SET;\n var keyMatch = is(key, this.entry[0]);\n if (keyMatch ? value === this.entry[1] : removed) {\n return this;\n }\n\n SetRef(didAlter);\n\n if (removed) {\n SetRef(didChangeSize);\n return; // undefined\n }\n\n if (keyMatch) {\n if (ownerID && ownerID === this.ownerID) {\n this.entry[1] = value;\n return this;\n }\n return new ValueNode(ownerID, this.keyHash, [key, value]);\n }\n\n SetRef(didChangeSize);\n return mergeIntoNode(this, ownerID, shift, hash(key), [key, value]);\n };\n\n\n\n // #pragma Iterators\n\n ArrayMapNode.prototype.iterate =\n HashCollisionNode.prototype.iterate = function (fn, reverse) {\n var entries = this.entries;\n for (var ii = 0, maxIndex = entries.length - 1; ii <= maxIndex; ii++) {\n if (fn(entries[reverse ? maxIndex - ii : ii]) === false) {\n return false;\n }\n }\n }\n\n BitmapIndexedNode.prototype.iterate =\n HashArrayMapNode.prototype.iterate = function (fn, reverse) {\n var nodes = this.nodes;\n for (var ii = 0, maxIndex = nodes.length - 1; ii <= maxIndex; ii++) {\n var node = nodes[reverse ? maxIndex - ii : ii];\n if (node && node.iterate(fn, reverse) === false) {\n return false;\n }\n }\n }\n\n ValueNode.prototype.iterate = function (fn, reverse) {\n return fn(this.entry);\n }\n\n createClass(MapIterator, Iterator);\n\n function MapIterator(map, type, reverse) {\n this._type = type;\n this._reverse = reverse;\n this._stack = map._root && mapIteratorFrame(map._root);\n }\n\n MapIterator.prototype.next = function() {\n var type = this._type;\n var stack = this._stack;\n while (stack) {\n var node = stack.node;\n var index = stack.index++;\n var maxIndex;\n if (node.entry) {\n if (index === 0) {\n return mapIteratorValue(type, node.entry);\n }\n } else if (node.entries) {\n maxIndex = node.entries.length - 1;\n if (index <= maxIndex) {\n return mapIteratorValue(type, node.entries[this._reverse ? maxIndex - index : index]);\n }\n } else {\n maxIndex = node.nodes.length - 1;\n if (index <= maxIndex) {\n var subNode = node.nodes[this._reverse ? maxIndex - index : index];\n if (subNode) {\n if (subNode.entry) {\n return mapIteratorValue(type, subNode.entry);\n }\n stack = this._stack = mapIteratorFrame(subNode, stack);\n }\n continue;\n }\n }\n stack = this._stack = this._stack.__prev;\n }\n return iteratorDone();\n };\n\n\n function mapIteratorValue(type, entry) {\n return iteratorValue(type, entry[0], entry[1]);\n }\n\n function mapIteratorFrame(node, prev) {\n return {\n node: node,\n index: 0,\n __prev: prev\n };\n }\n\n function makeMap(size, root, ownerID, hash) {\n var map = Object.create(MapPrototype);\n map.size = size;\n map._root = root;\n map.__ownerID = ownerID;\n map.__hash = hash;\n map.__altered = false;\n return map;\n }\n\n var EMPTY_MAP;\n function emptyMap() {\n return EMPTY_MAP || (EMPTY_MAP = makeMap(0));\n }\n\n function updateMap(map, k, v) {\n var newRoot;\n var newSize;\n if (!map._root) {\n if (v === NOT_SET) {\n return map;\n }\n newSize = 1;\n newRoot = new ArrayMapNode(map.__ownerID, [[k, v]]);\n } else {\n var didChangeSize = MakeRef(CHANGE_LENGTH);\n var didAlter = MakeRef(DID_ALTER);\n newRoot = updateNode(map._root, map.__ownerID, 0, undefined, k, v, didChangeSize, didAlter);\n if (!didAlter.value) {\n return map;\n }\n newSize = map.size + (didChangeSize.value ? v === NOT_SET ? -1 : 1 : 0);\n }\n if (map.__ownerID) {\n map.size = newSize;\n map._root = newRoot;\n map.__hash = undefined;\n map.__altered = true;\n return map;\n }\n return newRoot ? makeMap(newSize, newRoot) : emptyMap();\n }\n\n function updateNode(node, ownerID, shift, keyHash, key, value, didChangeSize, didAlter) {\n if (!node) {\n if (value === NOT_SET) {\n return node;\n }\n SetRef(didAlter);\n SetRef(didChangeSize);\n return new ValueNode(ownerID, keyHash, [key, value]);\n }\n return node.update(ownerID, shift, keyHash, key, value, didChangeSize, didAlter);\n }\n\n function isLeafNode(node) {\n return node.constructor === ValueNode || node.constructor === HashCollisionNode;\n }\n\n function mergeIntoNode(node, ownerID, shift, keyHash, entry) {\n if (node.keyHash === keyHash) {\n return new HashCollisionNode(ownerID, keyHash, [node.entry, entry]);\n }\n\n var idx1 = (shift === 0 ? node.keyHash : node.keyHash >>> shift) & MASK;\n var idx2 = (shift === 0 ? keyHash : keyHash >>> shift) & MASK;\n\n var newNode;\n var nodes = idx1 === idx2 ?\n [mergeIntoNode(node, ownerID, shift + SHIFT, keyHash, entry)] :\n ((newNode = new ValueNode(ownerID, keyHash, entry)), idx1 < idx2 ? [node, newNode] : [newNode, node]);\n\n return new BitmapIndexedNode(ownerID, (1 << idx1) | (1 << idx2), nodes);\n }\n\n function createNodes(ownerID, entries, key, value) {\n if (!ownerID) {\n ownerID = new OwnerID();\n }\n var node = new ValueNode(ownerID, hash(key), [key, value]);\n for (var ii = 0; ii < entries.length; ii++) {\n var entry = entries[ii];\n node = node.update(ownerID, 0, undefined, entry[0], entry[1]);\n }\n return node;\n }\n\n function packNodes(ownerID, nodes, count, excluding) {\n var bitmap = 0;\n var packedII = 0;\n var packedNodes = new Array(count);\n for (var ii = 0, bit = 1, len = nodes.length; ii < len; ii++, bit <<= 1) {\n var node = nodes[ii];\n if (node !== undefined && ii !== excluding) {\n bitmap |= bit;\n packedNodes[packedII++] = node;\n }\n }\n return new BitmapIndexedNode(ownerID, bitmap, packedNodes);\n }\n\n function expandNodes(ownerID, nodes, bitmap, including, node) {\n var count = 0;\n var expandedNodes = new Array(SIZE);\n for (var ii = 0; bitmap !== 0; ii++, bitmap >>>= 1) {\n expandedNodes[ii] = bitmap & 1 ? nodes[count++] : undefined;\n }\n expandedNodes[including] = node;\n return new HashArrayMapNode(ownerID, count + 1, expandedNodes);\n }\n\n function mergeIntoMapWith(map, merger, iterables) {\n var iters = [];\n for (var ii = 0; ii < iterables.length; ii++) {\n var value = iterables[ii];\n var iter = KeyedIterable(value);\n if (!isIterable(value)) {\n iter = iter.map(function(v ) {return fromJS(v)});\n }\n iters.push(iter);\n }\n return mergeIntoCollectionWith(map, merger, iters);\n }\n\n function deepMerger(existing, value, key) {\n return existing && existing.mergeDeep && isIterable(value) ?\n existing.mergeDeep(value) :\n is(existing, value) ? existing : value;\n }\n\n function deepMergerWith(merger) {\n return function(existing, value, key) {\n if (existing && existing.mergeDeepWith && isIterable(value)) {\n return existing.mergeDeepWith(merger, value);\n }\n var nextValue = merger(existing, value, key);\n return is(existing, nextValue) ? existing : nextValue;\n };\n }\n\n function mergeIntoCollectionWith(collection, merger, iters) {\n iters = iters.filter(function(x ) {return x.size !== 0});\n if (iters.length === 0) {\n return collection;\n }\n if (collection.size === 0 && !collection.__ownerID && iters.length === 1) {\n return collection.constructor(iters[0]);\n }\n return collection.withMutations(function(collection ) {\n var mergeIntoMap = merger ?\n function(value, key) {\n collection.update(key, NOT_SET, function(existing )\n {return existing === NOT_SET ? value : merger(existing, value, key)}\n );\n } :\n function(value, key) {\n collection.set(key, value);\n }\n for (var ii = 0; ii < iters.length; ii++) {\n iters[ii].forEach(mergeIntoMap);\n }\n });\n }\n\n function updateInDeepMap(existing, keyPathIter, notSetValue, updater) {\n var isNotSet = existing === NOT_SET;\n var step = keyPathIter.next();\n if (step.done) {\n var existingValue = isNotSet ? notSetValue : existing;\n var newValue = updater(existingValue);\n return newValue === existingValue ? existing : newValue;\n }\n invariant(\n isNotSet || (existing && existing.set),\n 'invalid keyPath'\n );\n var key = step.value;\n var nextExisting = isNotSet ? NOT_SET : existing.get(key, NOT_SET);\n var nextUpdated = updateInDeepMap(\n nextExisting,\n keyPathIter,\n notSetValue,\n updater\n );\n return nextUpdated === nextExisting ? existing :\n nextUpdated === NOT_SET ? existing.remove(key) :\n (isNotSet ? emptyMap() : existing).set(key, nextUpdated);\n }\n\n function popCount(x) {\n x = x - ((x >> 1) & 0x55555555);\n x = (x & 0x33333333) + ((x >> 2) & 0x33333333);\n x = (x + (x >> 4)) & 0x0f0f0f0f;\n x = x + (x >> 8);\n x = x + (x >> 16);\n return x & 0x7f;\n }\n\n function setIn(array, idx, val, canEdit) {\n var newArray = canEdit ? array : arrCopy(array);\n newArray[idx] = val;\n return newArray;\n }\n\n function spliceIn(array, idx, val, canEdit) {\n var newLen = array.length + 1;\n if (canEdit && idx + 1 === newLen) {\n array[idx] = val;\n return array;\n }\n var newArray = new Array(newLen);\n var after = 0;\n for (var ii = 0; ii < newLen; ii++) {\n if (ii === idx) {\n newArray[ii] = val;\n after = -1;\n } else {\n newArray[ii] = array[ii + after];\n }\n }\n return newArray;\n }\n\n function spliceOut(array, idx, canEdit) {\n var newLen = array.length - 1;\n if (canEdit && idx === newLen) {\n array.pop();\n return array;\n }\n var newArray = new Array(newLen);\n var after = 0;\n for (var ii = 0; ii < newLen; ii++) {\n if (ii === idx) {\n after = 1;\n }\n newArray[ii] = array[ii + after];\n }\n return newArray;\n }\n\n var MAX_ARRAY_MAP_SIZE = SIZE / 4;\n var MAX_BITMAP_INDEXED_SIZE = SIZE / 2;\n var MIN_HASH_ARRAY_MAP_SIZE = SIZE / 4;\n\n createClass(List, IndexedCollection);\n\n // @pragma Construction\n\n function List(value) {\n var empty = emptyList();\n if (value === null || value === undefined) {\n return empty;\n }\n if (isList(value)) {\n return value;\n }\n var iter = IndexedIterable(value);\n var size = iter.size;\n if (size === 0) {\n return empty;\n }\n assertNotInfinite(size);\n if (size > 0 && size < SIZE) {\n return makeList(0, size, SHIFT, null, new VNode(iter.toArray()));\n }\n return empty.withMutations(function(list ) {\n list.setSize(size);\n iter.forEach(function(v, i) {return list.set(i, v)});\n });\n }\n\n List.of = function(/*...values*/) {\n return this(arguments);\n };\n\n List.prototype.toString = function() {\n return this.__toString('List [', ']');\n };\n\n // @pragma Access\n\n List.prototype.get = function(index, notSetValue) {\n index = wrapIndex(this, index);\n if (index >= 0 && index < this.size) {\n index += this._origin;\n var node = listNodeFor(this, index);\n return node && node.array[index & MASK];\n }\n return notSetValue;\n };\n\n // @pragma Modification\n\n List.prototype.set = function(index, value) {\n return updateList(this, index, value);\n };\n\n List.prototype.remove = function(index) {\n return !this.has(index) ? this :\n index === 0 ? this.shift() :\n index === this.size - 1 ? this.pop() :\n this.splice(index, 1);\n };\n\n List.prototype.insert = function(index, value) {\n return this.splice(index, 0, value);\n };\n\n List.prototype.clear = function() {\n if (this.size === 0) {\n return this;\n }\n if (this.__ownerID) {\n this.size = this._origin = this._capacity = 0;\n this._level = SHIFT;\n this._root = this._tail = null;\n this.__hash = undefined;\n this.__altered = true;\n return this;\n }\n return emptyList();\n };\n\n List.prototype.push = function(/*...values*/) {\n var values = arguments;\n var oldSize = this.size;\n return this.withMutations(function(list ) {\n setListBounds(list, 0, oldSize + values.length);\n for (var ii = 0; ii < values.length; ii++) {\n list.set(oldSize + ii, values[ii]);\n }\n });\n };\n\n List.prototype.pop = function() {\n return setListBounds(this, 0, -1);\n };\n\n List.prototype.unshift = function(/*...values*/) {\n var values = arguments;\n return this.withMutations(function(list ) {\n setListBounds(list, -values.length);\n for (var ii = 0; ii < values.length; ii++) {\n list.set(ii, values[ii]);\n }\n });\n };\n\n List.prototype.shift = function() {\n return setListBounds(this, 1);\n };\n\n // @pragma Composition\n\n List.prototype.merge = function(/*...iters*/) {\n return mergeIntoListWith(this, undefined, arguments);\n };\n\n List.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1);\n return mergeIntoListWith(this, merger, iters);\n };\n\n List.prototype.mergeDeep = function(/*...iters*/) {\n return mergeIntoListWith(this, deepMerger, arguments);\n };\n\n List.prototype.mergeDeepWith = function(merger) {var iters = SLICE$0.call(arguments, 1);\n return mergeIntoListWith(this, deepMergerWith(merger), iters);\n };\n\n List.prototype.setSize = function(size) {\n return setListBounds(this, 0, size);\n };\n\n // @pragma Iteration\n\n List.prototype.slice = function(begin, end) {\n var size = this.size;\n if (wholeSlice(begin, end, size)) {\n return this;\n }\n return setListBounds(\n this,\n resolveBegin(begin, size),\n resolveEnd(end, size)\n );\n };\n\n List.prototype.__iterator = function(type, reverse) {\n var index = 0;\n var values = iterateList(this, reverse);\n return new Iterator(function() {\n var value = values();\n return value === DONE ?\n iteratorDone() :\n iteratorValue(type, index++, value);\n });\n };\n\n List.prototype.__iterate = function(fn, reverse) {\n var index = 0;\n var values = iterateList(this, reverse);\n var value;\n while ((value = values()) !== DONE) {\n if (fn(value, index++, this) === false) {\n break;\n }\n }\n return index;\n };\n\n List.prototype.__ensureOwner = function(ownerID) {\n if (ownerID === this.__ownerID) {\n return this;\n }\n if (!ownerID) {\n this.__ownerID = ownerID;\n return this;\n }\n return makeList(this._origin, this._capacity, this._level, this._root, this._tail, ownerID, this.__hash);\n };\n\n\n function isList(maybeList) {\n return !!(maybeList && maybeList[IS_LIST_SENTINEL]);\n }\n\n List.isList = isList;\n\n var IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@';\n\n var ListPrototype = List.prototype;\n ListPrototype[IS_LIST_SENTINEL] = true;\n ListPrototype[DELETE] = ListPrototype.remove;\n ListPrototype.setIn = MapPrototype.setIn;\n ListPrototype.deleteIn =\n ListPrototype.removeIn = MapPrototype.removeIn;\n ListPrototype.update = MapPrototype.update;\n ListPrototype.updateIn = MapPrototype.updateIn;\n ListPrototype.mergeIn = MapPrototype.mergeIn;\n ListPrototype.mergeDeepIn = MapPrototype.mergeDeepIn;\n ListPrototype.withMutations = MapPrototype.withMutations;\n ListPrototype.asMutable = MapPrototype.asMutable;\n ListPrototype.asImmutable = MapPrototype.asImmutable;\n ListPrototype.wasAltered = MapPrototype.wasAltered;\n\n\n\n function VNode(array, ownerID) {\n this.array = array;\n this.ownerID = ownerID;\n }\n\n // TODO: seems like these methods are very similar\n\n VNode.prototype.removeBefore = function(ownerID, level, index) {\n if (index === level ? 1 << level : 0 || this.array.length === 0) {\n return this;\n }\n var originIndex = (index >>> level) & MASK;\n if (originIndex >= this.array.length) {\n return new VNode([], ownerID);\n }\n var removingFirst = originIndex === 0;\n var newChild;\n if (level > 0) {\n var oldChild = this.array[originIndex];\n newChild = oldChild && oldChild.removeBefore(ownerID, level - SHIFT, index);\n if (newChild === oldChild && removingFirst) {\n return this;\n }\n }\n if (removingFirst && !newChild) {\n return this;\n }\n var editable = editableVNode(this, ownerID);\n if (!removingFirst) {\n for (var ii = 0; ii < originIndex; ii++) {\n editable.array[ii] = undefined;\n }\n }\n if (newChild) {\n editable.array[originIndex] = newChild;\n }\n return editable;\n };\n\n VNode.prototype.removeAfter = function(ownerID, level, index) {\n if (index === (level ? 1 << level : 0) || this.array.length === 0) {\n return this;\n }\n var sizeIndex = ((index - 1) >>> level) & MASK;\n if (sizeIndex >= this.array.length) {\n return this;\n }\n\n var newChild;\n if (level > 0) {\n var oldChild = this.array[sizeIndex];\n newChild = oldChild && oldChild.removeAfter(ownerID, level - SHIFT, index);\n if (newChild === oldChild && sizeIndex === this.array.length - 1) {\n return this;\n }\n }\n\n var editable = editableVNode(this, ownerID);\n editable.array.splice(sizeIndex + 1);\n if (newChild) {\n editable.array[sizeIndex] = newChild;\n }\n return editable;\n };\n\n\n\n var DONE = {};\n\n function iterateList(list, reverse) {\n var left = list._origin;\n var right = list._capacity;\n var tailPos = getTailOffset(right);\n var tail = list._tail;\n\n return iterateNodeOrLeaf(list._root, list._level, 0);\n\n function iterateNodeOrLeaf(node, level, offset) {\n return level === 0 ?\n iterateLeaf(node, offset) :\n iterateNode(node, level, offset);\n }\n\n function iterateLeaf(node, offset) {\n var array = offset === tailPos ? tail && tail.array : node && node.array;\n var from = offset > left ? 0 : left - offset;\n var to = right - offset;\n if (to > SIZE) {\n to = SIZE;\n }\n return function() {\n if (from === to) {\n return DONE;\n }\n var idx = reverse ? --to : from++;\n return array && array[idx];\n };\n }\n\n function iterateNode(node, level, offset) {\n var values;\n var array = node && node.array;\n var from = offset > left ? 0 : (left - offset) >> level;\n var to = ((right - offset) >> level) + 1;\n if (to > SIZE) {\n to = SIZE;\n }\n return function() {\n do {\n if (values) {\n var value = values();\n if (value !== DONE) {\n return value;\n }\n values = null;\n }\n if (from === to) {\n return DONE;\n }\n var idx = reverse ? --to : from++;\n values = iterateNodeOrLeaf(\n array && array[idx], level - SHIFT, offset + (idx << level)\n );\n } while (true);\n };\n }\n }\n\n function makeList(origin, capacity, level, root, tail, ownerID, hash) {\n var list = Object.create(ListPrototype);\n list.size = capacity - origin;\n list._origin = origin;\n list._capacity = capacity;\n list._level = level;\n list._root = root;\n list._tail = tail;\n list.__ownerID = ownerID;\n list.__hash = hash;\n list.__altered = false;\n return list;\n }\n\n var EMPTY_LIST;\n function emptyList() {\n return EMPTY_LIST || (EMPTY_LIST = makeList(0, 0, SHIFT));\n }\n\n function updateList(list, index, value) {\n index = wrapIndex(list, index);\n\n if (index !== index) {\n return list;\n }\n\n if (index >= list.size || index < 0) {\n return list.withMutations(function(list ) {\n index < 0 ?\n setListBounds(list, index).set(0, value) :\n setListBounds(list, 0, index + 1).set(index, value)\n });\n }\n\n index += list._origin;\n\n var newTail = list._tail;\n var newRoot = list._root;\n var didAlter = MakeRef(DID_ALTER);\n if (index >= getTailOffset(list._capacity)) {\n newTail = updateVNode(newTail, list.__ownerID, 0, index, value, didAlter);\n } else {\n newRoot = updateVNode(newRoot, list.__ownerID, list._level, index, value, didAlter);\n }\n\n if (!didAlter.value) {\n return list;\n }\n\n if (list.__ownerID) {\n list._root = newRoot;\n list._tail = newTail;\n list.__hash = undefined;\n list.__altered = true;\n return list;\n }\n return makeList(list._origin, list._capacity, list._level, newRoot, newTail);\n }\n\n function updateVNode(node, ownerID, level, index, value, didAlter) {\n var idx = (index >>> level) & MASK;\n var nodeHas = node && idx < node.array.length;\n if (!nodeHas && value === undefined) {\n return node;\n }\n\n var newNode;\n\n if (level > 0) {\n var lowerNode = node && node.array[idx];\n var newLowerNode = updateVNode(lowerNode, ownerID, level - SHIFT, index, value, didAlter);\n if (newLowerNode === lowerNode) {\n return node;\n }\n newNode = editableVNode(node, ownerID);\n newNode.array[idx] = newLowerNode;\n return newNode;\n }\n\n if (nodeHas && node.array[idx] === value) {\n return node;\n }\n\n SetRef(didAlter);\n\n newNode = editableVNode(node, ownerID);\n if (value === undefined && idx === newNode.array.length - 1) {\n newNode.array.pop();\n } else {\n newNode.array[idx] = value;\n }\n return newNode;\n }\n\n function editableVNode(node, ownerID) {\n if (ownerID && node && ownerID === node.ownerID) {\n return node;\n }\n return new VNode(node ? node.array.slice() : [], ownerID);\n }\n\n function listNodeFor(list, rawIndex) {\n if (rawIndex >= getTailOffset(list._capacity)) {\n return list._tail;\n }\n if (rawIndex < 1 << (list._level + SHIFT)) {\n var node = list._root;\n var level = list._level;\n while (node && level > 0) {\n node = node.array[(rawIndex >>> level) & MASK];\n level -= SHIFT;\n }\n return node;\n }\n }\n\n function setListBounds(list, begin, end) {\n // Sanitize begin & end using this shorthand for ToInt32(argument)\n // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32\n if (begin !== undefined) {\n begin = begin | 0;\n }\n if (end !== undefined) {\n end = end | 0;\n }\n var owner = list.__ownerID || new OwnerID();\n var oldOrigin = list._origin;\n var oldCapacity = list._capacity;\n var newOrigin = oldOrigin + begin;\n var newCapacity = end === undefined ? oldCapacity : end < 0 ? oldCapacity + end : oldOrigin + end;\n if (newOrigin === oldOrigin && newCapacity === oldCapacity) {\n return list;\n }\n\n // If it's going to end after it starts, it's empty.\n if (newOrigin >= newCapacity) {\n return list.clear();\n }\n\n var newLevel = list._level;\n var newRoot = list._root;\n\n // New origin might need creating a higher root.\n var offsetShift = 0;\n while (newOrigin + offsetShift < 0) {\n newRoot = new VNode(newRoot && newRoot.array.length ? [undefined, newRoot] : [], owner);\n newLevel += SHIFT;\n offsetShift += 1 << newLevel;\n }\n if (offsetShift) {\n newOrigin += offsetShift;\n oldOrigin += offsetShift;\n newCapacity += offsetShift;\n oldCapacity += offsetShift;\n }\n\n var oldTailOffset = getTailOffset(oldCapacity);\n var newTailOffset = getTailOffset(newCapacity);\n\n // New size might need creating a higher root.\n while (newTailOffset >= 1 << (newLevel + SHIFT)) {\n newRoot = new VNode(newRoot && newRoot.array.length ? [newRoot] : [], owner);\n newLevel += SHIFT;\n }\n\n // Locate or create the new tail.\n var oldTail = list._tail;\n var newTail = newTailOffset < oldTailOffset ?\n listNodeFor(list, newCapacity - 1) :\n newTailOffset > oldTailOffset ? new VNode([], owner) : oldTail;\n\n // Merge Tail into tree.\n if (oldTail && newTailOffset > oldTailOffset && newOrigin < oldCapacity && oldTail.array.length) {\n newRoot = editableVNode(newRoot, owner);\n var node = newRoot;\n for (var level = newLevel; level > SHIFT; level -= SHIFT) {\n var idx = (oldTailOffset >>> level) & MASK;\n node = node.array[idx] = editableVNode(node.array[idx], owner);\n }\n node.array[(oldTailOffset >>> SHIFT) & MASK] = oldTail;\n }\n\n // If the size has been reduced, there's a chance the tail needs to be trimmed.\n if (newCapacity < oldCapacity) {\n newTail = newTail && newTail.removeAfter(owner, 0, newCapacity);\n }\n\n // If the new origin is within the tail, then we do not need a root.\n if (newOrigin >= newTailOffset) {\n newOrigin -= newTailOffset;\n newCapacity -= newTailOffset;\n newLevel = SHIFT;\n newRoot = null;\n newTail = newTail && newTail.removeBefore(owner, 0, newOrigin);\n\n // Otherwise, if the root has been trimmed, garbage collect.\n } else if (newOrigin > oldOrigin || newTailOffset < oldTailOffset) {\n offsetShift = 0;\n\n // Identify the new top root node of the subtree of the old root.\n while (newRoot) {\n var beginIndex = (newOrigin >>> newLevel) & MASK;\n if (beginIndex !== (newTailOffset >>> newLevel) & MASK) {\n break;\n }\n if (beginIndex) {\n offsetShift += (1 << newLevel) * beginIndex;\n }\n newLevel -= SHIFT;\n newRoot = newRoot.array[beginIndex];\n }\n\n // Trim the new sides of the new root.\n if (newRoot && newOrigin > oldOrigin) {\n newRoot = newRoot.removeBefore(owner, newLevel, newOrigin - offsetShift);\n }\n if (newRoot && newTailOffset < oldTailOffset) {\n newRoot = newRoot.removeAfter(owner, newLevel, newTailOffset - offsetShift);\n }\n if (offsetShift) {\n newOrigin -= offsetShift;\n newCapacity -= offsetShift;\n }\n }\n\n if (list.__ownerID) {\n list.size = newCapacity - newOrigin;\n list._origin = newOrigin;\n list._capacity = newCapacity;\n list._level = newLevel;\n list._root = newRoot;\n list._tail = newTail;\n list.__hash = undefined;\n list.__altered = true;\n return list;\n }\n return makeList(newOrigin, newCapacity, newLevel, newRoot, newTail);\n }\n\n function mergeIntoListWith(list, merger, iterables) {\n var iters = [];\n var maxSize = 0;\n for (var ii = 0; ii < iterables.length; ii++) {\n var value = iterables[ii];\n var iter = IndexedIterable(value);\n if (iter.size > maxSize) {\n maxSize = iter.size;\n }\n if (!isIterable(value)) {\n iter = iter.map(function(v ) {return fromJS(v)});\n }\n iters.push(iter);\n }\n if (maxSize > list.size) {\n list = list.setSize(maxSize);\n }\n return mergeIntoCollectionWith(list, merger, iters);\n }\n\n function getTailOffset(size) {\n return size < SIZE ? 0 : (((size - 1) >>> SHIFT) << SHIFT);\n }\n\n createClass(OrderedMap, Map);\n\n // @pragma Construction\n\n function OrderedMap(value) {\n return value === null || value === undefined ? emptyOrderedMap() :\n isOrderedMap(value) ? value :\n emptyOrderedMap().withMutations(function(map ) {\n var iter = KeyedIterable(value);\n assertNotInfinite(iter.size);\n iter.forEach(function(v, k) {return map.set(k, v)});\n });\n }\n\n OrderedMap.of = function(/*...values*/) {\n return this(arguments);\n };\n\n OrderedMap.prototype.toString = function() {\n return this.__toString('OrderedMap {', '}');\n };\n\n // @pragma Access\n\n OrderedMap.prototype.get = function(k, notSetValue) {\n var index = this._map.get(k);\n return index !== undefined ? this._list.get(index)[1] : notSetValue;\n };\n\n // @pragma Modification\n\n OrderedMap.prototype.clear = function() {\n if (this.size === 0) {\n return this;\n }\n if (this.__ownerID) {\n this.size = 0;\n this._map.clear();\n this._list.clear();\n return this;\n }\n return emptyOrderedMap();\n };\n\n OrderedMap.prototype.set = function(k, v) {\n return updateOrderedMap(this, k, v);\n };\n\n OrderedMap.prototype.remove = function(k) {\n return updateOrderedMap(this, k, NOT_SET);\n };\n\n OrderedMap.prototype.wasAltered = function() {\n return this._map.wasAltered() || this._list.wasAltered();\n };\n\n OrderedMap.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n return this._list.__iterate(\n function(entry ) {return entry && fn(entry[1], entry[0], this$0)},\n reverse\n );\n };\n\n OrderedMap.prototype.__iterator = function(type, reverse) {\n return this._list.fromEntrySeq().__iterator(type, reverse);\n };\n\n OrderedMap.prototype.__ensureOwner = function(ownerID) {\n if (ownerID === this.__ownerID) {\n return this;\n }\n var newMap = this._map.__ensureOwner(ownerID);\n var newList = this._list.__ensureOwner(ownerID);\n if (!ownerID) {\n this.__ownerID = ownerID;\n this._map = newMap;\n this._list = newList;\n return this;\n }\n return makeOrderedMap(newMap, newList, ownerID, this.__hash);\n };\n\n\n function isOrderedMap(maybeOrderedMap) {\n return isMap(maybeOrderedMap) && isOrdered(maybeOrderedMap);\n }\n\n OrderedMap.isOrderedMap = isOrderedMap;\n\n OrderedMap.prototype[IS_ORDERED_SENTINEL] = true;\n OrderedMap.prototype[DELETE] = OrderedMap.prototype.remove;\n\n\n\n function makeOrderedMap(map, list, ownerID, hash) {\n var omap = Object.create(OrderedMap.prototype);\n omap.size = map ? map.size : 0;\n omap._map = map;\n omap._list = list;\n omap.__ownerID = ownerID;\n omap.__hash = hash;\n return omap;\n }\n\n var EMPTY_ORDERED_MAP;\n function emptyOrderedMap() {\n return EMPTY_ORDERED_MAP || (EMPTY_ORDERED_MAP = makeOrderedMap(emptyMap(), emptyList()));\n }\n\n function updateOrderedMap(omap, k, v) {\n var map = omap._map;\n var list = omap._list;\n var i = map.get(k);\n var has = i !== undefined;\n var newMap;\n var newList;\n if (v === NOT_SET) { // removed\n if (!has) {\n return omap;\n }\n if (list.size >= SIZE && list.size >= map.size * 2) {\n newList = list.filter(function(entry, idx) {return entry !== undefined && i !== idx});\n newMap = newList.toKeyedSeq().map(function(entry ) {return entry[0]}).flip().toMap();\n if (omap.__ownerID) {\n newMap.__ownerID = newList.__ownerID = omap.__ownerID;\n }\n } else {\n newMap = map.remove(k);\n newList = i === list.size - 1 ? list.pop() : list.set(i, undefined);\n }\n } else {\n if (has) {\n if (v === list.get(i)[1]) {\n return omap;\n }\n newMap = map;\n newList = list.set(i, [k, v]);\n } else {\n newMap = map.set(k, list.size);\n newList = list.set(list.size, [k, v]);\n }\n }\n if (omap.__ownerID) {\n omap.size = newMap.size;\n omap._map = newMap;\n omap._list = newList;\n omap.__hash = undefined;\n return omap;\n }\n return makeOrderedMap(newMap, newList);\n }\n\n createClass(ToKeyedSequence, KeyedSeq);\n function ToKeyedSequence(indexed, useKeys) {\n this._iter = indexed;\n this._useKeys = useKeys;\n this.size = indexed.size;\n }\n\n ToKeyedSequence.prototype.get = function(key, notSetValue) {\n return this._iter.get(key, notSetValue);\n };\n\n ToKeyedSequence.prototype.has = function(key) {\n return this._iter.has(key);\n };\n\n ToKeyedSequence.prototype.valueSeq = function() {\n return this._iter.valueSeq();\n };\n\n ToKeyedSequence.prototype.reverse = function() {var this$0 = this;\n var reversedSequence = reverseFactory(this, true);\n if (!this._useKeys) {\n reversedSequence.valueSeq = function() {return this$0._iter.toSeq().reverse()};\n }\n return reversedSequence;\n };\n\n ToKeyedSequence.prototype.map = function(mapper, context) {var this$0 = this;\n var mappedSequence = mapFactory(this, mapper, context);\n if (!this._useKeys) {\n mappedSequence.valueSeq = function() {return this$0._iter.toSeq().map(mapper, context)};\n }\n return mappedSequence;\n };\n\n ToKeyedSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n var ii;\n return this._iter.__iterate(\n this._useKeys ?\n function(v, k) {return fn(v, k, this$0)} :\n ((ii = reverse ? resolveSize(this) : 0),\n function(v ) {return fn(v, reverse ? --ii : ii++, this$0)}),\n reverse\n );\n };\n\n ToKeyedSequence.prototype.__iterator = function(type, reverse) {\n if (this._useKeys) {\n return this._iter.__iterator(type, reverse);\n }\n var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);\n var ii = reverse ? resolveSize(this) : 0;\n return new Iterator(function() {\n var step = iterator.next();\n return step.done ? step :\n iteratorValue(type, reverse ? --ii : ii++, step.value, step);\n });\n };\n\n ToKeyedSequence.prototype[IS_ORDERED_SENTINEL] = true;\n\n\n createClass(ToIndexedSequence, IndexedSeq);\n function ToIndexedSequence(iter) {\n this._iter = iter;\n this.size = iter.size;\n }\n\n ToIndexedSequence.prototype.includes = function(value) {\n return this._iter.includes(value);\n };\n\n ToIndexedSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n var iterations = 0;\n return this._iter.__iterate(function(v ) {return fn(v, iterations++, this$0)}, reverse);\n };\n\n ToIndexedSequence.prototype.__iterator = function(type, reverse) {\n var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);\n var iterations = 0;\n return new Iterator(function() {\n var step = iterator.next();\n return step.done ? step :\n iteratorValue(type, iterations++, step.value, step)\n });\n };\n\n\n\n createClass(ToSetSequence, SetSeq);\n function ToSetSequence(iter) {\n this._iter = iter;\n this.size = iter.size;\n }\n\n ToSetSequence.prototype.has = function(key) {\n return this._iter.includes(key);\n };\n\n ToSetSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n return this._iter.__iterate(function(v ) {return fn(v, v, this$0)}, reverse);\n };\n\n ToSetSequence.prototype.__iterator = function(type, reverse) {\n var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);\n return new Iterator(function() {\n var step = iterator.next();\n return step.done ? step :\n iteratorValue(type, step.value, step.value, step);\n });\n };\n\n\n\n createClass(FromEntriesSequence, KeyedSeq);\n function FromEntriesSequence(entries) {\n this._iter = entries;\n this.size = entries.size;\n }\n\n FromEntriesSequence.prototype.entrySeq = function() {\n return this._iter.toSeq();\n };\n\n FromEntriesSequence.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n return this._iter.__iterate(function(entry ) {\n // Check if entry exists first so array access doesn't throw for holes\n // in the parent iteration.\n if (entry) {\n validateEntry(entry);\n var indexedIterable = isIterable(entry);\n return fn(\n indexedIterable ? entry.get(1) : entry[1],\n indexedIterable ? entry.get(0) : entry[0],\n this$0\n );\n }\n }, reverse);\n };\n\n FromEntriesSequence.prototype.__iterator = function(type, reverse) {\n var iterator = this._iter.__iterator(ITERATE_VALUES, reverse);\n return new Iterator(function() {\n while (true) {\n var step = iterator.next();\n if (step.done) {\n return step;\n }\n var entry = step.value;\n // Check if entry exists first so array access doesn't throw for holes\n // in the parent iteration.\n if (entry) {\n validateEntry(entry);\n var indexedIterable = isIterable(entry);\n return iteratorValue(\n type,\n indexedIterable ? entry.get(0) : entry[0],\n indexedIterable ? entry.get(1) : entry[1],\n step\n );\n }\n }\n });\n };\n\n\n ToIndexedSequence.prototype.cacheResult =\n ToKeyedSequence.prototype.cacheResult =\n ToSetSequence.prototype.cacheResult =\n FromEntriesSequence.prototype.cacheResult =\n cacheResultThrough;\n\n\n function flipFactory(iterable) {\n var flipSequence = makeSequence(iterable);\n flipSequence._iter = iterable;\n flipSequence.size = iterable.size;\n flipSequence.flip = function() {return iterable};\n flipSequence.reverse = function () {\n var reversedSequence = iterable.reverse.apply(this); // super.reverse()\n reversedSequence.flip = function() {return iterable.reverse()};\n return reversedSequence;\n };\n flipSequence.has = function(key ) {return iterable.includes(key)};\n flipSequence.includes = function(key ) {return iterable.has(key)};\n flipSequence.cacheResult = cacheResultThrough;\n flipSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;\n return iterable.__iterate(function(v, k) {return fn(k, v, this$0) !== false}, reverse);\n }\n flipSequence.__iteratorUncached = function(type, reverse) {\n if (type === ITERATE_ENTRIES) {\n var iterator = iterable.__iterator(type, reverse);\n return new Iterator(function() {\n var step = iterator.next();\n if (!step.done) {\n var k = step.value[0];\n step.value[0] = step.value[1];\n step.value[1] = k;\n }\n return step;\n });\n }\n return iterable.__iterator(\n type === ITERATE_VALUES ? ITERATE_KEYS : ITERATE_VALUES,\n reverse\n );\n }\n return flipSequence;\n }\n\n\n function mapFactory(iterable, mapper, context) {\n var mappedSequence = makeSequence(iterable);\n mappedSequence.size = iterable.size;\n mappedSequence.has = function(key ) {return iterable.has(key)};\n mappedSequence.get = function(key, notSetValue) {\n var v = iterable.get(key, NOT_SET);\n return v === NOT_SET ?\n notSetValue :\n mapper.call(context, v, key, iterable);\n };\n mappedSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;\n return iterable.__iterate(\n function(v, k, c) {return fn(mapper.call(context, v, k, c), k, this$0) !== false},\n reverse\n );\n }\n mappedSequence.__iteratorUncached = function (type, reverse) {\n var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);\n return new Iterator(function() {\n var step = iterator.next();\n if (step.done) {\n return step;\n }\n var entry = step.value;\n var key = entry[0];\n return iteratorValue(\n type,\n key,\n mapper.call(context, entry[1], key, iterable),\n step\n );\n });\n }\n return mappedSequence;\n }\n\n\n function reverseFactory(iterable, useKeys) {\n var reversedSequence = makeSequence(iterable);\n reversedSequence._iter = iterable;\n reversedSequence.size = iterable.size;\n reversedSequence.reverse = function() {return iterable};\n if (iterable.flip) {\n reversedSequence.flip = function () {\n var flipSequence = flipFactory(iterable);\n flipSequence.reverse = function() {return iterable.flip()};\n return flipSequence;\n };\n }\n reversedSequence.get = function(key, notSetValue) \n {return iterable.get(useKeys ? key : -1 - key, notSetValue)};\n reversedSequence.has = function(key )\n {return iterable.has(useKeys ? key : -1 - key)};\n reversedSequence.includes = function(value ) {return iterable.includes(value)};\n reversedSequence.cacheResult = cacheResultThrough;\n reversedSequence.__iterate = function (fn, reverse) {var this$0 = this;\n return iterable.__iterate(function(v, k) {return fn(v, k, this$0)}, !reverse);\n };\n reversedSequence.__iterator =\n function(type, reverse) {return iterable.__iterator(type, !reverse)};\n return reversedSequence;\n }\n\n\n function filterFactory(iterable, predicate, context, useKeys) {\n var filterSequence = makeSequence(iterable);\n if (useKeys) {\n filterSequence.has = function(key ) {\n var v = iterable.get(key, NOT_SET);\n return v !== NOT_SET && !!predicate.call(context, v, key, iterable);\n };\n filterSequence.get = function(key, notSetValue) {\n var v = iterable.get(key, NOT_SET);\n return v !== NOT_SET && predicate.call(context, v, key, iterable) ?\n v : notSetValue;\n };\n }\n filterSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;\n var iterations = 0;\n iterable.__iterate(function(v, k, c) {\n if (predicate.call(context, v, k, c)) {\n iterations++;\n return fn(v, useKeys ? k : iterations - 1, this$0);\n }\n }, reverse);\n return iterations;\n };\n filterSequence.__iteratorUncached = function (type, reverse) {\n var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);\n var iterations = 0;\n return new Iterator(function() {\n while (true) {\n var step = iterator.next();\n if (step.done) {\n return step;\n }\n var entry = step.value;\n var key = entry[0];\n var value = entry[1];\n if (predicate.call(context, value, key, iterable)) {\n return iteratorValue(type, useKeys ? key : iterations++, value, step);\n }\n }\n });\n }\n return filterSequence;\n }\n\n\n function countByFactory(iterable, grouper, context) {\n var groups = Map().asMutable();\n iterable.__iterate(function(v, k) {\n groups.update(\n grouper.call(context, v, k, iterable),\n 0,\n function(a ) {return a + 1}\n );\n });\n return groups.asImmutable();\n }\n\n\n function groupByFactory(iterable, grouper, context) {\n var isKeyedIter = isKeyed(iterable);\n var groups = (isOrdered(iterable) ? OrderedMap() : Map()).asMutable();\n iterable.__iterate(function(v, k) {\n groups.update(\n grouper.call(context, v, k, iterable),\n function(a ) {return (a = a || [], a.push(isKeyedIter ? [k, v] : v), a)}\n );\n });\n var coerce = iterableClass(iterable);\n return groups.map(function(arr ) {return reify(iterable, coerce(arr))});\n }\n\n\n function sliceFactory(iterable, begin, end, useKeys) {\n var originalSize = iterable.size;\n\n // Sanitize begin & end using this shorthand for ToInt32(argument)\n // http://www.ecma-international.org/ecma-262/6.0/#sec-toint32\n if (begin !== undefined) {\n begin = begin | 0;\n }\n if (end !== undefined) {\n if (end === Infinity) {\n end = originalSize;\n } else {\n end = end | 0;\n }\n }\n\n if (wholeSlice(begin, end, originalSize)) {\n return iterable;\n }\n\n var resolvedBegin = resolveBegin(begin, originalSize);\n var resolvedEnd = resolveEnd(end, originalSize);\n\n // begin or end will be NaN if they were provided as negative numbers and\n // this iterable's size is unknown. In that case, cache first so there is\n // a known size and these do not resolve to NaN.\n if (resolvedBegin !== resolvedBegin || resolvedEnd !== resolvedEnd) {\n return sliceFactory(iterable.toSeq().cacheResult(), begin, end, useKeys);\n }\n\n // Note: resolvedEnd is undefined when the original sequence's length is\n // unknown and this slice did not supply an end and should contain all\n // elements after resolvedBegin.\n // In that case, resolvedSize will be NaN and sliceSize will remain undefined.\n var resolvedSize = resolvedEnd - resolvedBegin;\n var sliceSize;\n if (resolvedSize === resolvedSize) {\n sliceSize = resolvedSize < 0 ? 0 : resolvedSize;\n }\n\n var sliceSeq = makeSequence(iterable);\n\n // If iterable.size is undefined, the size of the realized sliceSeq is\n // unknown at this point unless the number of items to slice is 0\n sliceSeq.size = sliceSize === 0 ? sliceSize : iterable.size && sliceSize || undefined;\n\n if (!useKeys && isSeq(iterable) && sliceSize >= 0) {\n sliceSeq.get = function (index, notSetValue) {\n index = wrapIndex(this, index);\n return index >= 0 && index < sliceSize ?\n iterable.get(index + resolvedBegin, notSetValue) :\n notSetValue;\n }\n }\n\n sliceSeq.__iterateUncached = function(fn, reverse) {var this$0 = this;\n if (sliceSize === 0) {\n return 0;\n }\n if (reverse) {\n return this.cacheResult().__iterate(fn, reverse);\n }\n var skipped = 0;\n var isSkipping = true;\n var iterations = 0;\n iterable.__iterate(function(v, k) {\n if (!(isSkipping && (isSkipping = skipped++ < resolvedBegin))) {\n iterations++;\n return fn(v, useKeys ? k : iterations - 1, this$0) !== false &&\n iterations !== sliceSize;\n }\n });\n return iterations;\n };\n\n sliceSeq.__iteratorUncached = function(type, reverse) {\n if (sliceSize !== 0 && reverse) {\n return this.cacheResult().__iterator(type, reverse);\n }\n // Don't bother instantiating parent iterator if taking 0.\n var iterator = sliceSize !== 0 && iterable.__iterator(type, reverse);\n var skipped = 0;\n var iterations = 0;\n return new Iterator(function() {\n while (skipped++ < resolvedBegin) {\n iterator.next();\n }\n if (++iterations > sliceSize) {\n return iteratorDone();\n }\n var step = iterator.next();\n if (useKeys || type === ITERATE_VALUES) {\n return step;\n } else if (type === ITERATE_KEYS) {\n return iteratorValue(type, iterations - 1, undefined, step);\n } else {\n return iteratorValue(type, iterations - 1, step.value[1], step);\n }\n });\n }\n\n return sliceSeq;\n }\n\n\n function takeWhileFactory(iterable, predicate, context) {\n var takeSequence = makeSequence(iterable);\n takeSequence.__iterateUncached = function(fn, reverse) {var this$0 = this;\n if (reverse) {\n return this.cacheResult().__iterate(fn, reverse);\n }\n var iterations = 0;\n iterable.__iterate(function(v, k, c) \n {return predicate.call(context, v, k, c) && ++iterations && fn(v, k, this$0)}\n );\n return iterations;\n };\n takeSequence.__iteratorUncached = function(type, reverse) {var this$0 = this;\n if (reverse) {\n return this.cacheResult().__iterator(type, reverse);\n }\n var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);\n var iterating = true;\n return new Iterator(function() {\n if (!iterating) {\n return iteratorDone();\n }\n var step = iterator.next();\n if (step.done) {\n return step;\n }\n var entry = step.value;\n var k = entry[0];\n var v = entry[1];\n if (!predicate.call(context, v, k, this$0)) {\n iterating = false;\n return iteratorDone();\n }\n return type === ITERATE_ENTRIES ? step :\n iteratorValue(type, k, v, step);\n });\n };\n return takeSequence;\n }\n\n\n function skipWhileFactory(iterable, predicate, context, useKeys) {\n var skipSequence = makeSequence(iterable);\n skipSequence.__iterateUncached = function (fn, reverse) {var this$0 = this;\n if (reverse) {\n return this.cacheResult().__iterate(fn, reverse);\n }\n var isSkipping = true;\n var iterations = 0;\n iterable.__iterate(function(v, k, c) {\n if (!(isSkipping && (isSkipping = predicate.call(context, v, k, c)))) {\n iterations++;\n return fn(v, useKeys ? k : iterations - 1, this$0);\n }\n });\n return iterations;\n };\n skipSequence.__iteratorUncached = function(type, reverse) {var this$0 = this;\n if (reverse) {\n return this.cacheResult().__iterator(type, reverse);\n }\n var iterator = iterable.__iterator(ITERATE_ENTRIES, reverse);\n var skipping = true;\n var iterations = 0;\n return new Iterator(function() {\n var step, k, v;\n do {\n step = iterator.next();\n if (step.done) {\n if (useKeys || type === ITERATE_VALUES) {\n return step;\n } else if (type === ITERATE_KEYS) {\n return iteratorValue(type, iterations++, undefined, step);\n } else {\n return iteratorValue(type, iterations++, step.value[1], step);\n }\n }\n var entry = step.value;\n k = entry[0];\n v = entry[1];\n skipping && (skipping = predicate.call(context, v, k, this$0));\n } while (skipping);\n return type === ITERATE_ENTRIES ? step :\n iteratorValue(type, k, v, step);\n });\n };\n return skipSequence;\n }\n\n\n function concatFactory(iterable, values) {\n var isKeyedIterable = isKeyed(iterable);\n var iters = [iterable].concat(values).map(function(v ) {\n if (!isIterable(v)) {\n v = isKeyedIterable ?\n keyedSeqFromValue(v) :\n indexedSeqFromValue(Array.isArray(v) ? v : [v]);\n } else if (isKeyedIterable) {\n v = KeyedIterable(v);\n }\n return v;\n }).filter(function(v ) {return v.size !== 0});\n\n if (iters.length === 0) {\n return iterable;\n }\n\n if (iters.length === 1) {\n var singleton = iters[0];\n if (singleton === iterable ||\n isKeyedIterable && isKeyed(singleton) ||\n isIndexed(iterable) && isIndexed(singleton)) {\n return singleton;\n }\n }\n\n var concatSeq = new ArraySeq(iters);\n if (isKeyedIterable) {\n concatSeq = concatSeq.toKeyedSeq();\n } else if (!isIndexed(iterable)) {\n concatSeq = concatSeq.toSetSeq();\n }\n concatSeq = concatSeq.flatten(true);\n concatSeq.size = iters.reduce(\n function(sum, seq) {\n if (sum !== undefined) {\n var size = seq.size;\n if (size !== undefined) {\n return sum + size;\n }\n }\n },\n 0\n );\n return concatSeq;\n }\n\n\n function flattenFactory(iterable, depth, useKeys) {\n var flatSequence = makeSequence(iterable);\n flatSequence.__iterateUncached = function(fn, reverse) {\n var iterations = 0;\n var stopped = false;\n function flatDeep(iter, currentDepth) {var this$0 = this;\n iter.__iterate(function(v, k) {\n if ((!depth || currentDepth < depth) && isIterable(v)) {\n flatDeep(v, currentDepth + 1);\n } else if (fn(v, useKeys ? k : iterations++, this$0) === false) {\n stopped = true;\n }\n return !stopped;\n }, reverse);\n }\n flatDeep(iterable, 0);\n return iterations;\n }\n flatSequence.__iteratorUncached = function(type, reverse) {\n var iterator = iterable.__iterator(type, reverse);\n var stack = [];\n var iterations = 0;\n return new Iterator(function() {\n while (iterator) {\n var step = iterator.next();\n if (step.done !== false) {\n iterator = stack.pop();\n continue;\n }\n var v = step.value;\n if (type === ITERATE_ENTRIES) {\n v = v[1];\n }\n if ((!depth || stack.length < depth) && isIterable(v)) {\n stack.push(iterator);\n iterator = v.__iterator(type, reverse);\n } else {\n return useKeys ? step : iteratorValue(type, iterations++, v, step);\n }\n }\n return iteratorDone();\n });\n }\n return flatSequence;\n }\n\n\n function flatMapFactory(iterable, mapper, context) {\n var coerce = iterableClass(iterable);\n return iterable.toSeq().map(\n function(v, k) {return coerce(mapper.call(context, v, k, iterable))}\n ).flatten(true);\n }\n\n\n function interposeFactory(iterable, separator) {\n var interposedSequence = makeSequence(iterable);\n interposedSequence.size = iterable.size && iterable.size * 2 -1;\n interposedSequence.__iterateUncached = function(fn, reverse) {var this$0 = this;\n var iterations = 0;\n iterable.__iterate(function(v, k) \n {return (!iterations || fn(separator, iterations++, this$0) !== false) &&\n fn(v, iterations++, this$0) !== false},\n reverse\n );\n return iterations;\n };\n interposedSequence.__iteratorUncached = function(type, reverse) {\n var iterator = iterable.__iterator(ITERATE_VALUES, reverse);\n var iterations = 0;\n var step;\n return new Iterator(function() {\n if (!step || iterations % 2) {\n step = iterator.next();\n if (step.done) {\n return step;\n }\n }\n return iterations % 2 ?\n iteratorValue(type, iterations++, separator) :\n iteratorValue(type, iterations++, step.value, step);\n });\n };\n return interposedSequence;\n }\n\n\n function sortFactory(iterable, comparator, mapper) {\n if (!comparator) {\n comparator = defaultComparator;\n }\n var isKeyedIterable = isKeyed(iterable);\n var index = 0;\n var entries = iterable.toSeq().map(\n function(v, k) {return [k, v, index++, mapper ? mapper(v, k, iterable) : v]}\n ).toArray();\n entries.sort(function(a, b) {return comparator(a[3], b[3]) || a[2] - b[2]}).forEach(\n isKeyedIterable ?\n function(v, i) { entries[i].length = 2; } :\n function(v, i) { entries[i] = v[1]; }\n );\n return isKeyedIterable ? KeyedSeq(entries) :\n isIndexed(iterable) ? IndexedSeq(entries) :\n SetSeq(entries);\n }\n\n\n function maxFactory(iterable, comparator, mapper) {\n if (!comparator) {\n comparator = defaultComparator;\n }\n if (mapper) {\n var entry = iterable.toSeq()\n .map(function(v, k) {return [v, mapper(v, k, iterable)]})\n .reduce(function(a, b) {return maxCompare(comparator, a[1], b[1]) ? b : a});\n return entry && entry[0];\n } else {\n return iterable.reduce(function(a, b) {return maxCompare(comparator, a, b) ? b : a});\n }\n }\n\n function maxCompare(comparator, a, b) {\n var comp = comparator(b, a);\n // b is considered the new max if the comparator declares them equal, but\n // they are not equal and b is in fact a nullish value.\n return (comp === 0 && b !== a && (b === undefined || b === null || b !== b)) || comp > 0;\n }\n\n\n function zipWithFactory(keyIter, zipper, iters) {\n var zipSequence = makeSequence(keyIter);\n zipSequence.size = new ArraySeq(iters).map(function(i ) {return i.size}).min();\n // Note: this a generic base implementation of __iterate in terms of\n // __iterator which may be more generically useful in the future.\n zipSequence.__iterate = function(fn, reverse) {\n /* generic:\n var iterator = this.__iterator(ITERATE_ENTRIES, reverse);\n var step;\n var iterations = 0;\n while (!(step = iterator.next()).done) {\n iterations++;\n if (fn(step.value[1], step.value[0], this) === false) {\n break;\n }\n }\n return iterations;\n */\n // indexed:\n var iterator = this.__iterator(ITERATE_VALUES, reverse);\n var step;\n var iterations = 0;\n while (!(step = iterator.next()).done) {\n if (fn(step.value, iterations++, this) === false) {\n break;\n }\n }\n return iterations;\n };\n zipSequence.__iteratorUncached = function(type, reverse) {\n var iterators = iters.map(function(i )\n {return (i = Iterable(i), getIterator(reverse ? i.reverse() : i))}\n );\n var iterations = 0;\n var isDone = false;\n return new Iterator(function() {\n var steps;\n if (!isDone) {\n steps = iterators.map(function(i ) {return i.next()});\n isDone = steps.some(function(s ) {return s.done});\n }\n if (isDone) {\n return iteratorDone();\n }\n return iteratorValue(\n type,\n iterations++,\n zipper.apply(null, steps.map(function(s ) {return s.value}))\n );\n });\n };\n return zipSequence\n }\n\n\n // #pragma Helper Functions\n\n function reify(iter, seq) {\n return isSeq(iter) ? seq : iter.constructor(seq);\n }\n\n function validateEntry(entry) {\n if (entry !== Object(entry)) {\n throw new TypeError('Expected [K, V] tuple: ' + entry);\n }\n }\n\n function resolveSize(iter) {\n assertNotInfinite(iter.size);\n return ensureSize(iter);\n }\n\n function iterableClass(iterable) {\n return isKeyed(iterable) ? KeyedIterable :\n isIndexed(iterable) ? IndexedIterable :\n SetIterable;\n }\n\n function makeSequence(iterable) {\n return Object.create(\n (\n isKeyed(iterable) ? KeyedSeq :\n isIndexed(iterable) ? IndexedSeq :\n SetSeq\n ).prototype\n );\n }\n\n function cacheResultThrough() {\n if (this._iter.cacheResult) {\n this._iter.cacheResult();\n this.size = this._iter.size;\n return this;\n } else {\n return Seq.prototype.cacheResult.call(this);\n }\n }\n\n function defaultComparator(a, b) {\n return a > b ? 1 : a < b ? -1 : 0;\n }\n\n function forceIterator(keyPath) {\n var iter = getIterator(keyPath);\n if (!iter) {\n // Array might not be iterable in this environment, so we need a fallback\n // to our wrapped type.\n if (!isArrayLike(keyPath)) {\n throw new TypeError('Expected iterable or array-like: ' + keyPath);\n }\n iter = getIterator(Iterable(keyPath));\n }\n return iter;\n }\n\n createClass(Record, KeyedCollection);\n\n function Record(defaultValues, name) {\n var hasInitialized;\n\n var RecordType = function Record(values) {\n if (values instanceof RecordType) {\n return values;\n }\n if (!(this instanceof RecordType)) {\n return new RecordType(values);\n }\n if (!hasInitialized) {\n hasInitialized = true;\n var keys = Object.keys(defaultValues);\n setProps(RecordTypePrototype, keys);\n RecordTypePrototype.size = keys.length;\n RecordTypePrototype._name = name;\n RecordTypePrototype._keys = keys;\n RecordTypePrototype._defaultValues = defaultValues;\n }\n this._map = Map(values);\n };\n\n var RecordTypePrototype = RecordType.prototype = Object.create(RecordPrototype);\n RecordTypePrototype.constructor = RecordType;\n\n return RecordType;\n }\n\n Record.prototype.toString = function() {\n return this.__toString(recordName(this) + ' {', '}');\n };\n\n // @pragma Access\n\n Record.prototype.has = function(k) {\n return this._defaultValues.hasOwnProperty(k);\n };\n\n Record.prototype.get = function(k, notSetValue) {\n if (!this.has(k)) {\n return notSetValue;\n }\n var defaultVal = this._defaultValues[k];\n return this._map ? this._map.get(k, defaultVal) : defaultVal;\n };\n\n // @pragma Modification\n\n Record.prototype.clear = function() {\n if (this.__ownerID) {\n this._map && this._map.clear();\n return this;\n }\n var RecordType = this.constructor;\n return RecordType._empty || (RecordType._empty = makeRecord(this, emptyMap()));\n };\n\n Record.prototype.set = function(k, v) {\n if (!this.has(k)) {\n throw new Error('Cannot set unknown key \"' + k + '\" on ' + recordName(this));\n }\n if (this._map && !this._map.has(k)) {\n var defaultVal = this._defaultValues[k];\n if (v === defaultVal) {\n return this;\n }\n }\n var newMap = this._map && this._map.set(k, v);\n if (this.__ownerID || newMap === this._map) {\n return this;\n }\n return makeRecord(this, newMap);\n };\n\n Record.prototype.remove = function(k) {\n if (!this.has(k)) {\n return this;\n }\n var newMap = this._map && this._map.remove(k);\n if (this.__ownerID || newMap === this._map) {\n return this;\n }\n return makeRecord(this, newMap);\n };\n\n Record.prototype.wasAltered = function() {\n return this._map.wasAltered();\n };\n\n Record.prototype.__iterator = function(type, reverse) {var this$0 = this;\n return KeyedIterable(this._defaultValues).map(function(_, k) {return this$0.get(k)}).__iterator(type, reverse);\n };\n\n Record.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n return KeyedIterable(this._defaultValues).map(function(_, k) {return this$0.get(k)}).__iterate(fn, reverse);\n };\n\n Record.prototype.__ensureOwner = function(ownerID) {\n if (ownerID === this.__ownerID) {\n return this;\n }\n var newMap = this._map && this._map.__ensureOwner(ownerID);\n if (!ownerID) {\n this.__ownerID = ownerID;\n this._map = newMap;\n return this;\n }\n return makeRecord(this, newMap, ownerID);\n };\n\n\n var RecordPrototype = Record.prototype;\n RecordPrototype[DELETE] = RecordPrototype.remove;\n RecordPrototype.deleteIn =\n RecordPrototype.removeIn = MapPrototype.removeIn;\n RecordPrototype.merge = MapPrototype.merge;\n RecordPrototype.mergeWith = MapPrototype.mergeWith;\n RecordPrototype.mergeIn = MapPrototype.mergeIn;\n RecordPrototype.mergeDeep = MapPrototype.mergeDeep;\n RecordPrototype.mergeDeepWith = MapPrototype.mergeDeepWith;\n RecordPrototype.mergeDeepIn = MapPrototype.mergeDeepIn;\n RecordPrototype.setIn = MapPrototype.setIn;\n RecordPrototype.update = MapPrototype.update;\n RecordPrototype.updateIn = MapPrototype.updateIn;\n RecordPrototype.withMutations = MapPrototype.withMutations;\n RecordPrototype.asMutable = MapPrototype.asMutable;\n RecordPrototype.asImmutable = MapPrototype.asImmutable;\n\n\n function makeRecord(likeRecord, map, ownerID) {\n var record = Object.create(Object.getPrototypeOf(likeRecord));\n record._map = map;\n record.__ownerID = ownerID;\n return record;\n }\n\n function recordName(record) {\n return record._name || record.constructor.name || 'Record';\n }\n\n function setProps(prototype, names) {\n try {\n names.forEach(setProp.bind(undefined, prototype));\n } catch (error) {\n // Object.defineProperty failed. Probably IE8.\n }\n }\n\n function setProp(prototype, name) {\n Object.defineProperty(prototype, name, {\n get: function() {\n return this.get(name);\n },\n set: function(value) {\n invariant(this.__ownerID, 'Cannot set on an immutable record.');\n this.set(name, value);\n }\n });\n }\n\n createClass(Set, SetCollection);\n\n // @pragma Construction\n\n function Set(value) {\n return value === null || value === undefined ? emptySet() :\n isSet(value) && !isOrdered(value) ? value :\n emptySet().withMutations(function(set ) {\n var iter = SetIterable(value);\n assertNotInfinite(iter.size);\n iter.forEach(function(v ) {return set.add(v)});\n });\n }\n\n Set.of = function(/*...values*/) {\n return this(arguments);\n };\n\n Set.fromKeys = function(value) {\n return this(KeyedIterable(value).keySeq());\n };\n\n Set.prototype.toString = function() {\n return this.__toString('Set {', '}');\n };\n\n // @pragma Access\n\n Set.prototype.has = function(value) {\n return this._map.has(value);\n };\n\n // @pragma Modification\n\n Set.prototype.add = function(value) {\n return updateSet(this, this._map.set(value, true));\n };\n\n Set.prototype.remove = function(value) {\n return updateSet(this, this._map.remove(value));\n };\n\n Set.prototype.clear = function() {\n return updateSet(this, this._map.clear());\n };\n\n // @pragma Composition\n\n Set.prototype.union = function() {var iters = SLICE$0.call(arguments, 0);\n iters = iters.filter(function(x ) {return x.size !== 0});\n if (iters.length === 0) {\n return this;\n }\n if (this.size === 0 && !this.__ownerID && iters.length === 1) {\n return this.constructor(iters[0]);\n }\n return this.withMutations(function(set ) {\n for (var ii = 0; ii < iters.length; ii++) {\n SetIterable(iters[ii]).forEach(function(value ) {return set.add(value)});\n }\n });\n };\n\n Set.prototype.intersect = function() {var iters = SLICE$0.call(arguments, 0);\n if (iters.length === 0) {\n return this;\n }\n iters = iters.map(function(iter ) {return SetIterable(iter)});\n var originalSet = this;\n return this.withMutations(function(set ) {\n originalSet.forEach(function(value ) {\n if (!iters.every(function(iter ) {return iter.includes(value)})) {\n set.remove(value);\n }\n });\n });\n };\n\n Set.prototype.subtract = function() {var iters = SLICE$0.call(arguments, 0);\n if (iters.length === 0) {\n return this;\n }\n iters = iters.map(function(iter ) {return SetIterable(iter)});\n var originalSet = this;\n return this.withMutations(function(set ) {\n originalSet.forEach(function(value ) {\n if (iters.some(function(iter ) {return iter.includes(value)})) {\n set.remove(value);\n }\n });\n });\n };\n\n Set.prototype.merge = function() {\n return this.union.apply(this, arguments);\n };\n\n Set.prototype.mergeWith = function(merger) {var iters = SLICE$0.call(arguments, 1);\n return this.union.apply(this, iters);\n };\n\n Set.prototype.sort = function(comparator) {\n // Late binding\n return OrderedSet(sortFactory(this, comparator));\n };\n\n Set.prototype.sortBy = function(mapper, comparator) {\n // Late binding\n return OrderedSet(sortFactory(this, comparator, mapper));\n };\n\n Set.prototype.wasAltered = function() {\n return this._map.wasAltered();\n };\n\n Set.prototype.__iterate = function(fn, reverse) {var this$0 = this;\n return this._map.__iterate(function(_, k) {return fn(k, k, this$0)}, reverse);\n };\n\n Set.prototype.__iterator = function(type, reverse) {\n return this._map.map(function(_, k) {return k}).__iterator(type, reverse);\n };\n\n Set.prototype.__ensureOwner = function(ownerID) {\n if (ownerID === this.__ownerID) {\n return this;\n }\n var newMap = this._map.__ensureOwner(ownerID);\n if (!ownerID) {\n this.__ownerID = ownerID;\n this._map = newMap;\n return this;\n }\n return this.__make(newMap, ownerID);\n };\n\n\n function isSet(maybeSet) {\n return !!(maybeSet && maybeSet[IS_SET_SENTINEL]);\n }\n\n Set.isSet = isSet;\n\n var IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@';\n\n var SetPrototype = Set.prototype;\n SetPrototype[IS_SET_SENTINEL] = true;\n SetPrototype[DELETE] = SetPrototype.remove;\n SetPrototype.mergeDeep = SetPrototype.merge;\n SetPrototype.mergeDeepWith = SetPrototype.mergeWith;\n SetPrototype.withMutations = MapPrototype.withMutations;\n SetPrototype.asMutable = MapPrototype.asMutable;\n SetPrototype.asImmutable = MapPrototype.asImmutable;\n\n SetPrototype.__empty = emptySet;\n SetPrototype.__make = makeSet;\n\n function updateSet(set, newMap) {\n if (set.__ownerID) {\n set.size = newMap.size;\n set._map = newMap;\n return set;\n }\n return newMap === set._map ? set :\n newMap.size === 0 ? set.__empty() :\n set.__make(newMap);\n }\n\n function makeSet(map, ownerID) {\n var set = Object.create(SetPrototype);\n set.size = map ? map.size : 0;\n set._map = map;\n set.__ownerID = ownerID;\n return set;\n }\n\n var EMPTY_SET;\n function emptySet() {\n return EMPTY_SET || (EMPTY_SET = makeSet(emptyMap()));\n }\n\n createClass(OrderedSet, Set);\n\n // @pragma Construction\n\n function OrderedSet(value) {\n return value === null || value === undefined ? emptyOrderedSet() :\n isOrderedSet(value) ? value :\n emptyOrderedSet().withMutations(function(set ) {\n var iter = SetIterable(value);\n assertNotInfinite(iter.size);\n iter.forEach(function(v ) {return set.add(v)});\n });\n }\n\n OrderedSet.of = function(/*...values*/) {\n return this(arguments);\n };\n\n OrderedSet.fromKeys = function(value) {\n return this(KeyedIterable(value).keySeq());\n };\n\n OrderedSet.prototype.toString = function() {\n return this.__toString('OrderedSet {', '}');\n };\n\n\n function isOrderedSet(maybeOrderedSet) {\n return isSet(maybeOrderedSet) && isOrdered(maybeOrderedSet);\n }\n\n OrderedSet.isOrderedSet = isOrderedSet;\n\n var OrderedSetPrototype = OrderedSet.prototype;\n OrderedSetPrototype[IS_ORDERED_SENTINEL] = true;\n\n OrderedSetPrototype.__empty = emptyOrderedSet;\n OrderedSetPrototype.__make = makeOrderedSet;\n\n function makeOrderedSet(map, ownerID) {\n var set = Object.create(OrderedSetPrototype);\n set.size = map ? map.size : 0;\n set._map = map;\n set.__ownerID = ownerID;\n return set;\n }\n\n var EMPTY_ORDERED_SET;\n function emptyOrderedSet() {\n return EMPTY_ORDERED_SET || (EMPTY_ORDERED_SET = makeOrderedSet(emptyOrderedMap()));\n }\n\n createClass(Stack, IndexedCollection);\n\n // @pragma Construction\n\n function Stack(value) {\n return value === null || value === undefined ? emptyStack() :\n isStack(value) ? value :\n emptyStack().unshiftAll(value);\n }\n\n Stack.of = function(/*...values*/) {\n return this(arguments);\n };\n\n Stack.prototype.toString = function() {\n return this.__toString('Stack [', ']');\n };\n\n // @pragma Access\n\n Stack.prototype.get = function(index, notSetValue) {\n var head = this._head;\n index = wrapIndex(this, index);\n while (head && index--) {\n head = head.next;\n }\n return head ? head.value : notSetValue;\n };\n\n Stack.prototype.peek = function() {\n return this._head && this._head.value;\n };\n\n // @pragma Modification\n\n Stack.prototype.push = function(/*...values*/) {\n if (arguments.length === 0) {\n return this;\n }\n var newSize = this.size + arguments.length;\n var head = this._head;\n for (var ii = arguments.length - 1; ii >= 0; ii--) {\n head = {\n value: arguments[ii],\n next: head\n };\n }\n if (this.__ownerID) {\n this.size = newSize;\n this._head = head;\n this.__hash = undefined;\n this.__altered = true;\n return this;\n }\n return makeStack(newSize, head);\n };\n\n Stack.prototype.pushAll = function(iter) {\n iter = IndexedIterable(iter);\n if (iter.size === 0) {\n return this;\n }\n assertNotInfinite(iter.size);\n var newSize = this.size;\n var head = this._head;\n iter.reverse().forEach(function(value ) {\n newSize++;\n head = {\n value: value,\n next: head\n };\n });\n if (this.__ownerID) {\n this.size = newSize;\n this._head = head;\n this.__hash = undefined;\n this.__altered = true;\n return this;\n }\n return makeStack(newSize, head);\n };\n\n Stack.prototype.pop = function() {\n return this.slice(1);\n };\n\n Stack.prototype.unshift = function(/*...values*/) {\n return this.push.apply(this, arguments);\n };\n\n Stack.prototype.unshiftAll = function(iter) {\n return this.pushAll(iter);\n };\n\n Stack.prototype.shift = function() {\n return this.pop.apply(this, arguments);\n };\n\n Stack.prototype.clear = function() {\n if (this.size === 0) {\n return this;\n }\n if (this.__ownerID) {\n this.size = 0;\n this._head = undefined;\n this.__hash = undefined;\n this.__altered = true;\n return this;\n }\n return emptyStack();\n };\n\n Stack.prototype.slice = function(begin, end) {\n if (wholeSlice(begin, end, this.size)) {\n return this;\n }\n var resolvedBegin = resolveBegin(begin, this.size);\n var resolvedEnd = resolveEnd(end, this.size);\n if (resolvedEnd !== this.size) {\n // super.slice(begin, end);\n return IndexedCollection.prototype.slice.call(this, begin, end);\n }\n var newSize = this.size - resolvedBegin;\n var head = this._head;\n while (resolvedBegin--) {\n head = head.next;\n }\n if (this.__ownerID) {\n this.size = newSize;\n this._head = head;\n this.__hash = undefined;\n this.__altered = true;\n return this;\n }\n return makeStack(newSize, head);\n };\n\n // @pragma Mutability\n\n Stack.prototype.__ensureOwner = function(ownerID) {\n if (ownerID === this.__ownerID) {\n return this;\n }\n if (!ownerID) {\n this.__ownerID = ownerID;\n this.__altered = false;\n return this;\n }\n return makeStack(this.size, this._head, ownerID, this.__hash);\n };\n\n // @pragma Iteration\n\n Stack.prototype.__iterate = function(fn, reverse) {\n if (reverse) {\n return this.reverse().__iterate(fn);\n }\n var iterations = 0;\n var node = this._head;\n while (node) {\n if (fn(node.value, iterations++, this) === false) {\n break;\n }\n node = node.next;\n }\n return iterations;\n };\n\n Stack.prototype.__iterator = function(type, reverse) {\n if (reverse) {\n return this.reverse().__iterator(type);\n }\n var iterations = 0;\n var node = this._head;\n return new Iterator(function() {\n if (node) {\n var value = node.value;\n node = node.next;\n return iteratorValue(type, iterations++, value);\n }\n return iteratorDone();\n });\n };\n\n\n function isStack(maybeStack) {\n return !!(maybeStack && maybeStack[IS_STACK_SENTINEL]);\n }\n\n Stack.isStack = isStack;\n\n var IS_STACK_SENTINEL = '@@__IMMUTABLE_STACK__@@';\n\n var StackPrototype = Stack.prototype;\n StackPrototype[IS_STACK_SENTINEL] = true;\n StackPrototype.withMutations = MapPrototype.withMutations;\n StackPrototype.asMutable = MapPrototype.asMutable;\n StackPrototype.asImmutable = MapPrototype.asImmutable;\n StackPrototype.wasAltered = MapPrototype.wasAltered;\n\n\n function makeStack(size, head, ownerID, hash) {\n var map = Object.create(StackPrototype);\n map.size = size;\n map._head = head;\n map.__ownerID = ownerID;\n map.__hash = hash;\n map.__altered = false;\n return map;\n }\n\n var EMPTY_STACK;\n function emptyStack() {\n return EMPTY_STACK || (EMPTY_STACK = makeStack(0));\n }\n\n /**\n * Contributes additional methods to a constructor\n */\n function mixin(ctor, methods) {\n var keyCopier = function(key ) { ctor.prototype[key] = methods[key]; };\n Object.keys(methods).forEach(keyCopier);\n Object.getOwnPropertySymbols &&\n Object.getOwnPropertySymbols(methods).forEach(keyCopier);\n return ctor;\n }\n\n Iterable.Iterator = Iterator;\n\n mixin(Iterable, {\n\n // ### Conversion to other types\n\n toArray: function() {\n assertNotInfinite(this.size);\n var array = new Array(this.size || 0);\n this.valueSeq().__iterate(function(v, i) { array[i] = v; });\n return array;\n },\n\n toIndexedSeq: function() {\n return new ToIndexedSequence(this);\n },\n\n toJS: function() {\n return this.toSeq().map(\n function(value ) {return value && typeof value.toJS === 'function' ? value.toJS() : value}\n ).__toJS();\n },\n\n toJSON: function() {\n return this.toSeq().map(\n function(value ) {return value && typeof value.toJSON === 'function' ? value.toJSON() : value}\n ).__toJS();\n },\n\n toKeyedSeq: function() {\n return new ToKeyedSequence(this, true);\n },\n\n toMap: function() {\n // Use Late Binding here to solve the circular dependency.\n return Map(this.toKeyedSeq());\n },\n\n toObject: function() {\n assertNotInfinite(this.size);\n var object = {};\n this.__iterate(function(v, k) { object[k] = v; });\n return object;\n },\n\n toOrderedMap: function() {\n // Use Late Binding here to solve the circular dependency.\n return OrderedMap(this.toKeyedSeq());\n },\n\n toOrderedSet: function() {\n // Use Late Binding here to solve the circular dependency.\n return OrderedSet(isKeyed(this) ? this.valueSeq() : this);\n },\n\n toSet: function() {\n // Use Late Binding here to solve the circular dependency.\n return Set(isKeyed(this) ? this.valueSeq() : this);\n },\n\n toSetSeq: function() {\n return new ToSetSequence(this);\n },\n\n toSeq: function() {\n return isIndexed(this) ? this.toIndexedSeq() :\n isKeyed(this) ? this.toKeyedSeq() :\n this.toSetSeq();\n },\n\n toStack: function() {\n // Use Late Binding here to solve the circular dependency.\n return Stack(isKeyed(this) ? this.valueSeq() : this);\n },\n\n toList: function() {\n // Use Late Binding here to solve the circular dependency.\n return List(isKeyed(this) ? this.valueSeq() : this);\n },\n\n\n // ### Common JavaScript methods and properties\n\n toString: function() {\n return '[Iterable]';\n },\n\n __toString: function(head, tail) {\n if (this.size === 0) {\n return head + tail;\n }\n return head + ' ' + this.toSeq().map(this.__toStringMapper).join(', ') + ' ' + tail;\n },\n\n\n // ### ES6 Collection methods (ES6 Array and Map)\n\n concat: function() {var values = SLICE$0.call(arguments, 0);\n return reify(this, concatFactory(this, values));\n },\n\n includes: function(searchValue) {\n return this.some(function(value ) {return is(value, searchValue)});\n },\n\n entries: function() {\n return this.__iterator(ITERATE_ENTRIES);\n },\n\n every: function(predicate, context) {\n assertNotInfinite(this.size);\n var returnValue = true;\n this.__iterate(function(v, k, c) {\n if (!predicate.call(context, v, k, c)) {\n returnValue = false;\n return false;\n }\n });\n return returnValue;\n },\n\n filter: function(predicate, context) {\n return reify(this, filterFactory(this, predicate, context, true));\n },\n\n find: function(predicate, context, notSetValue) {\n var entry = this.findEntry(predicate, context);\n return entry ? entry[1] : notSetValue;\n },\n\n forEach: function(sideEffect, context) {\n assertNotInfinite(this.size);\n return this.__iterate(context ? sideEffect.bind(context) : sideEffect);\n },\n\n join: function(separator) {\n assertNotInfinite(this.size);\n separator = separator !== undefined ? '' + separator : ',';\n var joined = '';\n var isFirst = true;\n this.__iterate(function(v ) {\n isFirst ? (isFirst = false) : (joined += separator);\n joined += v !== null && v !== undefined ? v.toString() : '';\n });\n return joined;\n },\n\n keys: function() {\n return this.__iterator(ITERATE_KEYS);\n },\n\n map: function(mapper, context) {\n return reify(this, mapFactory(this, mapper, context));\n },\n\n reduce: function(reducer, initialReduction, context) {\n assertNotInfinite(this.size);\n var reduction;\n var useFirst;\n if (arguments.length < 2) {\n useFirst = true;\n } else {\n reduction = initialReduction;\n }\n this.__iterate(function(v, k, c) {\n if (useFirst) {\n useFirst = false;\n reduction = v;\n } else {\n reduction = reducer.call(context, reduction, v, k, c);\n }\n });\n return reduction;\n },\n\n reduceRight: function(reducer, initialReduction, context) {\n var reversed = this.toKeyedSeq().reverse();\n return reversed.reduce.apply(reversed, arguments);\n },\n\n reverse: function() {\n return reify(this, reverseFactory(this, true));\n },\n\n slice: function(begin, end) {\n return reify(this, sliceFactory(this, begin, end, true));\n },\n\n some: function(predicate, context) {\n return !this.every(not(predicate), context);\n },\n\n sort: function(comparator) {\n return reify(this, sortFactory(this, comparator));\n },\n\n values: function() {\n return this.__iterator(ITERATE_VALUES);\n },\n\n\n // ### More sequential methods\n\n butLast: function() {\n return this.slice(0, -1);\n },\n\n isEmpty: function() {\n return this.size !== undefined ? this.size === 0 : !this.some(function() {return true});\n },\n\n count: function(predicate, context) {\n return ensureSize(\n predicate ? this.toSeq().filter(predicate, context) : this\n );\n },\n\n countBy: function(grouper, context) {\n return countByFactory(this, grouper, context);\n },\n\n equals: function(other) {\n return deepEqual(this, other);\n },\n\n entrySeq: function() {\n var iterable = this;\n if (iterable._cache) {\n // We cache as an entries array, so we can just return the cache!\n return new ArraySeq(iterable._cache);\n }\n var entriesSequence = iterable.toSeq().map(entryMapper).toIndexedSeq();\n entriesSequence.fromEntrySeq = function() {return iterable.toSeq()};\n return entriesSequence;\n },\n\n filterNot: function(predicate, context) {\n return this.filter(not(predicate), context);\n },\n\n findEntry: function(predicate, context, notSetValue) {\n var found = notSetValue;\n this.__iterate(function(v, k, c) {\n if (predicate.call(context, v, k, c)) {\n found = [k, v];\n return false;\n }\n });\n return found;\n },\n\n findKey: function(predicate, context) {\n var entry = this.findEntry(predicate, context);\n return entry && entry[0];\n },\n\n findLast: function(predicate, context, notSetValue) {\n return this.toKeyedSeq().reverse().find(predicate, context, notSetValue);\n },\n\n findLastEntry: function(predicate, context, notSetValue) {\n return this.toKeyedSeq().reverse().findEntry(predicate, context, notSetValue);\n },\n\n findLastKey: function(predicate, context) {\n return this.toKeyedSeq().reverse().findKey(predicate, context);\n },\n\n first: function() {\n return this.find(returnTrue);\n },\n\n flatMap: function(mapper, context) {\n return reify(this, flatMapFactory(this, mapper, context));\n },\n\n flatten: function(depth) {\n return reify(this, flattenFactory(this, depth, true));\n },\n\n fromEntrySeq: function() {\n return new FromEntriesSequence(this);\n },\n\n get: function(searchKey, notSetValue) {\n return this.find(function(_, key) {return is(key, searchKey)}, undefined, notSetValue);\n },\n\n getIn: function(searchKeyPath, notSetValue) {\n var nested = this;\n // Note: in an ES6 environment, we would prefer:\n // for (var key of searchKeyPath) {\n var iter = forceIterator(searchKeyPath);\n var step;\n while (!(step = iter.next()).done) {\n var key = step.value;\n nested = nested && nested.get ? nested.get(key, NOT_SET) : NOT_SET;\n if (nested === NOT_SET) {\n return notSetValue;\n }\n }\n return nested;\n },\n\n groupBy: function(grouper, context) {\n return groupByFactory(this, grouper, context);\n },\n\n has: function(searchKey) {\n return this.get(searchKey, NOT_SET) !== NOT_SET;\n },\n\n hasIn: function(searchKeyPath) {\n return this.getIn(searchKeyPath, NOT_SET) !== NOT_SET;\n },\n\n isSubset: function(iter) {\n iter = typeof iter.includes === 'function' ? iter : Iterable(iter);\n return this.every(function(value ) {return iter.includes(value)});\n },\n\n isSuperset: function(iter) {\n iter = typeof iter.isSubset === 'function' ? iter : Iterable(iter);\n return iter.isSubset(this);\n },\n\n keyOf: function(searchValue) {\n return this.findKey(function(value ) {return is(value, searchValue)});\n },\n\n keySeq: function() {\n return this.toSeq().map(keyMapper).toIndexedSeq();\n },\n\n last: function() {\n return this.toSeq().reverse().first();\n },\n\n lastKeyOf: function(searchValue) {\n return this.toKeyedSeq().reverse().keyOf(searchValue);\n },\n\n max: function(comparator) {\n return maxFactory(this, comparator);\n },\n\n maxBy: function(mapper, comparator) {\n return maxFactory(this, comparator, mapper);\n },\n\n min: function(comparator) {\n return maxFactory(this, comparator ? neg(comparator) : defaultNegComparator);\n },\n\n minBy: function(mapper, comparator) {\n return maxFactory(this, comparator ? neg(comparator) : defaultNegComparator, mapper);\n },\n\n rest: function() {\n return this.slice(1);\n },\n\n skip: function(amount) {\n return this.slice(Math.max(0, amount));\n },\n\n skipLast: function(amount) {\n return reify(this, this.toSeq().reverse().skip(amount).reverse());\n },\n\n skipWhile: function(predicate, context) {\n return reify(this, skipWhileFactory(this, predicate, context, true));\n },\n\n skipUntil: function(predicate, context) {\n return this.skipWhile(not(predicate), context);\n },\n\n sortBy: function(mapper, comparator) {\n return reify(this, sortFactory(this, comparator, mapper));\n },\n\n take: function(amount) {\n return this.slice(0, Math.max(0, amount));\n },\n\n takeLast: function(amount) {\n return reify(this, this.toSeq().reverse().take(amount).reverse());\n },\n\n takeWhile: function(predicate, context) {\n return reify(this, takeWhileFactory(this, predicate, context));\n },\n\n takeUntil: function(predicate, context) {\n return this.takeWhile(not(predicate), context);\n },\n\n valueSeq: function() {\n return this.toIndexedSeq();\n },\n\n\n // ### Hashable Object\n\n hashCode: function() {\n return this.__hash || (this.__hash = hashIterable(this));\n }\n\n\n // ### Internal\n\n // abstract __iterate(fn, reverse)\n\n // abstract __iterator(type, reverse)\n });\n\n // var IS_ITERABLE_SENTINEL = '@@__IMMUTABLE_ITERABLE__@@';\n // var IS_KEYED_SENTINEL = '@@__IMMUTABLE_KEYED__@@';\n // var IS_INDEXED_SENTINEL = '@@__IMMUTABLE_INDEXED__@@';\n // var IS_ORDERED_SENTINEL = '@@__IMMUTABLE_ORDERED__@@';\n\n var IterablePrototype = Iterable.prototype;\n IterablePrototype[IS_ITERABLE_SENTINEL] = true;\n IterablePrototype[ITERATOR_SYMBOL] = IterablePrototype.values;\n IterablePrototype.__toJS = IterablePrototype.toArray;\n IterablePrototype.__toStringMapper = quoteString;\n IterablePrototype.inspect =\n IterablePrototype.toSource = function() { return this.toString(); };\n IterablePrototype.chain = IterablePrototype.flatMap;\n IterablePrototype.contains = IterablePrototype.includes;\n\n mixin(KeyedIterable, {\n\n // ### More sequential methods\n\n flip: function() {\n return reify(this, flipFactory(this));\n },\n\n mapEntries: function(mapper, context) {var this$0 = this;\n var iterations = 0;\n return reify(this,\n this.toSeq().map(\n function(v, k) {return mapper.call(context, [k, v], iterations++, this$0)}\n ).fromEntrySeq()\n );\n },\n\n mapKeys: function(mapper, context) {var this$0 = this;\n return reify(this,\n this.toSeq().flip().map(\n function(k, v) {return mapper.call(context, k, v, this$0)}\n ).flip()\n );\n }\n\n });\n\n var KeyedIterablePrototype = KeyedIterable.prototype;\n KeyedIterablePrototype[IS_KEYED_SENTINEL] = true;\n KeyedIterablePrototype[ITERATOR_SYMBOL] = IterablePrototype.entries;\n KeyedIterablePrototype.__toJS = IterablePrototype.toObject;\n KeyedIterablePrototype.__toStringMapper = function(v, k) {return JSON.stringify(k) + ': ' + quoteString(v)};\n\n\n\n mixin(IndexedIterable, {\n\n // ### Conversion to other types\n\n toKeyedSeq: function() {\n return new ToKeyedSequence(this, false);\n },\n\n\n // ### ES6 Collection methods (ES6 Array and Map)\n\n filter: function(predicate, context) {\n return reify(this, filterFactory(this, predicate, context, false));\n },\n\n findIndex: function(predicate, context) {\n var entry = this.findEntry(predicate, context);\n return entry ? entry[0] : -1;\n },\n\n indexOf: function(searchValue) {\n var key = this.keyOf(searchValue);\n return key === undefined ? -1 : key;\n },\n\n lastIndexOf: function(searchValue) {\n var key = this.lastKeyOf(searchValue);\n return key === undefined ? -1 : key;\n },\n\n reverse: function() {\n return reify(this, reverseFactory(this, false));\n },\n\n slice: function(begin, end) {\n return reify(this, sliceFactory(this, begin, end, false));\n },\n\n splice: function(index, removeNum /*, ...values*/) {\n var numArgs = arguments.length;\n removeNum = Math.max(removeNum | 0, 0);\n if (numArgs === 0 || (numArgs === 2 && !removeNum)) {\n return this;\n }\n // If index is negative, it should resolve relative to the size of the\n // collection. However size may be expensive to compute if not cached, so\n // only call count() if the number is in fact negative.\n index = resolveBegin(index, index < 0 ? this.count() : this.size);\n var spliced = this.slice(0, index);\n return reify(\n this,\n numArgs === 1 ?\n spliced :\n spliced.concat(arrCopy(arguments, 2), this.slice(index + removeNum))\n );\n },\n\n\n // ### More collection methods\n\n findLastIndex: function(predicate, context) {\n var entry = this.findLastEntry(predicate, context);\n return entry ? entry[0] : -1;\n },\n\n first: function() {\n return this.get(0);\n },\n\n flatten: function(depth) {\n return reify(this, flattenFactory(this, depth, false));\n },\n\n get: function(index, notSetValue) {\n index = wrapIndex(this, index);\n return (index < 0 || (this.size === Infinity ||\n (this.size !== undefined && index > this.size))) ?\n notSetValue :\n this.find(function(_, key) {return key === index}, undefined, notSetValue);\n },\n\n has: function(index) {\n index = wrapIndex(this, index);\n return index >= 0 && (this.size !== undefined ?\n this.size === Infinity || index < this.size :\n this.indexOf(index) !== -1\n );\n },\n\n interpose: function(separator) {\n return reify(this, interposeFactory(this, separator));\n },\n\n interleave: function(/*...iterables*/) {\n var iterables = [this].concat(arrCopy(arguments));\n var zipped = zipWithFactory(this.toSeq(), IndexedSeq.of, iterables);\n var interleaved = zipped.flatten(true);\n if (zipped.size) {\n interleaved.size = zipped.size * iterables.length;\n }\n return reify(this, interleaved);\n },\n\n keySeq: function() {\n return Range(0, this.size);\n },\n\n last: function() {\n return this.get(-1);\n },\n\n skipWhile: function(predicate, context) {\n return reify(this, skipWhileFactory(this, predicate, context, false));\n },\n\n zip: function(/*, ...iterables */) {\n var iterables = [this].concat(arrCopy(arguments));\n return reify(this, zipWithFactory(this, defaultZipper, iterables));\n },\n\n zipWith: function(zipper/*, ...iterables */) {\n var iterables = arrCopy(arguments);\n iterables[0] = this;\n return reify(this, zipWithFactory(this, zipper, iterables));\n }\n\n });\n\n IndexedIterable.prototype[IS_INDEXED_SENTINEL] = true;\n IndexedIterable.prototype[IS_ORDERED_SENTINEL] = true;\n\n\n\n mixin(SetIterable, {\n\n // ### ES6 Collection methods (ES6 Array and Map)\n\n get: function(value, notSetValue) {\n return this.has(value) ? value : notSetValue;\n },\n\n includes: function(value) {\n return this.has(value);\n },\n\n\n // ### More sequential methods\n\n keySeq: function() {\n return this.valueSeq();\n }\n\n });\n\n SetIterable.prototype.has = IterablePrototype.includes;\n SetIterable.prototype.contains = SetIterable.prototype.includes;\n\n\n // Mixin subclasses\n\n mixin(KeyedSeq, KeyedIterable.prototype);\n mixin(IndexedSeq, IndexedIterable.prototype);\n mixin(SetSeq, SetIterable.prototype);\n\n mixin(KeyedCollection, KeyedIterable.prototype);\n mixin(IndexedCollection, IndexedIterable.prototype);\n mixin(SetCollection, SetIterable.prototype);\n\n\n // #pragma Helper functions\n\n function keyMapper(v, k) {\n return k;\n }\n\n function entryMapper(v, k) {\n return [k, v];\n }\n\n function not(predicate) {\n return function() {\n return !predicate.apply(this, arguments);\n }\n }\n\n function neg(predicate) {\n return function() {\n return -predicate.apply(this, arguments);\n }\n }\n\n function quoteString(value) {\n return typeof value === 'string' ? JSON.stringify(value) : String(value);\n }\n\n function defaultZipper() {\n return arrCopy(arguments);\n }\n\n function defaultNegComparator(a, b) {\n return a < b ? 1 : a > b ? -1 : 0;\n }\n\n function hashIterable(iterable) {\n if (iterable.size === Infinity) {\n return 0;\n }\n var ordered = isOrdered(iterable);\n var keyed = isKeyed(iterable);\n var h = ordered ? 1 : 0;\n var size = iterable.__iterate(\n keyed ?\n ordered ?\n function(v, k) { h = 31 * h + hashMerge(hash(v), hash(k)) | 0; } :\n function(v, k) { h = h + hashMerge(hash(v), hash(k)) | 0; } :\n ordered ?\n function(v ) { h = 31 * h + hash(v) | 0; } :\n function(v ) { h = h + hash(v) | 0; }\n );\n return murmurHashOfSize(size, h);\n }\n\n function murmurHashOfSize(size, h) {\n h = imul(h, 0xCC9E2D51);\n h = imul(h << 15 | h >>> -15, 0x1B873593);\n h = imul(h << 13 | h >>> -13, 5);\n h = (h + 0xE6546B64 | 0) ^ size;\n h = imul(h ^ h >>> 16, 0x85EBCA6B);\n h = imul(h ^ h >>> 13, 0xC2B2AE35);\n h = smi(h ^ h >>> 16);\n return h;\n }\n\n function hashMerge(a, b) {\n return a ^ b + 0x9E3779B9 + (a << 6) + (a >> 2) | 0; // int\n }\n\n var Immutable = {\n\n Iterable: Iterable,\n\n Seq: Seq,\n Collection: Collection,\n Map: Map,\n OrderedMap: OrderedMap,\n List: List,\n Stack: Stack,\n Set: Set,\n OrderedSet: OrderedSet,\n\n Record: Record,\n Range: Range,\n Repeat: Repeat,\n\n is: is,\n fromJS: fromJS\n\n };\n\n return Immutable;\n\n}));","module.exports = function() {\n return new Worker(__webpack_public_path__ + \"extension-worker.js\");\n};","const Cast = require('../util/cast');\n\nclass Scratch3ControlBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n /**\n * The \"counter\" block value. For compatibility with 2.0.\n * @type {number}\n */\n this._counter = 0;\n\n this.runtime.on('RUNTIME_DISPOSED', this.clearCounter.bind(this));\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n control_repeat: this.repeat,\n control_repeat_until: this.repeatUntil,\n control_while: this.repeatWhile,\n control_for_each: this.forEach,\n control_forever: this.forever,\n control_wait: this.wait,\n control_wait_until: this.waitUntil,\n control_if: this.if,\n control_if_else: this.ifElse,\n control_stop: this.stop,\n control_create_clone_of: this.createClone,\n control_delete_this_clone: this.deleteClone,\n control_get_counter: this.getCounter,\n control_incr_counter: this.incrCounter,\n control_clear_counter: this.clearCounter,\n control_all_at_once: this.allAtOnce\n };\n }\n\n getHats () {\n return {\n control_start_as_clone: {\n restartExistingThreads: false\n }\n };\n }\n\n repeat (args, util) {\n const times = Math.round(Cast.toNumber(args.TIMES));\n // Initialize loop\n if (typeof util.stackFrame.loopCounter === 'undefined') {\n util.stackFrame.loopCounter = times;\n }\n // Only execute once per frame.\n // When the branch finishes, `repeat` will be executed again and\n // the second branch will be taken, yielding for the rest of the frame.\n // Decrease counter\n util.stackFrame.loopCounter--;\n // If we still have some left, start the branch.\n if (util.stackFrame.loopCounter >= 0) {\n util.startBranch(1, true);\n }\n }\n\n repeatUntil (args, util) {\n const condition = Cast.toBoolean(args.CONDITION);\n // If the condition is false (repeat UNTIL), start the branch.\n if (!condition) {\n util.startBranch(1, true);\n }\n }\n\n repeatWhile (args, util) {\n const condition = Cast.toBoolean(args.CONDITION);\n // If the condition is true (repeat WHILE), start the branch.\n if (condition) {\n util.startBranch(1, true);\n }\n }\n\n forEach (args, util) {\n const variable = util.target.lookupOrCreateVariable(\n args.VARIABLE.id, args.VARIABLE.name);\n\n if (typeof util.stackFrame.index === 'undefined') {\n util.stackFrame.index = 0;\n }\n\n if (util.stackFrame.index < Number(args.VALUE)) {\n util.stackFrame.index++;\n variable.value = util.stackFrame.index;\n util.startBranch(1, true);\n }\n }\n\n waitUntil (args, util) {\n const condition = Cast.toBoolean(args.CONDITION);\n if (!condition) {\n util.yield();\n }\n }\n\n forever (args, util) {\n util.startBranch(1, true);\n }\n\n wait (args, util) {\n if (util.stackTimerNeedsInit()) {\n const duration = Math.max(0, 1000 * Cast.toNumber(args.DURATION));\n\n util.startStackTimer(duration);\n this.runtime.requestRedraw();\n util.yield();\n } else if (!util.stackTimerFinished()) {\n util.yield();\n }\n }\n\n if (args, util) {\n const condition = Cast.toBoolean(args.CONDITION);\n if (condition) {\n util.startBranch(1, false);\n }\n }\n\n ifElse (args, util) {\n const condition = Cast.toBoolean(args.CONDITION);\n if (condition) {\n util.startBranch(1, false);\n } else {\n util.startBranch(2, false);\n }\n }\n\n stop (args, util) {\n const option = args.STOP_OPTION;\n if (option === 'all') {\n util.stopAll();\n } else if (option === 'other scripts in sprite' ||\n option === 'other scripts in stage') {\n util.stopOtherTargetThreads();\n } else if (option === 'this script') {\n util.stopThisScript();\n }\n }\n\n createClone (args, util) {\n // Cast argument to string\n args.CLONE_OPTION = Cast.toString(args.CLONE_OPTION);\n\n // Set clone target\n let cloneTarget;\n if (args.CLONE_OPTION === '_myself_') {\n cloneTarget = util.target;\n } else {\n cloneTarget = this.runtime.getSpriteTargetByName(args.CLONE_OPTION);\n }\n\n // If clone target is not found, return\n if (!cloneTarget) return;\n\n // Create clone\n const newClone = cloneTarget.makeClone();\n if (newClone) {\n this.runtime.addTarget(newClone);\n\n // Place behind the original target.\n newClone.goBehindOther(cloneTarget);\n }\n }\n\n deleteClone (args, util) {\n if (util.target.isOriginal) return;\n this.runtime.disposeTarget(util.target);\n this.runtime.stopForTarget(util.target);\n }\n\n getCounter () {\n return this._counter;\n }\n\n clearCounter () {\n this._counter = 0;\n }\n\n incrCounter () {\n this._counter++;\n }\n\n allAtOnce (args, util) {\n // Since the \"all at once\" block is implemented for compatiblity with\n // Scratch 2.0 projects, it behaves the same way it did in 2.0, which\n // is to simply run the contained script (like \"if 1 = 1\").\n // (In early versions of Scratch 2.0, it would work the same way as\n // \"run without screen refresh\" custom blocks do now, but this was\n // removed before the release of 2.0.)\n util.startBranch(1, false);\n }\n}\n\nmodule.exports = Scratch3ControlBlocks;\n","const BlockType = require('../extension-support/block-type');\nconst ArgumentType = require('../extension-support/argument-type');\n\n/* eslint-disable-next-line max-len */\nconst blockIconURI = 'data:image/svg+xml,%3Csvg id=\"rotate-counter-clockwise\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill:%233d79cc;%7D.cls-2%7Bfill:%23fff;%7D%3C/style%3E%3C/defs%3E%3Ctitle%3Erotate-counter-clockwise%3C/title%3E%3Cpath class=\"cls-1\" d=\"M22.68,12.2a1.6,1.6,0,0,1-1.27.63H13.72a1.59,1.59,0,0,1-1.16-2.58l1.12-1.41a4.82,4.82,0,0,0-3.14-.77,4.31,4.31,0,0,0-2,.8,4.25,4.25,0,0,0-1.34,1.73,5.06,5.06,0,0,0,.54,4.62A5.58,5.58,0,0,0,12,17.74h0a2.26,2.26,0,0,1-.16,4.52A10.25,10.25,0,0,1,3.74,18,10.14,10.14,0,0,1,2.25,8.78,9.7,9.7,0,0,1,5.08,4.64,9.92,9.92,0,0,1,9.66,2.5a10.66,10.66,0,0,1,7.72,1.68l1.08-1.35a1.57,1.57,0,0,1,1.24-.6,1.6,1.6,0,0,1,1.54,1.21l1.7,7.37A1.57,1.57,0,0,1,22.68,12.2Z\"/%3E%3Cpath class=\"cls-2\" d=\"M21.38,11.83H13.77a.59.59,0,0,1-.43-1l1.75-2.19a5.9,5.9,0,0,0-4.7-1.58,5.07,5.07,0,0,0-4.11,3.17A6,6,0,0,0,7,15.77a6.51,6.51,0,0,0,5,2.92,1.31,1.31,0,0,1-.08,2.62,9.3,9.3,0,0,1-7.35-3.82A9.16,9.16,0,0,1,3.17,9.12,8.51,8.51,0,0,1,5.71,5.4,8.76,8.76,0,0,1,9.82,3.48a9.71,9.71,0,0,1,7.75,2.07l1.67-2.1a.59.59,0,0,1,1,.21L22,11.08A.59.59,0,0,1,21.38,11.83Z\"/%3E%3C/svg%3E';\n\n/**\n * An example core block implemented using the extension spec.\n * This is not loaded as part of the core blocks in the VM but it is provided\n * and used as part of tests.\n */\nclass Scratch3CoreExample {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: 'coreExample',\n name: 'CoreEx', // This string does not need to be translated as this extension is only used as an example.\n blocks: [\n {\n func: 'MAKE_A_VARIABLE',\n blockType: BlockType.BUTTON,\n text: 'make a variable (CoreEx)'\n },\n {\n opcode: 'exampleOpcode',\n blockType: BlockType.REPORTER,\n text: 'example block'\n },\n {\n opcode: 'exampleWithInlineImage',\n blockType: BlockType.COMMAND,\n text: 'block with image [CLOCKWISE] inline',\n arguments: {\n CLOCKWISE: {\n type: ArgumentType.IMAGE,\n dataURI: blockIconURI\n }\n }\n }\n ]\n };\n }\n\n /**\n * Example opcode just returns the name of the stage target.\n * @returns {string} The name of the first target in the project.\n */\n exampleOpcode () {\n const stage = this.runtime.getTargetForStage();\n return stage ? stage.getName() : 'no stage yet';\n }\n\n exampleWithInlineImage () {\n return;\n }\n\n}\n\nmodule.exports = Scratch3CoreExample;\n","const Cast = require('../util/cast');\n\nclass Scratch3DataBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n data_variable: this.getVariable,\n data_setvariableto: this.setVariableTo,\n data_changevariableby: this.changeVariableBy,\n data_hidevariable: this.hideVariable,\n data_showvariable: this.showVariable,\n data_listcontents: this.getListContents,\n data_addtolist: this.addToList,\n data_deleteoflist: this.deleteOfList,\n data_deletealloflist: this.deleteAllOfList,\n data_insertatlist: this.insertAtList,\n data_replaceitemoflist: this.replaceItemOfList,\n data_itemoflist: this.getItemOfList,\n data_itemnumoflist: this.getItemNumOfList,\n data_lengthoflist: this.lengthOfList,\n data_listcontainsitem: this.listContainsItem,\n data_hidelist: this.hideList,\n data_showlist: this.showList\n };\n }\n\n getVariable (args, util) {\n const variable = util.target.lookupOrCreateVariable(\n args.VARIABLE.id, args.VARIABLE.name);\n return variable.value;\n }\n\n setVariableTo (args, util) {\n const variable = util.target.lookupOrCreateVariable(\n args.VARIABLE.id, args.VARIABLE.name);\n variable.value = args.VALUE;\n\n if (variable.isCloud) {\n util.ioQuery('cloud', 'requestUpdateVariable', [variable.name, args.VALUE]);\n }\n }\n\n changeVariableBy (args, util) {\n const variable = util.target.lookupOrCreateVariable(\n args.VARIABLE.id, args.VARIABLE.name);\n const castedValue = Cast.toNumber(variable.value);\n const dValue = Cast.toNumber(args.VALUE);\n const newValue = castedValue + dValue;\n variable.value = newValue;\n\n if (variable.isCloud) {\n util.ioQuery('cloud', 'requestUpdateVariable', [variable.name, newValue]);\n }\n }\n\n changeMonitorVisibility (id, visible) {\n // Send the monitor blocks an event like the flyout checkbox event.\n // This both updates the monitor state and changes the isMonitored block flag.\n this.runtime.monitorBlocks.changeBlock({\n id: id, // Monitor blocks for variables are the variable ID.\n element: 'checkbox', // Mimic checkbox event from flyout.\n value: visible\n }, this.runtime);\n }\n\n showVariable (args) {\n this.changeMonitorVisibility(args.VARIABLE.id, true);\n }\n\n hideVariable (args) {\n this.changeMonitorVisibility(args.VARIABLE.id, false);\n }\n\n showList (args) {\n this.changeMonitorVisibility(args.LIST.id, true);\n }\n\n hideList (args) {\n this.changeMonitorVisibility(args.LIST.id, false);\n }\n\n getListContents (args, util) {\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n\n // If block is running for monitors, return copy of list as an array if changed.\n if (util.thread.updateMonitor) {\n // Return original list value if up-to-date, which doesn't trigger monitor update.\n if (list._monitorUpToDate) return list.value;\n // If value changed, reset the flag and return a copy to trigger monitor update.\n // Because monitors use Immutable data structures, only new objects trigger updates.\n list._monitorUpToDate = true;\n return list.value.slice();\n }\n\n // Determine if the list is all single letters.\n // If it is, report contents joined together with no separator.\n // If it's not, report contents joined together with a space.\n let allSingleLetters = true;\n for (let i = 0; i < list.value.length; i++) {\n const listItem = list.value[i];\n if (!((typeof listItem === 'string') &&\n (listItem.length === 1))) {\n allSingleLetters = false;\n break;\n }\n }\n if (allSingleLetters) {\n return list.value.join('');\n }\n return list.value.join(' ');\n\n }\n\n addToList (args, util) {\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n if (list.value.length < Scratch3DataBlocks.LIST_ITEM_LIMIT) {\n list.value.push(args.ITEM);\n list._monitorUpToDate = false;\n }\n }\n\n deleteOfList (args, util) {\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n const index = Cast.toListIndex(args.INDEX, list.value.length, true);\n if (index === Cast.LIST_INVALID) {\n return;\n } else if (index === Cast.LIST_ALL) {\n list.value = [];\n return;\n }\n list.value.splice(index - 1, 1);\n list._monitorUpToDate = false;\n }\n\n deleteAllOfList (args, util) {\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n list.value = [];\n return;\n }\n\n insertAtList (args, util) {\n const item = args.ITEM;\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n const index = Cast.toListIndex(args.INDEX, list.value.length + 1, false);\n if (index === Cast.LIST_INVALID) {\n return;\n }\n const listLimit = Scratch3DataBlocks.LIST_ITEM_LIMIT;\n if (index > listLimit) return;\n list.value.splice(index - 1, 0, item);\n if (list.value.length > listLimit) {\n // If inserting caused the list to grow larger than the limit,\n // remove the last element in the list\n list.value.pop();\n }\n list._monitorUpToDate = false;\n }\n\n replaceItemOfList (args, util) {\n const item = args.ITEM;\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n const index = Cast.toListIndex(args.INDEX, list.value.length, false);\n if (index === Cast.LIST_INVALID) {\n return;\n }\n list.value[index - 1] = item;\n list._monitorUpToDate = false;\n }\n\n getItemOfList (args, util) {\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n const index = Cast.toListIndex(args.INDEX, list.value.length, false);\n if (index === Cast.LIST_INVALID) {\n return '';\n }\n return list.value[index - 1];\n }\n\n getItemNumOfList (args, util) {\n const item = args.ITEM;\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n\n // Go through the list items one-by-one using Cast.compare. This is for\n // cases like checking if 123 is contained in a list [4, 7, '123'] --\n // Scratch considers 123 and '123' to be equal.\n for (let i = 0; i < list.value.length; i++) {\n if (Cast.compare(list.value[i], item) === 0) {\n return i + 1;\n }\n }\n\n // We don't bother using .indexOf() at all, because it would end up with\n // edge cases such as the index of '123' in [4, 7, 123, '123', 9].\n // If we use indexOf(), this block would return 4 instead of 3, because\n // indexOf() sees the first occurence of the string 123 as the fourth\n // item in the list. With Scratch, this would be confusing -- after all,\n // '123' and 123 look the same, so one would expect the block to say\n // that the first occurrence of '123' (or 123) to be the third item.\n\n // Default to 0 if there's no match. Since Scratch lists are 1-indexed,\n // we don't have to worry about this conflicting with the \"this item is\n // the first value\" number (in JS that is 0, but in Scratch it's 1).\n return 0;\n }\n\n lengthOfList (args, util) {\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n return list.value.length;\n }\n\n listContainsItem (args, util) {\n const item = args.ITEM;\n const list = util.target.lookupOrCreateList(\n args.LIST.id, args.LIST.name);\n if (list.value.indexOf(item) >= 0) {\n return true;\n }\n // Try using Scratch comparison operator on each item.\n // (Scratch considers the string '123' equal to the number 123).\n for (let i = 0; i < list.value.length; i++) {\n if (Cast.compare(list.value[i], item) === 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Type representation for list variables.\n * @const {number}\n */\n static get LIST_ITEM_LIMIT () {\n return 200000;\n }\n}\n\nmodule.exports = Scratch3DataBlocks;\n","const Cast = require('../util/cast');\n\nclass Scratch3EventBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n this.runtime.on('KEY_PRESSED', key => {\n this.runtime.startHats('event_whenkeypressed', {\n KEY_OPTION: key\n });\n this.runtime.startHats('event_whenkeypressed', {\n KEY_OPTION: 'any'\n });\n });\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n event_whentouchingobject: this.touchingObject,\n event_broadcast: this.broadcast,\n event_broadcastandwait: this.broadcastAndWait,\n event_whengreaterthan: this.hatGreaterThanPredicate\n };\n }\n\n getHats () {\n return {\n event_whenflagclicked: {\n restartExistingThreads: true\n },\n event_whenkeypressed: {\n restartExistingThreads: false\n },\n event_whenthisspriteclicked: {\n restartExistingThreads: true\n },\n event_whentouchingobject: {\n restartExistingThreads: false,\n edgeActivated: true\n },\n event_whenstageclicked: {\n restartExistingThreads: true\n },\n event_whenbackdropswitchesto: {\n restartExistingThreads: true\n },\n event_whengreaterthan: {\n restartExistingThreads: false,\n edgeActivated: true\n },\n event_whenbroadcastreceived: {\n restartExistingThreads: true\n }\n };\n }\n\n touchingObject (args, util) {\n return util.target.isTouchingObject(args.TOUCHINGOBJECTMENU);\n }\n\n hatGreaterThanPredicate (args, util) {\n const option = Cast.toString(args.WHENGREATERTHANMENU).toLowerCase();\n const value = Cast.toNumber(args.VALUE);\n switch (option) {\n case 'timer':\n return util.ioQuery('clock', 'projectTimer') > value;\n case 'loudness':\n return this.runtime.audioEngine && this.runtime.audioEngine.getLoudness() > value;\n }\n return false;\n }\n\n broadcast (args, util) {\n const broadcastVar = util.runtime.getTargetForStage().lookupBroadcastMsg(\n args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name);\n if (broadcastVar) {\n const broadcastOption = broadcastVar.name;\n util.startHats('event_whenbroadcastreceived', {\n BROADCAST_OPTION: broadcastOption\n });\n }\n }\n\n broadcastAndWait (args, util) {\n if (!util.stackFrame.broadcastVar) {\n util.stackFrame.broadcastVar = util.runtime.getTargetForStage().lookupBroadcastMsg(\n args.BROADCAST_OPTION.id, args.BROADCAST_OPTION.name);\n }\n if (util.stackFrame.broadcastVar) {\n const broadcastOption = util.stackFrame.broadcastVar.name;\n // Have we run before, starting threads?\n if (!util.stackFrame.startedThreads) {\n // No - start hats for this broadcast.\n util.stackFrame.startedThreads = util.startHats(\n 'event_whenbroadcastreceived', {\n BROADCAST_OPTION: broadcastOption\n }\n );\n if (util.stackFrame.startedThreads.length === 0) {\n // Nothing was started.\n return;\n }\n }\n // We've run before; check if the wait is still going on.\n const instance = this;\n // Scratch 2 considers threads to be waiting if they are still in\n // runtime.threads. Threads that have run all their blocks, or are\n // marked done but still in runtime.threads are still considered to\n // be waiting.\n const waiting = util.stackFrame.startedThreads\n .some(thread => instance.runtime.threads.indexOf(thread) !== -1);\n if (waiting) {\n // If all threads are waiting for the next tick or later yield\n // for a tick as well. Otherwise yield until the next loop of\n // the threads.\n if (\n util.stackFrame.startedThreads\n .every(thread => instance.runtime.isWaitingThread(thread))\n ) {\n util.yieldTick();\n } else {\n util.yield();\n }\n }\n }\n }\n}\n\nmodule.exports = Scratch3EventBlocks;\n","const Cast = require('../util/cast');\nconst Clone = require('../util/clone');\nconst RenderedTarget = require('../sprites/rendered-target');\nconst uid = require('../util/uid');\nconst StageLayering = require('../engine/stage-layering');\nconst getMonitorIdForBlockWithArgs = require('../util/get-monitor-id');\nconst MathUtil = require('../util/math-util');\n\n/**\n * @typedef {object} BubbleState - the bubble state associated with a particular target.\n * @property {Boolean} onSpriteRight - tracks whether the bubble is right or left of the sprite.\n * @property {?int} drawableId - the ID of the associated bubble Drawable, null if none.\n * @property {string} text - the text of the bubble.\n * @property {string} type - the type of the bubble, \"say\" or \"think\"\n * @property {?string} usageId - ID indicating the most recent usage of the say/think bubble.\n * Used for comparison when determining whether to clear a say/think bubble.\n */\n\nclass Scratch3LooksBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n this._onTargetChanged = this._onTargetChanged.bind(this);\n this._onResetBubbles = this._onResetBubbles.bind(this);\n this._onTargetWillExit = this._onTargetWillExit.bind(this);\n this._updateBubble = this._updateBubble.bind(this);\n\n // Reset all bubbles on start/stop\n this.runtime.on('PROJECT_STOP_ALL', this._onResetBubbles);\n this.runtime.on('targetWasRemoved', this._onTargetWillExit);\n\n // Enable other blocks to use bubbles like ask/answer\n this.runtime.on(Scratch3LooksBlocks.SAY_OR_THINK, this._updateBubble);\n }\n\n /**\n * The default bubble state, to be used when a target has no existing bubble state.\n * @type {BubbleState}\n */\n static get DEFAULT_BUBBLE_STATE () {\n return {\n drawableId: null,\n onSpriteRight: true,\n skinId: null,\n text: '',\n type: 'say',\n usageId: null\n };\n }\n\n /**\n * The key to load & store a target's bubble-related state.\n * @type {string}\n */\n static get STATE_KEY () {\n return 'Scratch.looks';\n }\n\n /**\n * Event name for a text bubble being created or updated.\n * @const {string}\n */\n static get SAY_OR_THINK () {\n // There are currently many places in the codebase which explicitly refer to this event by the string 'SAY',\n // so keep this as the string 'SAY' for now rather than changing it to 'SAY_OR_THINK' and breaking things.\n return 'SAY';\n }\n\n /**\n * Limit for say bubble string.\n * @const {string}\n */\n static get SAY_BUBBLE_LIMIT () {\n return 330;\n }\n\n /**\n * Limit for ghost effect\n * @const {object}\n */\n static get EFFECT_GHOST_LIMIT (){\n return {min: 0, max: 100};\n }\n\n /**\n * Limit for brightness effect\n * @const {object}\n */\n static get EFFECT_BRIGHTNESS_LIMIT (){\n return {min: -100, max: 100};\n }\n\n /**\n * @param {Target} target - collect bubble state for this target. Probably, but not necessarily, a RenderedTarget.\n * @returns {BubbleState} the mutable bubble state associated with that target. This will be created if necessary.\n * @private\n */\n _getBubbleState (target) {\n let bubbleState = target.getCustomState(Scratch3LooksBlocks.STATE_KEY);\n if (!bubbleState) {\n bubbleState = Clone.simple(Scratch3LooksBlocks.DEFAULT_BUBBLE_STATE);\n target.setCustomState(Scratch3LooksBlocks.STATE_KEY, bubbleState);\n }\n return bubbleState;\n }\n\n /**\n * Handle a target which has moved.\n * @param {RenderedTarget} target - the target which has moved.\n * @private\n */\n _onTargetChanged (target) {\n const bubbleState = this._getBubbleState(target);\n if (bubbleState.drawableId) {\n this._positionBubble(target);\n }\n }\n\n /**\n * Handle a target which is exiting.\n * @param {RenderedTarget} target - the target.\n * @private\n */\n _onTargetWillExit (target) {\n const bubbleState = this._getBubbleState(target);\n if (bubbleState.drawableId && bubbleState.skinId) {\n this.runtime.renderer.destroyDrawable(bubbleState.drawableId, StageLayering.SPRITE_LAYER);\n this.runtime.renderer.destroySkin(bubbleState.skinId);\n bubbleState.drawableId = null;\n bubbleState.skinId = null;\n this.runtime.requestRedraw();\n }\n target.removeListener(RenderedTarget.EVENT_TARGET_VISUAL_CHANGE, this._onTargetChanged);\n }\n\n /**\n * Handle project start/stop by clearing all visible bubbles.\n * @private\n */\n _onResetBubbles () {\n for (let n = 0; n < this.runtime.targets.length; n++) {\n const bubbleState = this._getBubbleState(this.runtime.targets[n]);\n bubbleState.text = '';\n this._onTargetWillExit(this.runtime.targets[n]);\n }\n clearTimeout(this._bubbleTimeout);\n }\n\n /**\n * Position the bubble of a target. If it doesn't fit on the specified side, flip and rerender.\n * @param {!Target} target Target whose bubble needs positioning.\n * @private\n */\n _positionBubble (target) {\n if (!target.visible) return;\n const bubbleState = this._getBubbleState(target);\n const [bubbleWidth, bubbleHeight] = this.runtime.renderer.getCurrentSkinSize(bubbleState.drawableId);\n let targetBounds;\n try {\n targetBounds = target.getBoundsForBubble();\n } catch (error_) {\n // Bounds calculation could fail (e.g. on empty costumes), in that case\n // use the x/y position of the target.\n targetBounds = {\n left: target.x,\n right: target.x,\n top: target.y,\n bottom: target.y\n };\n }\n const stageSize = this.runtime.renderer.getNativeSize();\n const stageBounds = {\n left: -stageSize[0] / 2,\n right: stageSize[0] / 2,\n top: stageSize[1] / 2,\n bottom: -stageSize[1] / 2\n };\n if (bubbleState.onSpriteRight && bubbleWidth + targetBounds.right > stageBounds.right &&\n (targetBounds.left - bubbleWidth > stageBounds.left)) { // Only flip if it would fit\n bubbleState.onSpriteRight = false;\n this._renderBubble(target);\n } else if (!bubbleState.onSpriteRight && targetBounds.left - bubbleWidth < stageBounds.left &&\n (bubbleWidth + targetBounds.right < stageBounds.right)) { // Only flip if it would fit\n bubbleState.onSpriteRight = true;\n this._renderBubble(target);\n } else {\n this.runtime.renderer.updateDrawablePosition(bubbleState.drawableId, [\n bubbleState.onSpriteRight ? (\n Math.max(\n stageBounds.left, // Bubble should not extend past left edge of stage\n Math.min(stageBounds.right - bubbleWidth, targetBounds.right)\n )\n ) : (\n Math.min(\n stageBounds.right - bubbleWidth, // Bubble should not extend past right edge of stage\n Math.max(stageBounds.left, targetBounds.left - bubbleWidth)\n )\n ),\n // Bubble should not extend past the top of the stage\n Math.min(stageBounds.top, targetBounds.bottom + bubbleHeight)\n ]);\n this.runtime.requestRedraw();\n }\n }\n\n /**\n * Create a visible bubble for a target. If a bubble exists for the target,\n * just set it to visible and update the type/text. Otherwise create a new\n * bubble and update the relevant custom state.\n * @param {!Target} target Target who needs a bubble.\n * @return {undefined} Early return if text is empty string.\n * @private\n */\n _renderBubble (target) {\n if (!this.runtime.renderer) return;\n\n const bubbleState = this._getBubbleState(target);\n const {type, text, onSpriteRight} = bubbleState;\n\n // Remove the bubble if target is not visible, or text is being set to blank.\n if (!target.visible || text === '') {\n this._onTargetWillExit(target);\n return;\n }\n\n if (bubbleState.skinId) {\n this.runtime.renderer.updateTextSkin(bubbleState.skinId, type, text, onSpriteRight, [0, 0]);\n } else {\n target.addListener(RenderedTarget.EVENT_TARGET_VISUAL_CHANGE, this._onTargetChanged);\n bubbleState.drawableId = this.runtime.renderer.createDrawable(StageLayering.SPRITE_LAYER);\n bubbleState.skinId = this.runtime.renderer.createTextSkin(type, text, bubbleState.onSpriteRight, [0, 0]);\n this.runtime.renderer.updateDrawableSkinId(bubbleState.drawableId, bubbleState.skinId);\n }\n\n this._positionBubble(target);\n }\n\n /**\n * Properly format text for a text bubble.\n * @param {string} text The text to be formatted\n * @return {string} The formatted text\n * @private\n */\n _formatBubbleText (text) {\n if (text === '') return text;\n\n // Non-integers should be rounded to 2 decimal places (no more, no less), unless they're small enough that\n // rounding would display them as 0.00. This matches 2.0's behavior:\n // https://github.com/LLK/scratch-flash/blob/2e4a402ceb205a042887f54b26eebe1c2e6da6c0/src/scratch/ScratchSprite.as#L579-L585\n if (typeof text === 'number' &&\n Math.abs(text) >= 0.01 && text % 1 !== 0) {\n text = text.toFixed(2);\n }\n\n // Limit the length of the string.\n text = String(text).substr(0, Scratch3LooksBlocks.SAY_BUBBLE_LIMIT);\n\n return text;\n }\n\n /**\n * The entry point for say/think blocks. Clears existing bubble if the text is empty.\n * Set the bubble custom state and then call _renderBubble.\n * @param {!Target} target Target that say/think blocks are being called on.\n * @param {!string} type Either \"say\" or \"think\"\n * @param {!string} text The text for the bubble, empty string clears the bubble.\n * @private\n */\n _updateBubble (target, type, text) {\n const bubbleState = this._getBubbleState(target);\n bubbleState.type = type;\n bubbleState.text = this._formatBubbleText(text);\n bubbleState.usageId = uid();\n this._renderBubble(target);\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n looks_say: this.say,\n looks_sayforsecs: this.sayforsecs,\n looks_think: this.think,\n looks_thinkforsecs: this.thinkforsecs,\n looks_show: this.show,\n looks_hide: this.hide,\n looks_hideallsprites: () => {}, // legacy no-op block\n looks_switchcostumeto: this.switchCostume,\n looks_switchbackdropto: this.switchBackdrop,\n looks_switchbackdroptoandwait: this.switchBackdropAndWait,\n looks_nextcostume: this.nextCostume,\n looks_nextbackdrop: this.nextBackdrop,\n looks_changeeffectby: this.changeEffect,\n looks_seteffectto: this.setEffect,\n looks_cleargraphiceffects: this.clearEffects,\n looks_changesizeby: this.changeSize,\n looks_setsizeto: this.setSize,\n looks_changestretchby: () => {}, // legacy no-op blocks\n looks_setstretchto: () => {},\n looks_gotofrontback: this.goToFrontBack,\n looks_goforwardbackwardlayers: this.goForwardBackwardLayers,\n looks_size: this.getSize,\n looks_costumenumbername: this.getCostumeNumberName,\n looks_backdropnumbername: this.getBackdropNumberName\n };\n }\n\n getMonitored () {\n return {\n looks_size: {\n isSpriteSpecific: true,\n getId: targetId => `${targetId}_size`\n },\n looks_costumenumbername: {\n isSpriteSpecific: true,\n getId: (targetId, fields) => getMonitorIdForBlockWithArgs(`${targetId}_costumenumbername`, fields)\n },\n looks_backdropnumbername: {\n getId: (_, fields) => getMonitorIdForBlockWithArgs('backdropnumbername', fields)\n }\n };\n }\n\n say (args, util) {\n // @TODO in 2.0 calling say/think resets the right/left bias of the bubble\n this.runtime.emit(Scratch3LooksBlocks.SAY_OR_THINK, util.target, 'say', args.MESSAGE);\n }\n\n sayforsecs (args, util) {\n this.say(args, util);\n const target = util.target;\n const usageId = this._getBubbleState(target).usageId;\n return new Promise(resolve => {\n this._bubbleTimeout = setTimeout(() => {\n this._bubbleTimeout = null;\n // Clear say bubble if it hasn't been changed and proceed.\n if (this._getBubbleState(target).usageId === usageId) {\n this._updateBubble(target, 'say', '');\n }\n resolve();\n }, 1000 * args.SECS);\n });\n }\n\n think (args, util) {\n this.runtime.emit(Scratch3LooksBlocks.SAY_OR_THINK, util.target, 'think', args.MESSAGE);\n }\n\n thinkforsecs (args, util) {\n this.think(args, util);\n const target = util.target;\n const usageId = this._getBubbleState(target).usageId;\n return new Promise(resolve => {\n this._bubbleTimeout = setTimeout(() => {\n this._bubbleTimeout = null;\n // Clear think bubble if it hasn't been changed and proceed.\n if (this._getBubbleState(target).usageId === usageId) {\n this._updateBubble(target, 'think', '');\n }\n resolve();\n }, 1000 * args.SECS);\n });\n }\n\n show (args, util) {\n util.target.setVisible(true);\n this._renderBubble(util.target);\n }\n\n hide (args, util) {\n util.target.setVisible(false);\n this._renderBubble(util.target);\n }\n\n /**\n * Utility function to set the costume of a target.\n * Matches the behavior of Scratch 2.0 for different types of arguments.\n * @param {!Target} target Target to set costume to.\n * @param {Any} requestedCostume Costume requested, e.g., 0, 'name', etc.\n * @param {boolean=} optZeroIndex Set to zero-index the requestedCostume.\n * @return {Array.} Any threads started by this switch.\n */\n _setCostume (target, requestedCostume, optZeroIndex) {\n if (typeof requestedCostume === 'number') {\n // Numbers should be treated as costume indices, always\n target.setCostume(optZeroIndex ? requestedCostume : requestedCostume - 1);\n } else {\n // Strings should be treated as costume names, where possible\n const costumeIndex = target.getCostumeIndexByName(requestedCostume.toString());\n\n if (costumeIndex !== -1) {\n target.setCostume(costumeIndex);\n } else if (requestedCostume === 'next costume') {\n target.setCostume(target.currentCostume + 1);\n } else if (requestedCostume === 'previous costume') {\n target.setCostume(target.currentCostume - 1);\n // Try to cast the string to a number (and treat it as a costume index)\n // Pure whitespace should not be treated as a number\n // Note: isNaN will cast the string to a number before checking if it's NaN\n } else if (!(isNaN(requestedCostume) || Cast.isWhiteSpace(requestedCostume))) {\n target.setCostume(optZeroIndex ? Number(requestedCostume) : Number(requestedCostume) - 1);\n }\n }\n\n // Per 2.0, 'switch costume' can't start threads even in the Stage.\n return [];\n }\n\n /**\n * Utility function to set the backdrop of a target.\n * Matches the behavior of Scratch 2.0 for different types of arguments.\n * @param {!Target} stage Target to set backdrop to.\n * @param {Any} requestedBackdrop Backdrop requested, e.g., 0, 'name', etc.\n * @param {boolean=} optZeroIndex Set to zero-index the requestedBackdrop.\n * @return {Array.} Any threads started by this switch.\n */\n _setBackdrop (stage, requestedBackdrop, optZeroIndex) {\n if (typeof requestedBackdrop === 'number') {\n // Numbers should be treated as backdrop indices, always\n stage.setCostume(optZeroIndex ? requestedBackdrop : requestedBackdrop - 1);\n } else {\n // Strings should be treated as backdrop names where possible\n const costumeIndex = stage.getCostumeIndexByName(requestedBackdrop.toString());\n\n if (costumeIndex !== -1) {\n stage.setCostume(costumeIndex);\n } else if (requestedBackdrop === 'next backdrop') {\n stage.setCostume(stage.currentCostume + 1);\n } else if (requestedBackdrop === 'previous backdrop') {\n stage.setCostume(stage.currentCostume - 1);\n } else if (requestedBackdrop === 'random backdrop') {\n const numCostumes = stage.getCostumes().length;\n if (numCostumes > 1) {\n // Don't pick the current backdrop, so that the block\n // will always have an observable effect.\n const lowerBound = 0;\n const upperBound = numCostumes - 1;\n const costumeToExclude = stage.currentCostume;\n\n const nextCostume = MathUtil.inclusiveRandIntWithout(lowerBound, upperBound, costumeToExclude);\n\n stage.setCostume(nextCostume);\n }\n // Try to cast the string to a number (and treat it as a costume index)\n // Pure whitespace should not be treated as a number\n // Note: isNaN will cast the string to a number before checking if it's NaN\n } else if (!(isNaN(requestedBackdrop) || Cast.isWhiteSpace(requestedBackdrop))) {\n stage.setCostume(optZeroIndex ? Number(requestedBackdrop) : Number(requestedBackdrop) - 1);\n }\n }\n\n const newName = stage.getCostumes()[stage.currentCostume].name;\n return this.runtime.startHats('event_whenbackdropswitchesto', {\n BACKDROP: newName\n });\n }\n\n switchCostume (args, util) {\n this._setCostume(util.target, args.COSTUME);\n }\n\n nextCostume (args, util) {\n this._setCostume(\n util.target, util.target.currentCostume + 1, true\n );\n }\n\n switchBackdrop (args) {\n this._setBackdrop(this.runtime.getTargetForStage(), args.BACKDROP);\n }\n\n switchBackdropAndWait (args, util) {\n // Have we run before, starting threads?\n if (!util.stackFrame.startedThreads) {\n // No - switch the backdrop.\n util.stackFrame.startedThreads = (\n this._setBackdrop(\n this.runtime.getTargetForStage(),\n args.BACKDROP\n )\n );\n if (util.stackFrame.startedThreads.length === 0) {\n // Nothing was started.\n return;\n }\n }\n // We've run before; check if the wait is still going on.\n const instance = this;\n // Scratch 2 considers threads to be waiting if they are still in\n // runtime.threads. Threads that have run all their blocks, or are\n // marked done but still in runtime.threads are still considered to\n // be waiting.\n const waiting = util.stackFrame.startedThreads\n .some(thread => instance.runtime.threads.indexOf(thread) !== -1);\n if (waiting) {\n // If all threads are waiting for the next tick or later yield\n // for a tick as well. Otherwise yield until the next loop of\n // the threads.\n if (\n util.stackFrame.startedThreads\n .every(thread => instance.runtime.isWaitingThread(thread))\n ) {\n util.yieldTick();\n } else {\n util.yield();\n }\n }\n }\n\n nextBackdrop () {\n const stage = this.runtime.getTargetForStage();\n this._setBackdrop(\n stage, stage.currentCostume + 1, true\n );\n }\n\n clampEffect (effect, value) {\n let clampedValue = value;\n switch (effect) {\n case 'ghost':\n clampedValue = MathUtil.clamp(value,\n Scratch3LooksBlocks.EFFECT_GHOST_LIMIT.min,\n Scratch3LooksBlocks.EFFECT_GHOST_LIMIT.max);\n break;\n case 'brightness':\n clampedValue = MathUtil.clamp(value,\n Scratch3LooksBlocks.EFFECT_BRIGHTNESS_LIMIT.min,\n Scratch3LooksBlocks.EFFECT_BRIGHTNESS_LIMIT.max);\n break;\n }\n return clampedValue;\n }\n\n changeEffect (args, util) {\n const effect = Cast.toString(args.EFFECT).toLowerCase();\n const change = Cast.toNumber(args.CHANGE);\n if (!util.target.effects.hasOwnProperty(effect)) return;\n let newValue = change + util.target.effects[effect];\n newValue = this.clampEffect(effect, newValue);\n util.target.setEffect(effect, newValue);\n }\n\n setEffect (args, util) {\n const effect = Cast.toString(args.EFFECT).toLowerCase();\n let value = Cast.toNumber(args.VALUE);\n value = this.clampEffect(effect, value);\n util.target.setEffect(effect, value);\n }\n\n clearEffects (args, util) {\n util.target.clearEffects();\n }\n\n changeSize (args, util) {\n const change = Cast.toNumber(args.CHANGE);\n util.target.setSize(util.target.size + change);\n }\n\n setSize (args, util) {\n const size = Cast.toNumber(args.SIZE);\n util.target.setSize(size);\n }\n\n goToFrontBack (args, util) {\n if (!util.target.isStage) {\n if (args.FRONT_BACK === 'front') {\n util.target.goToFront();\n } else {\n util.target.goToBack();\n }\n }\n }\n\n goForwardBackwardLayers (args, util) {\n if (!util.target.isStage) {\n if (args.FORWARD_BACKWARD === 'forward') {\n util.target.goForwardLayers(Cast.toNumber(args.NUM));\n } else {\n util.target.goBackwardLayers(Cast.toNumber(args.NUM));\n }\n }\n }\n\n getSize (args, util) {\n return Math.round(util.target.size);\n }\n\n getBackdropNumberName (args) {\n const stage = this.runtime.getTargetForStage();\n if (args.NUMBER_NAME === 'number') {\n return stage.currentCostume + 1;\n }\n // Else return name\n return stage.getCostumes()[stage.currentCostume].name;\n }\n\n getCostumeNumberName (args, util) {\n if (args.NUMBER_NAME === 'number') {\n return util.target.currentCostume + 1;\n }\n // Else return name\n return util.target.getCostumes()[util.target.currentCostume].name;\n }\n}\n\nmodule.exports = Scratch3LooksBlocks;\n","const Cast = require('../util/cast');\nconst MathUtil = require('../util/math-util');\nconst Timer = require('../util/timer');\n\nclass Scratch3MotionBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n motion_movesteps: this.moveSteps,\n motion_gotoxy: this.goToXY,\n motion_goto: this.goTo,\n motion_turnright: this.turnRight,\n motion_turnleft: this.turnLeft,\n motion_pointindirection: this.pointInDirection,\n motion_pointtowards: this.pointTowards,\n motion_glidesecstoxy: this.glide,\n motion_glideto: this.glideTo,\n motion_ifonedgebounce: this.ifOnEdgeBounce,\n motion_setrotationstyle: this.setRotationStyle,\n motion_changexby: this.changeX,\n motion_setx: this.setX,\n motion_changeyby: this.changeY,\n motion_sety: this.setY,\n motion_xposition: this.getX,\n motion_yposition: this.getY,\n motion_direction: this.getDirection,\n // Legacy no-op blocks:\n motion_scroll_right: () => {},\n motion_scroll_up: () => {},\n motion_align_scene: () => {},\n motion_xscroll: () => {},\n motion_yscroll: () => {}\n };\n }\n\n getMonitored () {\n return {\n motion_xposition: {\n isSpriteSpecific: true,\n getId: targetId => `${targetId}_xposition`\n },\n motion_yposition: {\n isSpriteSpecific: true,\n getId: targetId => `${targetId}_yposition`\n },\n motion_direction: {\n isSpriteSpecific: true,\n getId: targetId => `${targetId}_direction`\n }\n };\n }\n\n moveSteps (args, util) {\n const steps = Cast.toNumber(args.STEPS);\n const radians = MathUtil.degToRad(90 - util.target.direction);\n const dx = steps * Math.cos(radians);\n const dy = steps * Math.sin(radians);\n util.target.setXY(util.target.x + dx, util.target.y + dy);\n }\n\n goToXY (args, util) {\n const x = Cast.toNumber(args.X);\n const y = Cast.toNumber(args.Y);\n util.target.setXY(x, y);\n }\n\n getTargetXY (targetName, util) {\n let targetX = 0;\n let targetY = 0;\n if (targetName === '_mouse_') {\n targetX = util.ioQuery('mouse', 'getScratchX');\n targetY = util.ioQuery('mouse', 'getScratchY');\n } else if (targetName === '_random_') {\n const stageWidth = this.runtime.constructor.STAGE_WIDTH;\n const stageHeight = this.runtime.constructor.STAGE_HEIGHT;\n targetX = Math.round(stageWidth * (Math.random() - 0.5));\n targetY = Math.round(stageHeight * (Math.random() - 0.5));\n } else {\n targetName = Cast.toString(targetName);\n const goToTarget = this.runtime.getSpriteTargetByName(targetName);\n if (!goToTarget) return;\n targetX = goToTarget.x;\n targetY = goToTarget.y;\n }\n return [targetX, targetY];\n }\n\n goTo (args, util) {\n const targetXY = this.getTargetXY(args.TO, util);\n if (targetXY) {\n util.target.setXY(targetXY[0], targetXY[1]);\n }\n }\n\n turnRight (args, util) {\n const degrees = Cast.toNumber(args.DEGREES);\n util.target.setDirection(util.target.direction + degrees);\n }\n\n turnLeft (args, util) {\n const degrees = Cast.toNumber(args.DEGREES);\n util.target.setDirection(util.target.direction - degrees);\n }\n\n pointInDirection (args, util) {\n const direction = Cast.toNumber(args.DIRECTION);\n util.target.setDirection(direction);\n }\n\n pointTowards (args, util) {\n let targetX = 0;\n let targetY = 0;\n if (args.TOWARDS === '_mouse_') {\n targetX = util.ioQuery('mouse', 'getScratchX');\n targetY = util.ioQuery('mouse', 'getScratchY');\n } else if (args.TOWARDS === '_random_') {\n util.target.setDirection(Math.round(Math.random() * 360) - 180);\n return;\n } else {\n args.TOWARDS = Cast.toString(args.TOWARDS);\n const pointTarget = this.runtime.getSpriteTargetByName(args.TOWARDS);\n if (!pointTarget) return;\n targetX = pointTarget.x;\n targetY = pointTarget.y;\n }\n\n const dx = targetX - util.target.x;\n const dy = targetY - util.target.y;\n const direction = 90 - MathUtil.radToDeg(Math.atan2(dy, dx));\n util.target.setDirection(direction);\n }\n\n glide (args, util) {\n if (util.stackFrame.timer) {\n const timeElapsed = util.stackFrame.timer.timeElapsed();\n if (timeElapsed < util.stackFrame.duration * 1000) {\n // In progress: move to intermediate position.\n const frac = timeElapsed / (util.stackFrame.duration * 1000);\n const dx = frac * (util.stackFrame.endX - util.stackFrame.startX);\n const dy = frac * (util.stackFrame.endY - util.stackFrame.startY);\n util.target.setXY(\n util.stackFrame.startX + dx,\n util.stackFrame.startY + dy\n );\n util.yield();\n } else {\n // Finished: move to final position.\n util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);\n }\n } else {\n // First time: save data for future use.\n util.stackFrame.timer = new Timer();\n util.stackFrame.timer.start();\n util.stackFrame.duration = Cast.toNumber(args.SECS);\n util.stackFrame.startX = util.target.x;\n util.stackFrame.startY = util.target.y;\n util.stackFrame.endX = Cast.toNumber(args.X);\n util.stackFrame.endY = Cast.toNumber(args.Y);\n if (util.stackFrame.duration <= 0) {\n // Duration too short to glide.\n util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);\n return;\n }\n util.yield();\n }\n }\n\n glideTo (args, util) {\n const targetXY = this.getTargetXY(args.TO, util);\n if (targetXY) {\n this.glide({SECS: args.SECS, X: targetXY[0], Y: targetXY[1]}, util);\n }\n }\n\n ifOnEdgeBounce (args, util) {\n const bounds = util.target.getBounds();\n if (!bounds) {\n return;\n }\n // Measure distance to edges.\n // Values are positive when the sprite is far away,\n // and clamped to zero when the sprite is beyond.\n const stageWidth = this.runtime.constructor.STAGE_WIDTH;\n const stageHeight = this.runtime.constructor.STAGE_HEIGHT;\n const distLeft = Math.max(0, (stageWidth / 2) + bounds.left);\n const distTop = Math.max(0, (stageHeight / 2) - bounds.top);\n const distRight = Math.max(0, (stageWidth / 2) - bounds.right);\n const distBottom = Math.max(0, (stageHeight / 2) + bounds.bottom);\n // Find the nearest edge.\n let nearestEdge = '';\n let minDist = Infinity;\n if (distLeft < minDist) {\n minDist = distLeft;\n nearestEdge = 'left';\n }\n if (distTop < minDist) {\n minDist = distTop;\n nearestEdge = 'top';\n }\n if (distRight < minDist) {\n minDist = distRight;\n nearestEdge = 'right';\n }\n if (distBottom < minDist) {\n minDist = distBottom;\n nearestEdge = 'bottom';\n }\n if (minDist > 0) {\n return; // Not touching any edge.\n }\n // Point away from the nearest edge.\n const radians = MathUtil.degToRad(90 - util.target.direction);\n let dx = Math.cos(radians);\n let dy = -Math.sin(radians);\n if (nearestEdge === 'left') {\n dx = Math.max(0.2, Math.abs(dx));\n } else if (nearestEdge === 'top') {\n dy = Math.max(0.2, Math.abs(dy));\n } else if (nearestEdge === 'right') {\n dx = 0 - Math.max(0.2, Math.abs(dx));\n } else if (nearestEdge === 'bottom') {\n dy = 0 - Math.max(0.2, Math.abs(dy));\n }\n const newDirection = MathUtil.radToDeg(Math.atan2(dy, dx)) + 90;\n util.target.setDirection(newDirection);\n // Keep within the stage.\n const fencedPosition = util.target.keepInFence(util.target.x, util.target.y);\n util.target.setXY(fencedPosition[0], fencedPosition[1]);\n }\n\n setRotationStyle (args, util) {\n util.target.setRotationStyle(args.STYLE);\n }\n\n changeX (args, util) {\n const dx = Cast.toNumber(args.DX);\n util.target.setXY(util.target.x + dx, util.target.y);\n }\n\n setX (args, util) {\n const x = Cast.toNumber(args.X);\n util.target.setXY(x, util.target.y);\n }\n\n changeY (args, util) {\n const dy = Cast.toNumber(args.DY);\n util.target.setXY(util.target.x, util.target.y + dy);\n }\n\n setY (args, util) {\n const y = Cast.toNumber(args.Y);\n util.target.setXY(util.target.x, y);\n }\n\n getX (args, util) {\n return this.limitPrecision(util.target.x);\n }\n\n getY (args, util) {\n return this.limitPrecision(util.target.y);\n }\n\n getDirection (args, util) {\n return util.target.direction;\n }\n\n // This corresponds to snapToInteger in Scratch 2\n limitPrecision (coordinate) {\n const rounded = Math.round(coordinate);\n const delta = coordinate - rounded;\n const limitedCoord = (Math.abs(delta) < 1e-9) ? rounded : coordinate;\n\n return limitedCoord;\n }\n}\n\nmodule.exports = Scratch3MotionBlocks;\n","const Cast = require('../util/cast.js');\nconst MathUtil = require('../util/math-util.js');\n\nclass Scratch3OperatorsBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n operator_add: this.add,\n operator_subtract: this.subtract,\n operator_multiply: this.multiply,\n operator_divide: this.divide,\n operator_lt: this.lt,\n operator_equals: this.equals,\n operator_gt: this.gt,\n operator_and: this.and,\n operator_or: this.or,\n operator_not: this.not,\n operator_random: this.random,\n operator_join: this.join,\n operator_letter_of: this.letterOf,\n operator_length: this.length,\n operator_contains: this.contains,\n operator_mod: this.mod,\n operator_round: this.round,\n operator_mathop: this.mathop\n };\n }\n\n add (args) {\n return Cast.toNumber(args.NUM1) + Cast.toNumber(args.NUM2);\n }\n\n subtract (args) {\n return Cast.toNumber(args.NUM1) - Cast.toNumber(args.NUM2);\n }\n\n multiply (args) {\n return Cast.toNumber(args.NUM1) * Cast.toNumber(args.NUM2);\n }\n\n divide (args) {\n return Cast.toNumber(args.NUM1) / Cast.toNumber(args.NUM2);\n }\n\n lt (args) {\n return Cast.compare(args.OPERAND1, args.OPERAND2) < 0;\n }\n\n equals (args) {\n return Cast.compare(args.OPERAND1, args.OPERAND2) === 0;\n }\n\n gt (args) {\n return Cast.compare(args.OPERAND1, args.OPERAND2) > 0;\n }\n\n and (args) {\n return Cast.toBoolean(args.OPERAND1) && Cast.toBoolean(args.OPERAND2);\n }\n\n or (args) {\n return Cast.toBoolean(args.OPERAND1) || Cast.toBoolean(args.OPERAND2);\n }\n\n not (args) {\n return !Cast.toBoolean(args.OPERAND);\n }\n\n random (args) {\n const nFrom = Cast.toNumber(args.FROM);\n const nTo = Cast.toNumber(args.TO);\n const low = nFrom <= nTo ? nFrom : nTo;\n const high = nFrom <= nTo ? nTo : nFrom;\n if (low === high) return low;\n // If both arguments are ints, truncate the result to an int.\n if (Cast.isInt(args.FROM) && Cast.isInt(args.TO)) {\n return low + Math.floor(Math.random() * ((high + 1) - low));\n }\n return (Math.random() * (high - low)) + low;\n }\n\n join (args) {\n return Cast.toString(args.STRING1) + Cast.toString(args.STRING2);\n }\n\n letterOf (args) {\n const index = Cast.toNumber(args.LETTER) - 1;\n const str = Cast.toString(args.STRING);\n // Out of bounds?\n if (index < 0 || index >= str.length) {\n return '';\n }\n return str.charAt(index);\n }\n\n length (args) {\n return Cast.toString(args.STRING).length;\n }\n\n contains (args) {\n const format = function (string) {\n return Cast.toString(string).toLowerCase();\n };\n return format(args.STRING1).includes(format(args.STRING2));\n }\n\n mod (args) {\n const n = Cast.toNumber(args.NUM1);\n const modulus = Cast.toNumber(args.NUM2);\n let result = n % modulus;\n // Scratch mod uses floored division instead of truncated division.\n if (result / modulus < 0) result += modulus;\n return result;\n }\n\n round (args) {\n return Math.round(Cast.toNumber(args.NUM));\n }\n\n mathop (args) {\n const operator = Cast.toString(args.OPERATOR).toLowerCase();\n const n = Cast.toNumber(args.NUM);\n switch (operator) {\n case 'abs': return Math.abs(n);\n case 'floor': return Math.floor(n);\n case 'ceiling': return Math.ceil(n);\n case 'sqrt': return Math.sqrt(n);\n case 'sin': return parseFloat(Math.sin((Math.PI * n) / 180).toFixed(10));\n case 'cos': return parseFloat(Math.cos((Math.PI * n) / 180).toFixed(10));\n case 'tan': return MathUtil.tan(n);\n case 'asin': return (Math.asin(n) * 180) / Math.PI;\n case 'acos': return (Math.acos(n) * 180) / Math.PI;\n case 'atan': return (Math.atan(n) * 180) / Math.PI;\n case 'ln': return Math.log(n);\n case 'log': return Math.log(n) / Math.LN10;\n case 'e ^': return Math.exp(n);\n case '10 ^': return Math.pow(10, n);\n }\n return 0;\n }\n}\n\nmodule.exports = Scratch3OperatorsBlocks;\n","class Scratch3ProcedureBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n procedures_definition: this.definition,\n procedures_call: this.call,\n argument_reporter_string_number: this.argumentReporterStringNumber,\n argument_reporter_boolean: this.argumentReporterBoolean\n };\n }\n\n definition () {\n // No-op: execute the blocks.\n }\n\n call (args, util) {\n if (!util.stackFrame.executed) {\n const procedureCode = args.mutation.proccode;\n const paramNamesIdsAndDefaults = util.getProcedureParamNamesIdsAndDefaults(procedureCode);\n\n // If null, procedure could not be found, which can happen if custom\n // block is dragged between sprites without the definition.\n // Match Scratch 2.0 behavior and noop.\n if (paramNamesIdsAndDefaults === null) {\n return;\n }\n\n const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults;\n\n // Initialize params for the current stackFrame to {}, even if the procedure does\n // not take any arguments. This is so that `getParam` down the line does not look\n // at earlier stack frames for the values of a given parameter (#1729)\n util.initParams();\n for (let i = 0; i < paramIds.length; i++) {\n if (args.hasOwnProperty(paramIds[i])) {\n util.pushParam(paramNames[i], args[paramIds[i]]);\n } else {\n util.pushParam(paramNames[i], paramDefaults[i]);\n }\n }\n\n util.stackFrame.executed = true;\n util.startProcedure(procedureCode);\n }\n }\n\n argumentReporterStringNumber (args, util) {\n const value = util.getParam(args.VALUE);\n if (value === null) {\n // When the parameter is not found in the most recent procedure\n // call, the default is always 0.\n return 0;\n }\n return value;\n }\n\n argumentReporterBoolean (args, util) {\n const value = util.getParam(args.VALUE);\n if (value === null) {\n // When the parameter is not found in the most recent procedure\n // call, the default is always 0.\n return 0;\n }\n return value;\n }\n}\n\nmodule.exports = Scratch3ProcedureBlocks;\n","const Cast = require('../util/cast');\nconst Timer = require('../util/timer');\nconst getMonitorIdForBlockWithArgs = require('../util/get-monitor-id');\n\nclass Scratch3SensingBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n /**\n * The \"answer\" block value.\n * @type {string}\n */\n this._answer = '';\n\n /**\n * The timer utility.\n * @type {Timer}\n */\n this._timer = new Timer();\n\n /**\n * The stored microphone loudness measurement.\n * @type {number}\n */\n this._cachedLoudness = -1;\n\n /**\n * The time of the most recent microphone loudness measurement.\n * @type {number}\n */\n this._cachedLoudnessTimestamp = 0;\n\n /**\n * The list of queued questions and respective `resolve` callbacks.\n * @type {!Array}\n */\n this._questionList = [];\n\n this.runtime.on('ANSWER', this._onAnswer.bind(this));\n this.runtime.on('PROJECT_START', this._resetAnswer.bind(this));\n this.runtime.on('PROJECT_STOP_ALL', this._clearAllQuestions.bind(this));\n this.runtime.on('STOP_FOR_TARGET', this._clearTargetQuestions.bind(this));\n this.runtime.on('RUNTIME_DISPOSED', this._resetAnswer.bind(this));\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n sensing_touchingobject: this.touchingObject,\n sensing_touchingcolor: this.touchingColor,\n sensing_coloristouchingcolor: this.colorTouchingColor,\n sensing_distanceto: this.distanceTo,\n sensing_timer: this.getTimer,\n sensing_resettimer: this.resetTimer,\n sensing_of: this.getAttributeOf,\n sensing_mousex: this.getMouseX,\n sensing_mousey: this.getMouseY,\n sensing_setdragmode: this.setDragMode,\n sensing_mousedown: this.getMouseDown,\n sensing_keypressed: this.getKeyPressed,\n sensing_current: this.current,\n sensing_dayssince2000: this.daysSince2000,\n sensing_loudness: this.getLoudness,\n sensing_loud: this.isLoud,\n sensing_askandwait: this.askAndWait,\n sensing_answer: this.getAnswer,\n sensing_username: this.getUsername,\n sensing_userid: () => {} // legacy no-op block\n };\n }\n\n getMonitored () {\n return {\n sensing_answer: {\n getId: () => 'answer'\n },\n sensing_loudness: {\n getId: () => 'loudness'\n },\n sensing_timer: {\n getId: () => 'timer'\n },\n sensing_current: {\n // This is different from the default toolbox xml id in order to support\n // importing multiple monitors from the same opcode from sb2 files,\n // something that is not currently supported in scratch 3.\n getId: (_, fields) => getMonitorIdForBlockWithArgs('current', fields) // _${param}`\n }\n };\n }\n\n _onAnswer (answer) {\n this._answer = answer;\n const questionObj = this._questionList.shift();\n if (questionObj) {\n const [_question, resolve, target, wasVisible, wasStage] = questionObj;\n // If the target was visible when asked, hide the say bubble unless the target was the stage.\n if (wasVisible && !wasStage) {\n this.runtime.emit('SAY', target, 'say', '');\n }\n resolve();\n this._askNextQuestion();\n }\n }\n\n _resetAnswer () {\n this._answer = '';\n }\n\n _enqueueAsk (question, resolve, target, wasVisible, wasStage) {\n this._questionList.push([question, resolve, target, wasVisible, wasStage]);\n }\n\n _askNextQuestion () {\n if (this._questionList.length > 0) {\n const [question, _resolve, target, wasVisible, wasStage] = this._questionList[0];\n // If the target is visible, emit a blank question and use the\n // say event to trigger a bubble unless the target was the stage.\n if (wasVisible && !wasStage) {\n this.runtime.emit('SAY', target, 'say', question);\n this.runtime.emit('QUESTION', '');\n } else {\n this.runtime.emit('QUESTION', question);\n }\n }\n }\n\n _clearAllQuestions () {\n this._questionList = [];\n this.runtime.emit('QUESTION', null);\n }\n\n _clearTargetQuestions (stopTarget) {\n const currentlyAsking = this._questionList.length > 0 && this._questionList[0][2] === stopTarget;\n this._questionList = this._questionList.filter(question => (\n question[2] !== stopTarget\n ));\n\n if (currentlyAsking) {\n this.runtime.emit('SAY', stopTarget, 'say', '');\n if (this._questionList.length > 0) {\n this._askNextQuestion();\n } else {\n this.runtime.emit('QUESTION', null);\n }\n }\n }\n\n askAndWait (args, util) {\n const _target = util.target;\n return new Promise(resolve => {\n const isQuestionAsked = this._questionList.length > 0;\n this._enqueueAsk(String(args.QUESTION), resolve, _target, _target.visible, _target.isStage);\n if (!isQuestionAsked) {\n this._askNextQuestion();\n }\n });\n }\n\n getAnswer () {\n return this._answer;\n }\n\n touchingObject (args, util) {\n return util.target.isTouchingObject(args.TOUCHINGOBJECTMENU);\n }\n\n touchingColor (args, util) {\n const color = Cast.toRgbColorList(args.COLOR);\n return util.target.isTouchingColor(color);\n }\n\n colorTouchingColor (args, util) {\n const maskColor = Cast.toRgbColorList(args.COLOR);\n const targetColor = Cast.toRgbColorList(args.COLOR2);\n return util.target.colorIsTouchingColor(targetColor, maskColor);\n }\n\n distanceTo (args, util) {\n if (util.target.isStage) return 10000;\n\n let targetX = 0;\n let targetY = 0;\n if (args.DISTANCETOMENU === '_mouse_') {\n targetX = util.ioQuery('mouse', 'getScratchX');\n targetY = util.ioQuery('mouse', 'getScratchY');\n } else {\n args.DISTANCETOMENU = Cast.toString(args.DISTANCETOMENU);\n const distTarget = this.runtime.getSpriteTargetByName(\n args.DISTANCETOMENU\n );\n if (!distTarget) return 10000;\n targetX = distTarget.x;\n targetY = distTarget.y;\n }\n\n const dx = util.target.x - targetX;\n const dy = util.target.y - targetY;\n return Math.sqrt((dx * dx) + (dy * dy));\n }\n\n setDragMode (args, util) {\n util.target.setDraggable(args.DRAG_MODE === 'draggable');\n }\n\n getTimer (args, util) {\n return util.ioQuery('clock', 'projectTimer');\n }\n\n resetTimer (args, util) {\n util.ioQuery('clock', 'resetProjectTimer');\n }\n\n getMouseX (args, util) {\n return util.ioQuery('mouse', 'getScratchX');\n }\n\n getMouseY (args, util) {\n return util.ioQuery('mouse', 'getScratchY');\n }\n\n getMouseDown (args, util) {\n return util.ioQuery('mouse', 'getIsDown');\n }\n\n current (args) {\n const menuOption = Cast.toString(args.CURRENTMENU).toLowerCase();\n const date = new Date();\n switch (menuOption) {\n case 'year': return date.getFullYear();\n case 'month': return date.getMonth() + 1; // getMonth is zero-based\n case 'date': return date.getDate();\n case 'dayofweek': return date.getDay() + 1; // getDay is zero-based, Sun=0\n case 'hour': return date.getHours();\n case 'minute': return date.getMinutes();\n case 'second': return date.getSeconds();\n }\n return 0;\n }\n\n getKeyPressed (args, util) {\n return util.ioQuery('keyboard', 'getKeyIsDown', [args.KEY_OPTION]);\n }\n\n daysSince2000 () {\n const msPerDay = 24 * 60 * 60 * 1000;\n const start = new Date(2000, 0, 1); // Months are 0-indexed.\n const today = new Date();\n const dstAdjust = today.getTimezoneOffset() - start.getTimezoneOffset();\n let mSecsSinceStart = today.valueOf() - start.valueOf();\n mSecsSinceStart += ((today.getTimezoneOffset() - dstAdjust) * 60 * 1000);\n return mSecsSinceStart / msPerDay;\n }\n\n getLoudness () {\n if (typeof this.runtime.audioEngine === 'undefined') return -1;\n if (this.runtime.currentStepTime === null) return -1;\n\n // Only measure loudness once per step\n const timeSinceLoudness = this._timer.time() - this._cachedLoudnessTimestamp;\n if (timeSinceLoudness < this.runtime.currentStepTime) {\n return this._cachedLoudness;\n }\n\n this._cachedLoudnessTimestamp = this._timer.time();\n this._cachedLoudness = this.runtime.audioEngine.getLoudness();\n return this._cachedLoudness;\n }\n\n isLoud () {\n return this.getLoudness() > 10;\n }\n\n getAttributeOf (args) {\n let attrTarget;\n\n if (args.OBJECT === '_stage_') {\n attrTarget = this.runtime.getTargetForStage();\n } else {\n args.OBJECT = Cast.toString(args.OBJECT);\n attrTarget = this.runtime.getSpriteTargetByName(args.OBJECT);\n }\n\n // attrTarget can be undefined if the target does not exist\n // (e.g. single sprite uploaded from larger project referencing\n // another sprite that wasn't uploaded)\n if (!attrTarget) return 0;\n\n // Generic attributes\n if (attrTarget.isStage) {\n switch (args.PROPERTY) {\n // Scratch 1.4 support\n case 'background #': return attrTarget.currentCostume + 1;\n\n case 'backdrop #': return attrTarget.currentCostume + 1;\n case 'backdrop name':\n return attrTarget.getCostumes()[attrTarget.currentCostume].name;\n case 'volume': return attrTarget.volume;\n }\n } else {\n switch (args.PROPERTY) {\n case 'x position': return attrTarget.x;\n case 'y position': return attrTarget.y;\n case 'direction': return attrTarget.direction;\n case 'costume #': return attrTarget.currentCostume + 1;\n case 'costume name':\n return attrTarget.getCostumes()[attrTarget.currentCostume].name;\n case 'size': return attrTarget.size;\n case 'volume': return attrTarget.volume;\n }\n }\n\n // Target variables.\n const varName = args.PROPERTY;\n const variable = attrTarget.lookupVariableByNameAndType(varName, '', true);\n if (variable) {\n return variable.value;\n }\n\n // Otherwise, 0\n return 0;\n }\n\n getUsername (args, util) {\n return util.ioQuery('userData', 'getUsername');\n }\n}\n\nmodule.exports = Scratch3SensingBlocks;\n","const MathUtil = require('../util/math-util');\nconst Cast = require('../util/cast');\nconst Clone = require('../util/clone');\n\n/**\n * Occluded boolean value to make its use more understandable.\n * @const {boolean}\n */\nconst STORE_WAITING = true;\n\nclass Scratch3SoundBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n this.waitingSounds = {};\n\n // Clear sound effects on green flag and stop button events.\n this.stopAllSounds = this.stopAllSounds.bind(this);\n this._stopWaitingSoundsForTarget = this._stopWaitingSoundsForTarget.bind(this);\n this._clearEffectsForAllTargets = this._clearEffectsForAllTargets.bind(this);\n if (this.runtime) {\n this.runtime.on('PROJECT_STOP_ALL', this.stopAllSounds);\n this.runtime.on('PROJECT_STOP_ALL', this._clearEffectsForAllTargets);\n this.runtime.on('STOP_FOR_TARGET', this._stopWaitingSoundsForTarget);\n this.runtime.on('PROJECT_START', this._clearEffectsForAllTargets);\n }\n\n this._onTargetCreated = this._onTargetCreated.bind(this);\n if (this.runtime) {\n runtime.on('targetWasCreated', this._onTargetCreated);\n }\n }\n\n /**\n * The key to load & store a target's sound-related state.\n * @type {string}\n */\n static get STATE_KEY () {\n return 'Scratch.sound';\n }\n\n /**\n * The default sound-related state, to be used when a target has no existing sound state.\n * @type {SoundState}\n */\n static get DEFAULT_SOUND_STATE () {\n return {\n effects: {\n pitch: 0,\n pan: 0\n }\n };\n }\n\n /**\n * The minimum and maximum MIDI note numbers, for clamping the input to play note.\n * @type {{min: number, max: number}}\n */\n static get MIDI_NOTE_RANGE () {\n return {min: 36, max: 96}; // C2 to C7\n }\n\n /**\n * The minimum and maximum beat values, for clamping the duration of play note, play drum and rest.\n * 100 beats at the default tempo of 60bpm is 100 seconds.\n * @type {{min: number, max: number}}\n */\n static get BEAT_RANGE () {\n return {min: 0, max: 100};\n }\n\n /** The minimum and maximum tempo values, in bpm.\n * @type {{min: number, max: number}}\n */\n static get TEMPO_RANGE () {\n return {min: 20, max: 500};\n }\n\n /** The minimum and maximum values for each sound effect.\n * @type {{effect:{min: number, max: number}}}\n */\n static get EFFECT_RANGE () {\n return {\n pitch: {min: -360, max: 360}, // -3 to 3 octaves\n pan: {min: -100, max: 100} // 100% left to 100% right\n };\n }\n\n /**\n * @param {Target} target - collect sound state for this target.\n * @returns {SoundState} the mutable sound state associated with that target. This will be created if necessary.\n * @private\n */\n _getSoundState (target) {\n let soundState = target.getCustomState(Scratch3SoundBlocks.STATE_KEY);\n if (!soundState) {\n soundState = Clone.simple(Scratch3SoundBlocks.DEFAULT_SOUND_STATE);\n target.setCustomState(Scratch3SoundBlocks.STATE_KEY, soundState);\n target.soundEffects = soundState.effects;\n }\n return soundState;\n }\n\n /**\n * When a Target is cloned, clone the sound state.\n * @param {Target} newTarget - the newly created target.\n * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.\n * @listens Runtime#event:targetWasCreated\n * @private\n */\n _onTargetCreated (newTarget, sourceTarget) {\n if (sourceTarget) {\n const soundState = sourceTarget.getCustomState(Scratch3SoundBlocks.STATE_KEY);\n if (soundState && newTarget) {\n newTarget.setCustomState(Scratch3SoundBlocks.STATE_KEY, Clone.simple(soundState));\n this._syncEffectsForTarget(newTarget);\n }\n }\n }\n\n /**\n * Retrieve the block primitives implemented by this package.\n * @return {object.} Mapping of opcode to Function.\n */\n getPrimitives () {\n return {\n sound_play: this.playSound,\n sound_playuntildone: this.playSoundAndWait,\n sound_stopallsounds: this.stopAllSounds,\n sound_seteffectto: this.setEffect,\n sound_changeeffectby: this.changeEffect,\n sound_cleareffects: this.clearEffects,\n sound_sounds_menu: this.soundsMenu,\n sound_beats_menu: this.beatsMenu,\n sound_effects_menu: this.effectsMenu,\n sound_setvolumeto: this.setVolume,\n sound_changevolumeby: this.changeVolume,\n sound_volume: this.getVolume\n };\n }\n\n getMonitored () {\n return {\n sound_volume: {\n isSpriteSpecific: true,\n getId: targetId => `${targetId}_volume`\n }\n };\n }\n\n playSound (args, util) {\n // Don't return the promise, it's the only difference for AndWait\n this._playSound(args, util);\n }\n\n playSoundAndWait (args, util) {\n return this._playSound(args, util, STORE_WAITING);\n }\n\n _playSound (args, util, storeWaiting) {\n const index = this._getSoundIndex(args.SOUND_MENU, util);\n if (index >= 0) {\n const {target} = util;\n const {sprite} = target;\n const {soundId} = sprite.sounds[index];\n if (sprite.soundBank) {\n if (storeWaiting === STORE_WAITING) {\n this._addWaitingSound(target.id, soundId);\n } else {\n this._removeWaitingSound(target.id, soundId);\n }\n return sprite.soundBank.playSound(target, soundId);\n }\n }\n }\n\n _addWaitingSound (targetId, soundId) {\n if (!this.waitingSounds[targetId]) {\n this.waitingSounds[targetId] = new Set();\n }\n this.waitingSounds[targetId].add(soundId);\n }\n\n _removeWaitingSound (targetId, soundId) {\n if (!this.waitingSounds[targetId]) {\n return;\n }\n this.waitingSounds[targetId].delete(soundId);\n }\n\n _getSoundIndex (soundName, util) {\n // if the sprite has no sounds, return -1\n const len = util.target.sprite.sounds.length;\n if (len === 0) {\n return -1;\n }\n\n // look up by name first\n const index = this.getSoundIndexByName(soundName, util);\n if (index !== -1) {\n return index;\n }\n\n // then try using the sound name as a 1-indexed index\n const oneIndexedIndex = parseInt(soundName, 10);\n if (!isNaN(oneIndexedIndex)) {\n return MathUtil.wrapClamp(oneIndexedIndex - 1, 0, len - 1);\n }\n\n // could not be found as a name or converted to index, return -1\n return -1;\n }\n\n getSoundIndexByName (soundName, util) {\n const sounds = util.target.sprite.sounds;\n for (let i = 0; i < sounds.length; i++) {\n if (sounds[i].name === soundName) {\n return i;\n }\n }\n // if there is no sound by that name, return -1\n return -1;\n }\n\n stopAllSounds () {\n if (this.runtime.targets === null) return;\n const allTargets = this.runtime.targets;\n for (let i = 0; i < allTargets.length; i++) {\n this._stopAllSoundsForTarget(allTargets[i]);\n }\n }\n\n _stopAllSoundsForTarget (target) {\n if (target.sprite.soundBank) {\n target.sprite.soundBank.stopAllSounds(target);\n if (this.waitingSounds[target.id]) {\n this.waitingSounds[target.id].clear();\n }\n }\n }\n\n _stopWaitingSoundsForTarget (target) {\n if (target.sprite.soundBank) {\n if (this.waitingSounds[target.id]) {\n for (const soundId of this.waitingSounds[target.id].values()) {\n target.sprite.soundBank.stop(target, soundId);\n }\n this.waitingSounds[target.id].clear();\n }\n }\n }\n\n setEffect (args, util) {\n return this._updateEffect(args, util, false);\n }\n\n changeEffect (args, util) {\n return this._updateEffect(args, util, true);\n }\n\n _updateEffect (args, util, change) {\n const effect = Cast.toString(args.EFFECT).toLowerCase();\n const value = Cast.toNumber(args.VALUE);\n\n const soundState = this._getSoundState(util.target);\n if (!soundState.effects.hasOwnProperty(effect)) return;\n\n if (change) {\n soundState.effects[effect] += value;\n } else {\n soundState.effects[effect] = value;\n }\n\n const {min, max} = Scratch3SoundBlocks.EFFECT_RANGE[effect];\n soundState.effects[effect] = MathUtil.clamp(soundState.effects[effect], min, max);\n\n this._syncEffectsForTarget(util.target);\n // Yield until the next tick.\n return Promise.resolve();\n }\n\n _syncEffectsForTarget (target) {\n if (!target || !target.sprite.soundBank) return;\n target.soundEffects = this._getSoundState(target).effects;\n\n target.sprite.soundBank.setEffects(target);\n }\n\n clearEffects (args, util) {\n this._clearEffectsForTarget(util.target);\n }\n\n _clearEffectsForTarget (target) {\n const soundState = this._getSoundState(target);\n for (const effect in soundState.effects) {\n if (!soundState.effects.hasOwnProperty(effect)) continue;\n soundState.effects[effect] = 0;\n }\n this._syncEffectsForTarget(target);\n }\n\n _clearEffectsForAllTargets () {\n if (this.runtime.targets === null) return;\n const allTargets = this.runtime.targets;\n for (let i = 0; i < allTargets.length; i++) {\n this._clearEffectsForTarget(allTargets[i]);\n }\n }\n\n setVolume (args, util) {\n const volume = Cast.toNumber(args.VOLUME);\n return this._updateVolume(volume, util);\n }\n\n changeVolume (args, util) {\n const volume = Cast.toNumber(args.VOLUME) + util.target.volume;\n return this._updateVolume(volume, util);\n }\n\n _updateVolume (volume, util) {\n volume = MathUtil.clamp(volume, 0, 100);\n util.target.volume = volume;\n this._syncEffectsForTarget(util.target);\n\n // Yield until the next tick.\n return Promise.resolve();\n }\n\n getVolume (args, util) {\n return util.target.volume;\n }\n\n soundsMenu (args) {\n return args.SOUND_MENU;\n }\n\n beatsMenu (args) {\n return args.BEATS;\n }\n\n effectsMenu (args) {\n return args.EFFECT;\n }\n}\n\nmodule.exports = Scratch3SoundBlocks;\n","const SharedDispatch = require('./shared-dispatch');\n\nconst log = require('../util/log');\n\n/**\n * This class serves as the central broker for message dispatch. It expects to operate on the main thread / Window and\n * it must be informed of any Worker threads which will participate in the messaging system. From any context in the\n * messaging system, the dispatcher's \"call\" method can call any method on any \"service\" provided in any participating\n * context. The dispatch system will forward function arguments and return values across worker boundaries as needed.\n * @see {WorkerDispatch}\n */\nclass CentralDispatch extends SharedDispatch {\n constructor () {\n super();\n\n /**\n * Map of channel name to worker or local service provider.\n * If the entry is a Worker, the service is provided by an object on that worker.\n * Otherwise, the service is provided locally and methods on the service will be called directly.\n * @see {setService}\n * @type {object.}\n */\n this.services = {};\n\n /**\n * The constructor we will use to recognize workers.\n * @type {Function}\n */\n this.workerClass = (typeof Worker === 'undefined' ? null : Worker);\n\n /**\n * List of workers attached to this dispatcher.\n * @type {Array}\n */\n this.workers = [];\n }\n\n /**\n * Synchronously call a particular method on a particular service provided locally.\n * Calling this function on a remote service will fail.\n * @param {string} service - the name of the service.\n * @param {string} method - the name of the method.\n * @param {*} [args] - the arguments to be copied to the method, if any.\n * @returns {*} - the return value of the service method.\n */\n callSync (service, method, ...args) {\n const {provider, isRemote} = this._getServiceProvider(service);\n if (provider) {\n if (isRemote) {\n throw new Error(`Cannot use 'callSync' on remote provider for service ${service}.`);\n }\n\n return provider[method].apply(provider, args);\n }\n throw new Error(`Provider not found for service: ${service}`);\n }\n\n /**\n * Synchronously set a local object as the global provider of the specified service.\n * WARNING: Any method on the provider can be called from any worker within the dispatch system.\n * @param {string} service - a globally unique string identifying this service. Examples: 'vm', 'gui', 'extension9'.\n * @param {object} provider - a local object which provides this service.\n */\n setServiceSync (service, provider) {\n if (this.services.hasOwnProperty(service)) {\n log.warn(`Central dispatch replacing existing service provider for ${service}`);\n }\n this.services[service] = provider;\n }\n\n /**\n * Set a local object as the global provider of the specified service.\n * WARNING: Any method on the provider can be called from any worker within the dispatch system.\n * @param {string} service - a globally unique string identifying this service. Examples: 'vm', 'gui', 'extension9'.\n * @param {object} provider - a local object which provides this service.\n * @returns {Promise} - a promise which will resolve once the service is registered.\n */\n setService (service, provider) {\n /** Return a promise for consistency with {@link WorkerDispatch#setService} */\n try {\n this.setServiceSync(service, provider);\n return Promise.resolve();\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n /**\n * Add a worker to the message dispatch system. The worker must implement a compatible message dispatch framework.\n * The dispatcher will immediately attempt to \"handshake\" with the worker.\n * @param {Worker} worker - the worker to add into the dispatch system.\n */\n addWorker (worker) {\n if (this.workers.indexOf(worker) === -1) {\n this.workers.push(worker);\n worker.onmessage = this._onMessage.bind(this, worker);\n this._remoteCall(worker, 'dispatch', 'handshake').catch(e => {\n log.error(`Could not handshake with worker: ${JSON.stringify(e)}`);\n });\n } else {\n log.warn('Central dispatch ignoring attempt to add duplicate worker');\n }\n }\n\n /**\n * Fetch the service provider object for a particular service name.\n * @override\n * @param {string} service - the name of the service to look up\n * @returns {{provider:(object|Worker), isRemote:boolean}} - the means to contact the service, if found\n * @protected\n */\n _getServiceProvider (service) {\n const provider = this.services[service];\n return provider && {\n provider,\n isRemote: Boolean(this.workerClass && provider instanceof this.workerClass)\n };\n }\n\n /**\n * Handle a call message sent to the dispatch service itself\n * @override\n * @param {Worker} worker - the worker which sent the message.\n * @param {DispatchCallMessage} message - the message to be handled.\n * @returns {Promise|undefined} - a promise for the results of this operation, if appropriate\n * @protected\n */\n _onDispatchMessage (worker, message) {\n let promise;\n switch (message.method) {\n case 'setService':\n promise = this.setService(message.args[0], worker);\n break;\n default:\n log.error(`Central dispatch received message for unknown method: ${message.method}`);\n }\n return promise;\n }\n}\n\nmodule.exports = new CentralDispatch();\n","const log = require('../util/log');\n\n/**\n * @typedef {object} DispatchCallMessage - a message to the dispatch system representing a service method call\n * @property {*} responseId - send a response message with this response ID. See {@link DispatchResponseMessage}\n * @property {string} service - the name of the service to be called\n * @property {string} method - the name of the method to be called\n * @property {Array|undefined} args - the arguments to be passed to the method\n */\n\n/**\n * @typedef {object} DispatchResponseMessage - a message to the dispatch system representing the results of a call\n * @property {*} responseId - a copy of the response ID from the call which generated this response\n * @property {*|undefined} error - if this is truthy, then it contains results from a failed call (such as an exception)\n * @property {*|undefined} result - if error is not truthy, then this contains the return value of the call (if any)\n */\n\n/**\n * @typedef {DispatchCallMessage|DispatchResponseMessage} DispatchMessage\n * Any message to the dispatch system.\n */\n\n/**\n * The SharedDispatch class is responsible for dispatch features shared by\n * {@link CentralDispatch} and {@link WorkerDispatch}.\n */\nclass SharedDispatch {\n constructor () {\n /**\n * List of callback registrations for promises waiting for a response from a call to a service on another\n * worker. A callback registration is an array of [resolve,reject] Promise functions.\n * Calls to local services don't enter this list.\n * @type {Array.}\n */\n this.callbacks = [];\n\n /**\n * The next response ID to be used.\n * @type {int}\n */\n this.nextResponseId = 0;\n }\n\n /**\n * Call a particular method on a particular service, regardless of whether that service is provided locally or on\n * a worker. If the service is provided by a worker, the `args` will be copied using the Structured Clone\n * algorithm, except for any items which are also in the `transfer` list. Ownership of those items will be\n * transferred to the worker, and they should not be used after this call.\n * @example\n * dispatcher.call('vm', 'setData', 'cat', 42);\n * // this finds the worker for the 'vm' service, then on that worker calls:\n * vm.setData('cat', 42);\n * @param {string} service - the name of the service.\n * @param {string} method - the name of the method.\n * @param {*} [args] - the arguments to be copied to the method, if any.\n * @returns {Promise} - a promise for the return value of the service method.\n */\n call (service, method, ...args) {\n return this.transferCall(service, method, null, ...args);\n }\n\n /**\n * Call a particular method on a particular service, regardless of whether that service is provided locally or on\n * a worker. If the service is provided by a worker, the `args` will be copied using the Structured Clone\n * algorithm, except for any items which are also in the `transfer` list. Ownership of those items will be\n * transferred to the worker, and they should not be used after this call.\n * @example\n * dispatcher.transferCall('vm', 'setData', [myArrayBuffer], 'cat', myArrayBuffer);\n * // this finds the worker for the 'vm' service, transfers `myArrayBuffer` to it, then on that worker calls:\n * vm.setData('cat', myArrayBuffer);\n * @param {string} service - the name of the service.\n * @param {string} method - the name of the method.\n * @param {Array} [transfer] - objects to be transferred instead of copied. Must be present in `args` to be useful.\n * @param {*} [args] - the arguments to be copied to the method, if any.\n * @returns {Promise} - a promise for the return value of the service method.\n */\n transferCall (service, method, transfer, ...args) {\n try {\n const {provider, isRemote} = this._getServiceProvider(service);\n if (provider) {\n if (isRemote) {\n return this._remoteTransferCall(provider, service, method, transfer, ...args);\n }\n\n const result = provider[method].apply(provider, args);\n return Promise.resolve(result);\n }\n return Promise.reject(new Error(`Service not found: ${service}`));\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n /**\n * Check if a particular service lives on another worker.\n * @param {string} service - the service to check.\n * @returns {boolean} - true if the service is remote (calls must cross a Worker boundary), false otherwise.\n * @private\n */\n _isRemoteService (service) {\n return this._getServiceProvider(service).isRemote;\n }\n\n /**\n * Like {@link call}, but force the call to be posted through a particular communication channel.\n * @param {object} provider - send the call through this object's `postMessage` function.\n * @param {string} service - the name of the service.\n * @param {string} method - the name of the method.\n * @param {*} [args] - the arguments to be copied to the method, if any.\n * @returns {Promise} - a promise for the return value of the service method.\n */\n _remoteCall (provider, service, method, ...args) {\n return this._remoteTransferCall(provider, service, method, null, ...args);\n }\n\n /**\n * Like {@link transferCall}, but force the call to be posted through a particular communication channel.\n * @param {object} provider - send the call through this object's `postMessage` function.\n * @param {string} service - the name of the service.\n * @param {string} method - the name of the method.\n * @param {Array} [transfer] - objects to be transferred instead of copied. Must be present in `args` to be useful.\n * @param {*} [args] - the arguments to be copied to the method, if any.\n * @returns {Promise} - a promise for the return value of the service method.\n */\n _remoteTransferCall (provider, service, method, transfer, ...args) {\n return new Promise((resolve, reject) => {\n const responseId = this._storeCallbacks(resolve, reject);\n\n /** @TODO: remove this hack! this is just here so we don't try to send `util` to a worker */\n if ((args.length > 0) && (typeof args[args.length - 1].yield === 'function')) {\n args.pop();\n }\n\n if (transfer) {\n provider.postMessage({service, method, responseId, args}, transfer);\n } else {\n provider.postMessage({service, method, responseId, args});\n }\n });\n }\n\n /**\n * Store callback functions pending a response message.\n * @param {Function} resolve - function to call if the service method returns.\n * @param {Function} reject - function to call if the service method throws.\n * @returns {*} - a unique response ID for this set of callbacks. See {@link _deliverResponse}.\n * @protected\n */\n _storeCallbacks (resolve, reject) {\n const responseId = this.nextResponseId++;\n this.callbacks[responseId] = [resolve, reject];\n return responseId;\n }\n\n /**\n * Deliver call response from a worker. This should only be called as the result of a message from a worker.\n * @param {int} responseId - the response ID of the callback set to call.\n * @param {DispatchResponseMessage} message - the message containing the response value(s).\n * @protected\n */\n _deliverResponse (responseId, message) {\n try {\n const [resolve, reject] = this.callbacks[responseId];\n delete this.callbacks[responseId];\n if (message.error) {\n reject(message.error);\n } else {\n resolve(message.result);\n }\n } catch (e) {\n log.error(`Dispatch callback failed: ${JSON.stringify(e)}`);\n }\n }\n\n /**\n * Handle a message event received from a connected worker.\n * @param {Worker} worker - the worker which sent the message, or the global object if running in a worker.\n * @param {MessageEvent} event - the message event to be handled.\n * @protected\n */\n _onMessage (worker, event) {\n /** @type {DispatchMessage} */\n const message = event.data;\n message.args = message.args || [];\n let promise;\n if (message.service) {\n if (message.service === 'dispatch') {\n promise = this._onDispatchMessage(worker, message);\n } else {\n promise = this.call(message.service, message.method, ...message.args);\n }\n } else if (typeof message.responseId === 'undefined') {\n log.error(`Dispatch caught malformed message from a worker: ${JSON.stringify(event)}`);\n } else {\n this._deliverResponse(message.responseId, message);\n }\n if (promise) {\n if (typeof message.responseId === 'undefined') {\n log.error(`Dispatch message missing required response ID: ${JSON.stringify(event)}`);\n } else {\n promise.then(\n result => worker.postMessage({responseId: message.responseId, result}),\n error => worker.postMessage({responseId: message.responseId, error})\n );\n }\n }\n }\n\n /**\n * Fetch the service provider object for a particular service name.\n * @abstract\n * @param {string} service - the name of the service to look up\n * @returns {{provider:(object|Worker), isRemote:boolean}} - the means to contact the service, if found\n * @protected\n */\n _getServiceProvider (service) {\n throw new Error(`Could not get provider for ${service}: _getServiceProvider not implemented`);\n }\n\n /**\n * Handle a call message sent to the dispatch service itself\n * @abstract\n * @param {Worker} worker - the worker which sent the message.\n * @param {DispatchCallMessage} message - the message to be handled.\n * @returns {Promise|undefined} - a promise for the results of this operation, if appropriate\n * @private\n */\n _onDispatchMessage (worker, message) {\n throw new Error(`Unimplemented dispatch message handler cannot handle ${message.method} method`);\n }\n}\n\nmodule.exports = SharedDispatch;\n","const mutationAdapter = require('./mutation-adapter');\nconst html = require('htmlparser2');\nconst uid = require('../util/uid');\n\n/**\n * Convert and an individual block DOM to the representation tree.\n * Based on Blockly's `domToBlockHeadless_`.\n * @param {Element} blockDOM DOM tree for an individual block.\n * @param {object} blocks Collection of blocks to add to.\n * @param {boolean} isTopBlock Whether blocks at this level are \"top blocks.\"\n * @param {?string} parent Parent block ID.\n * @return {undefined}\n */\nconst domToBlock = function (blockDOM, blocks, isTopBlock, parent) {\n if (!blockDOM.attribs.id) {\n blockDOM.attribs.id = uid();\n }\n\n // Block skeleton.\n const block = {\n id: blockDOM.attribs.id, // Block ID\n opcode: blockDOM.attribs.type, // For execution, \"event_whengreenflag\".\n inputs: {}, // Inputs to this block and the blocks they point to.\n fields: {}, // Fields on this block and their values.\n next: null, // Next block in the stack, if one exists.\n topLevel: isTopBlock, // If this block starts a stack.\n parent: parent, // Parent block ID, if available.\n shadow: blockDOM.name === 'shadow', // If this represents a shadow/slot.\n x: blockDOM.attribs.x, // X position of script, if top-level.\n y: blockDOM.attribs.y // Y position of script, if top-level.\n };\n\n // Add the block to the representation tree.\n blocks[block.id] = block;\n\n // Process XML children and find enclosed blocks, fields, etc.\n for (let i = 0; i < blockDOM.children.length; i++) {\n const xmlChild = blockDOM.children[i];\n // Enclosed blocks and shadows\n let childBlockNode = null;\n let childShadowNode = null;\n for (let j = 0; j < xmlChild.children.length; j++) {\n const grandChildNode = xmlChild.children[j];\n if (!grandChildNode.name) {\n // Non-XML tag node.\n continue;\n }\n const grandChildNodeName = grandChildNode.name.toLowerCase();\n if (grandChildNodeName === 'block') {\n childBlockNode = grandChildNode;\n } else if (grandChildNodeName === 'shadow') {\n childShadowNode = grandChildNode;\n }\n }\n\n // Use shadow block only if there's no real block node.\n if (!childBlockNode && childShadowNode) {\n childBlockNode = childShadowNode;\n }\n\n // Not all Blockly-type blocks are handled here,\n // as we won't be using all of them for Scratch.\n switch (xmlChild.name.toLowerCase()) {\n case 'field':\n {\n // Add the field to this block.\n const fieldName = xmlChild.attribs.name;\n // Add id in case it is a variable field\n const fieldId = xmlChild.attribs.id;\n let fieldData = '';\n if (xmlChild.children.length > 0 && xmlChild.children[0].data) {\n fieldData = xmlChild.children[0].data;\n } else {\n // If the child of the field with a data property\n // doesn't exist, set the data to an empty string.\n fieldData = '';\n }\n block.fields[fieldName] = {\n name: fieldName,\n id: fieldId,\n value: fieldData\n };\n const fieldVarType = xmlChild.attribs.variabletype;\n if (typeof fieldVarType === 'string') {\n block.fields[fieldName].variableType = fieldVarType;\n }\n break;\n }\n case 'comment':\n {\n block.comment = xmlChild.attribs.id;\n break;\n }\n case 'value':\n case 'statement':\n {\n // Recursively generate block structure for input block.\n domToBlock(childBlockNode, blocks, false, block.id);\n if (childShadowNode && childBlockNode !== childShadowNode) {\n // Also generate the shadow block.\n domToBlock(childShadowNode, blocks, false, block.id);\n }\n // Link this block's input to the child block.\n const inputName = xmlChild.attribs.name;\n block.inputs[inputName] = {\n name: inputName,\n block: childBlockNode.attribs.id,\n shadow: childShadowNode ? childShadowNode.attribs.id : null\n };\n break;\n }\n case 'next':\n {\n if (!childBlockNode || !childBlockNode.attribs) {\n // Invalid child block.\n continue;\n }\n // Recursively generate block structure for next block.\n domToBlock(childBlockNode, blocks, false, block.id);\n // Link next block to this block.\n block.next = childBlockNode.attribs.id;\n break;\n }\n case 'mutation':\n {\n block.mutation = mutationAdapter(xmlChild);\n break;\n }\n }\n }\n};\n\n/**\n * Convert outer blocks DOM from a Blockly CREATE event\n * to a usable form for the Scratch runtime.\n * This structure is based on Blockly xml.js:`domToWorkspace` and `domToBlock`.\n * @param {Element} blocksDOM DOM tree for this event.\n * @return {Array.} Usable list of blocks from this CREATE event.\n */\nconst domToBlocks = function (blocksDOM) {\n // At this level, there could be multiple blocks adjacent in the DOM tree.\n const blocks = {};\n for (let i = 0; i < blocksDOM.length; i++) {\n const block = blocksDOM[i];\n if (!block.name || !block.attribs) {\n continue;\n }\n const tagName = block.name.toLowerCase();\n if (tagName === 'block' || tagName === 'shadow') {\n domToBlock(block, blocks, true, null);\n }\n }\n // Flatten blocks object into a list.\n const blocksList = [];\n for (const b in blocks) {\n if (!blocks.hasOwnProperty(b)) continue;\n blocksList.push(blocks[b]);\n }\n return blocksList;\n};\n\n/**\n * Adapter between block creation events and block representation which can be\n * used by the Scratch runtime.\n * @param {object} e `Blockly.events.create` or `Blockly.events.endDrag`\n * @return {Array.} List of blocks from this CREATE event.\n */\nconst adapter = function (e) {\n // Validate input\n if (typeof e !== 'object') return;\n if (typeof e.xml !== 'object') return;\n\n return domToBlocks(html.parseDOM(e.xml.outerHTML, {decodeEntities: true}));\n};\n\nmodule.exports = adapter;\n","const Thread = require('./thread');\nconst Timer = require('../util/timer');\n\n/**\n * @fileoverview\n * Interface provided to block primitive functions for interacting with the\n * runtime, thread, target, and convenient methods.\n */\n\nclass BlockUtility {\n constructor (sequencer = null, thread = null) {\n /**\n * A sequencer block primitives use to branch or start procedures with\n * @type {?Sequencer}\n */\n this.sequencer = sequencer;\n\n /**\n * The block primitives thread with the block's target, stackFrame and\n * modifiable status.\n * @type {?Thread}\n */\n this.thread = thread;\n\n this._nowObj = {\n now: () => this.sequencer.runtime.currentMSecs\n };\n }\n\n /**\n * The target the primitive is working on.\n * @type {Target}\n */\n get target () {\n return this.thread.target;\n }\n\n /**\n * The runtime the block primitive is running in.\n * @type {Runtime}\n */\n get runtime () {\n return this.sequencer.runtime;\n }\n\n /**\n * Use the runtime's currentMSecs value as a timestamp value for now\n * This is useful in some cases where we need compatibility with Scratch 2\n * @type {function}\n */\n get nowObj () {\n if (this.runtime) {\n return this._nowObj;\n }\n return null;\n }\n\n /**\n * The stack frame used by loop and other blocks to track internal state.\n * @type {object}\n */\n get stackFrame () {\n const frame = this.thread.peekStackFrame();\n if (frame.executionContext === null) {\n frame.executionContext = {};\n }\n return frame.executionContext;\n }\n\n /**\n * Check the stack timer and return a boolean based on whether it has finished or not.\n * @return {boolean} - true if the stack timer has finished.\n */\n stackTimerFinished () {\n const timeElapsed = this.stackFrame.timer.timeElapsed();\n if (timeElapsed < this.stackFrame.duration) {\n return false;\n }\n return true;\n }\n\n /**\n * Check if the stack timer needs initialization.\n * @return {boolean} - true if the stack timer needs to be initialized.\n */\n stackTimerNeedsInit () {\n return !this.stackFrame.timer;\n }\n\n /**\n * Create and start a stack timer\n * @param {number} duration - a duration in milliseconds to set the timer for.\n */\n startStackTimer (duration) {\n if (this.nowObj) {\n this.stackFrame.timer = new Timer(this.nowObj);\n } else {\n this.stackFrame.timer = new Timer();\n }\n this.stackFrame.timer.start();\n this.stackFrame.duration = duration;\n }\n\n /**\n * Set the thread to yield.\n */\n yield () {\n this.thread.status = Thread.STATUS_YIELD;\n }\n\n /**\n * Set the thread to yield until the next tick of the runtime.\n */\n yieldTick () {\n this.thread.status = Thread.STATUS_YIELD_TICK;\n }\n\n /**\n * Start a branch in the current block.\n * @param {number} branchNum Which branch to step to (i.e., 1, 2).\n * @param {boolean} isLoop Whether this block is a loop.\n */\n startBranch (branchNum, isLoop) {\n this.sequencer.stepToBranch(this.thread, branchNum, isLoop);\n }\n\n /**\n * Stop all threads.\n */\n stopAll () {\n this.sequencer.runtime.stopAll();\n }\n\n /**\n * Stop threads other on this target other than the thread holding the\n * executed block.\n */\n stopOtherTargetThreads () {\n this.sequencer.runtime.stopForTarget(this.thread.target, this.thread);\n }\n\n /**\n * Stop this thread.\n */\n stopThisScript () {\n this.thread.stopThisScript();\n }\n\n /**\n * Start a specified procedure on this thread.\n * @param {string} procedureCode Procedure code for procedure to start.\n */\n startProcedure (procedureCode) {\n this.sequencer.stepToProcedure(this.thread, procedureCode);\n }\n\n /**\n * Get names and ids of parameters for the given procedure.\n * @param {string} procedureCode Procedure code for procedure to query.\n * @return {Array.} List of param names for a procedure.\n */\n getProcedureParamNamesAndIds (procedureCode) {\n return this.thread.target.blocks.getProcedureParamNamesAndIds(procedureCode);\n }\n\n /**\n * Get names, ids, and defaults of parameters for the given procedure.\n * @param {string} procedureCode Procedure code for procedure to query.\n * @return {Array.} List of param names for a procedure.\n */\n getProcedureParamNamesIdsAndDefaults (procedureCode) {\n return this.thread.target.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode);\n }\n\n /**\n * Initialize procedure parameters in the thread before pushing parameters.\n */\n initParams () {\n this.thread.initParams();\n }\n\n /**\n * Store a procedure parameter value by its name.\n * @param {string} paramName The procedure's parameter name.\n * @param {*} paramValue The procedure's parameter value.\n */\n pushParam (paramName, paramValue) {\n this.thread.pushParam(paramName, paramValue);\n }\n\n /**\n * Retrieve the stored parameter value for a given parameter name.\n * @param {string} paramName The procedure's parameter name.\n * @return {*} The parameter's current stored value.\n */\n getParam (paramName) {\n return this.thread.getParam(paramName);\n }\n\n /**\n * Start all relevant hats.\n * @param {!string} requestedHat Opcode of hats to start.\n * @param {object=} optMatchFields Optionally, fields to match on the hat.\n * @param {Target=} optTarget Optionally, a target to restrict to.\n * @return {Array.} List of threads started by this function.\n */\n startHats (requestedHat, optMatchFields, optTarget) {\n // Store thread and sequencer to ensure we can return to the calling block's context.\n // startHats may execute further blocks and dirty the BlockUtility's execution context\n // and confuse the calling block when we return to it.\n const callerThread = this.thread;\n const callerSequencer = this.sequencer;\n const result = this.sequencer.runtime.startHats(requestedHat, optMatchFields, optTarget);\n\n // Restore thread and sequencer to prior values before we return to the calling block.\n this.thread = callerThread;\n this.sequencer = callerSequencer;\n\n return result;\n }\n\n /**\n * Query a named IO device.\n * @param {string} device The name of like the device, like keyboard.\n * @param {string} func The name of the device's function to query.\n * @param {Array.<*>} args Arguments to pass to the device's function.\n * @return {*} The expected output for the device's function.\n */\n ioQuery (device, func, args) {\n // Find the I/O device and execute the query/function call.\n if (\n this.sequencer.runtime.ioDevices[device] &&\n this.sequencer.runtime.ioDevices[device][func]) {\n const devObject = this.sequencer.runtime.ioDevices[device];\n return devObject[func].apply(devObject, args);\n }\n }\n}\n\nmodule.exports = BlockUtility;\n","/**\n * @fileoverview\n * Access point for private method shared between blocks.js and execute.js for\n * caching execute information.\n */\n\n/**\n * A private method shared with execute to build an object containing the block\n * information execute needs and that is reset when other cached Blocks info is\n * reset.\n * @param {Blocks} blocks Blocks containing the expected blockId\n * @param {string} blockId blockId for the desired execute cache\n */\nexports.getCached = function () {\n throw new Error('blocks.js has not initialized BlocksExecuteCache');\n};\n\n// Call after the default throwing getCached is assigned for Blocks to replace.\nrequire('./blocks');\n","/**\n * @fileoverview\n * The BlocksRuntimeCache caches data about the top block of scripts so that\n * Runtime can iterate a targeted opcode and iterate the returned set faster.\n * Many top blocks need to match fields as well as opcode, since that matching\n * compares strings in uppercase we can go ahead and uppercase the cached value\n * so we don't need to in the future.\n */\n\n/**\n * A set of cached data about the top block of a script.\n * @param {Blocks} container - Container holding the block and related data\n * @param {string} blockId - Id for whose block data is cached in this instance\n */\nclass RuntimeScriptCache {\n constructor (container, blockId) {\n /**\n * Container with block data for blockId.\n * @type {Blocks}\n */\n this.container = container;\n\n /**\n * ID for block this instance caches.\n * @type {string}\n */\n this.blockId = blockId;\n\n const block = container.getBlock(blockId);\n const fields = container.getFields(block);\n\n /**\n * Formatted fields or fields of input blocks ready for comparison in\n * runtime.\n *\n * This is a clone of parts of the targeted blocks. Changes to these\n * clones are limited to copies under RuntimeScriptCache and will not\n * appear in the original blocks in their container. This copy is\n * modified changing the case of strings to uppercase. These uppercase\n * values will be compared later by the VM.\n * @type {object}\n */\n this.fieldsOfInputs = Object.assign({}, fields);\n if (Object.keys(fields).length === 0) {\n const inputs = container.getInputs(block);\n for (const input in inputs) {\n if (!inputs.hasOwnProperty(input)) continue;\n const id = inputs[input].block;\n const inputBlock = container.getBlock(id);\n const inputFields = container.getFields(inputBlock);\n Object.assign(this.fieldsOfInputs, inputFields);\n }\n }\n for (const key in this.fieldsOfInputs) {\n const field = this.fieldsOfInputs[key] = Object.assign({}, this.fieldsOfInputs[key]);\n if (field.value.toUpperCase) {\n field.value = field.value.toUpperCase();\n }\n }\n }\n}\n\n/**\n * Get an array of scripts from a block container prefiltered to match opcode.\n * @param {Blocks} container - Container of blocks\n * @param {string} opcode - Opcode to filter top blocks by\n */\nexports.getScripts = function () {\n throw new Error('blocks.js has not initialized BlocksRuntimeCache');\n};\n\n/**\n * Exposed RuntimeScriptCache class used by integration in blocks.js.\n * @private\n */\nexports._RuntimeScriptCache = RuntimeScriptCache;\n\nrequire('./blocks');\n","const adapter = require('./adapter');\nconst mutationAdapter = require('./mutation-adapter');\nconst xmlEscape = require('../util/xml-escape');\nconst MonitorRecord = require('./monitor-record');\nconst Clone = require('../util/clone');\nconst {Map} = require('immutable');\nconst BlocksExecuteCache = require('./blocks-execute-cache');\nconst BlocksRuntimeCache = require('./blocks-runtime-cache');\nconst log = require('../util/log');\nconst Variable = require('./variable');\nconst getMonitorIdForBlockWithArgs = require('../util/get-monitor-id');\n\n/**\n * @fileoverview\n * Store and mutate the VM block representation,\n * and handle updates from Scratch Blocks events.\n */\n\n/**\n * Create a block container.\n * @param {Runtime} runtime The runtime this block container operates within\n * @param {boolean} optNoGlow Optional flag to indicate that blocks in this container\n * should not request glows. This does not affect glows when clicking on a block to execute it.\n */\nclass Blocks {\n constructor (runtime, optNoGlow) {\n this.runtime = runtime;\n\n /**\n * All blocks in the workspace.\n * Keys are block IDs, values are metadata about the block.\n * @type {Object.}\n */\n this._blocks = {};\n\n /**\n * All top-level scripts in the workspace.\n * A list of block IDs that represent scripts (i.e., first block in script).\n * @type {Array.}\n */\n this._scripts = [];\n\n /**\n * Runtime Cache\n * @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}}\n * @private\n */\n Object.defineProperty(this, '_cache', {writable: true, enumerable: false});\n this._cache = {\n /**\n * Cache block inputs by block id\n * @type {object.>}\n */\n inputs: {},\n /**\n * Cache procedure Param Names by block id\n * @type {object.>}\n */\n procedureParamNames: {},\n /**\n * Cache procedure definitions by block id\n * @type {object.}\n */\n procedureDefinitions: {},\n\n /**\n * A cache for execute to use and store on. Only available to\n * execute.\n * @type {object.}\n */\n _executeCached: {},\n\n /**\n * A cache of block IDs and targets to start threads on as they are\n * actively monitored.\n * @type {Array<{blockId: string, target: Target}>}\n */\n _monitored: null,\n\n /**\n * A cache of hat opcodes to collection of theads to execute.\n * @type {object.}\n */\n scripts: {}\n };\n\n /**\n * Flag which indicates that blocks in this container should not glow.\n * Blocks will still glow when clicked on, but this flag is used to control\n * whether the blocks in this container can request a glow as part of\n * a running stack. E.g. the flyout block container and the monitor block container\n * should not be able to request a glow, but blocks containers belonging to\n * sprites should.\n * @type {boolean}\n */\n this.forceNoGlow = optNoGlow || false;\n }\n\n /**\n * Blockly inputs that represent statements/branch.\n * are prefixed with this string.\n * @const{string}\n */\n static get BRANCH_INPUT_PREFIX () {\n return 'SUBSTACK';\n }\n\n /**\n * Provide an object with metadata for the requested block ID.\n * @param {!string} blockId ID of block we have stored.\n * @return {?object} Metadata about the block, if it exists.\n */\n getBlock (blockId) {\n return this._blocks[blockId];\n }\n\n /**\n * Get all known top-level blocks that start scripts.\n * @return {Array.} List of block IDs.\n */\n getScripts () {\n return this._scripts;\n }\n\n /**\n * Get the next block for a particular block\n * @param {?string} id ID of block to get the next block for\n * @return {?string} ID of next block in the sequence\n */\n getNextBlock (id) {\n const block = this._blocks[id];\n return (typeof block === 'undefined') ? null : block.next;\n }\n\n /**\n * Get the branch for a particular C-shaped block.\n * @param {?string} id ID for block to get the branch for.\n * @param {?number} branchNum Which branch to select (e.g. for if-else).\n * @return {?string} ID of block in the branch.\n */\n getBranch (id, branchNum) {\n const block = this._blocks[id];\n if (typeof block === 'undefined') return null;\n if (!branchNum) branchNum = 1;\n\n let inputName = Blocks.BRANCH_INPUT_PREFIX;\n if (branchNum > 1) {\n inputName += branchNum;\n }\n\n // Empty C-block?\n const input = block.inputs[inputName];\n return (typeof input === 'undefined') ? null : input.block;\n }\n\n /**\n * Get the opcode for a particular block\n * @param {?object} block The block to query\n * @return {?string} the opcode corresponding to that block\n */\n getOpcode (block) {\n return (typeof block === 'undefined') ? null : block.opcode;\n }\n\n /**\n * Get all fields and their values for a block.\n * @param {?object} block The block to query.\n * @return {?object} All fields and their values.\n */\n getFields (block) {\n return (typeof block === 'undefined') ? null : block.fields;\n }\n\n /**\n * Get all non-branch inputs for a block.\n * @param {?object} block the block to query.\n * @return {?Array.} All non-branch inputs and their associated blocks.\n */\n getInputs (block) {\n if (typeof block === 'undefined') return null;\n let inputs = this._cache.inputs[block.id];\n if (typeof inputs !== 'undefined') {\n return inputs;\n }\n\n inputs = {};\n for (const input in block.inputs) {\n // Ignore blocks prefixed with branch prefix.\n if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !==\n Blocks.BRANCH_INPUT_PREFIX) {\n inputs[input] = block.inputs[input];\n }\n }\n\n this._cache.inputs[block.id] = inputs;\n return inputs;\n }\n\n /**\n * Get mutation data for a block.\n * @param {?object} block The block to query.\n * @return {?object} Mutation for the block.\n */\n getMutation (block) {\n return (typeof block === 'undefined') ? null : block.mutation;\n }\n\n /**\n * Get the top-level script for a given block.\n * @param {?string} id ID of block to query.\n * @return {?string} ID of top-level script block.\n */\n getTopLevelScript (id) {\n let block = this._blocks[id];\n if (typeof block === 'undefined') return null;\n while (block.parent !== null) {\n block = this._blocks[block.parent];\n }\n return block.id;\n }\n\n /**\n * Get the procedure definition for a given name.\n * @param {?string} name Name of procedure to query.\n * @return {?string} ID of procedure definition.\n */\n getProcedureDefinition (name) {\n const blockID = this._cache.procedureDefinitions[name];\n if (typeof blockID !== 'undefined') {\n return blockID;\n }\n\n for (const id in this._blocks) {\n if (!this._blocks.hasOwnProperty(id)) continue;\n const block = this._blocks[id];\n if (block.opcode === 'procedures_definition') {\n const internal = this._getCustomBlockInternal(block);\n if (internal && internal.mutation.proccode === name) {\n this._cache.procedureDefinitions[name] = id; // The outer define block id\n return id;\n }\n }\n }\n\n this._cache.procedureDefinitions[name] = null;\n return null;\n }\n\n /**\n * Get names and ids of parameters for the given procedure.\n * @param {?string} name Name of procedure to query.\n * @return {?Array.} List of param names for a procedure.\n */\n getProcedureParamNamesAndIds (name) {\n return this.getProcedureParamNamesIdsAndDefaults(name).slice(0, 2);\n }\n\n /**\n * Get names, ids, and defaults of parameters for the given procedure.\n * @param {?string} name Name of procedure to query.\n * @return {?Array.} List of param names for a procedure.\n */\n getProcedureParamNamesIdsAndDefaults (name) {\n const cachedNames = this._cache.procedureParamNames[name];\n if (typeof cachedNames !== 'undefined') {\n return cachedNames;\n }\n\n for (const id in this._blocks) {\n if (!this._blocks.hasOwnProperty(id)) continue;\n const block = this._blocks[id];\n if (block.opcode === 'procedures_prototype' &&\n block.mutation.proccode === name) {\n const names = JSON.parse(block.mutation.argumentnames);\n const ids = JSON.parse(block.mutation.argumentids);\n const defaults = JSON.parse(block.mutation.argumentdefaults);\n\n this._cache.procedureParamNames[name] = [names, ids, defaults];\n return this._cache.procedureParamNames[name];\n }\n }\n\n this._cache.procedureParamNames[name] = null;\n return null;\n }\n\n duplicate () {\n const newBlocks = new Blocks(this.runtime, this.forceNoGlow);\n newBlocks._blocks = Clone.simple(this._blocks);\n newBlocks._scripts = Clone.simple(this._scripts);\n return newBlocks;\n }\n // ---------------------------------------------------------------------\n\n /**\n * Create event listener for blocks, variables, and comments. Handles validation and\n * serves as a generic adapter between the blocks, variables, and the\n * runtime interface.\n * @param {object} e Blockly \"block\" or \"variable\" event\n */\n blocklyListen (e) {\n // Validate event\n if (typeof e !== 'object') return;\n if (typeof e.blockId !== 'string' && typeof e.varId !== 'string' &&\n typeof e.commentId !== 'string') {\n return;\n }\n const stage = this.runtime.getTargetForStage();\n const editingTarget = this.runtime.getEditingTarget();\n\n // UI event: clicked scripts toggle in the runtime.\n if (e.element === 'stackclick') {\n this.runtime.toggleScript(e.blockId, {stackClick: true});\n return;\n }\n\n // Block create/update/destroy\n switch (e.type) {\n case 'create': {\n const newBlocks = adapter(e);\n // A create event can create many blocks. Add them all.\n for (let i = 0; i < newBlocks.length; i++) {\n this.createBlock(newBlocks[i]);\n }\n break;\n }\n case 'change':\n this.changeBlock({\n id: e.blockId,\n element: e.element,\n name: e.name,\n value: e.newValue\n });\n break;\n case 'move':\n this.moveBlock({\n id: e.blockId,\n oldParent: e.oldParentId,\n oldInput: e.oldInputName,\n newParent: e.newParentId,\n newInput: e.newInputName,\n newCoordinate: e.newCoordinate\n });\n break;\n case 'dragOutside':\n this.runtime.emitBlockDragUpdate(e.isOutside);\n break;\n case 'endDrag':\n this.runtime.emitBlockDragUpdate(false /* areBlocksOverGui */);\n\n // Drag blocks onto another sprite\n if (e.isOutside) {\n const newBlocks = adapter(e);\n this.runtime.emitBlockEndDrag(newBlocks, e.blockId);\n }\n break;\n case 'delete':\n // Don't accept delete events for missing blocks,\n // or shadow blocks being obscured.\n if (!this._blocks.hasOwnProperty(e.blockId) ||\n this._blocks[e.blockId].shadow) {\n return;\n }\n // Inform any runtime to forget about glows on this script.\n if (this._blocks[e.blockId].topLevel) {\n this.runtime.quietGlow(e.blockId);\n }\n this.deleteBlock(e.blockId);\n break;\n case 'var_create':\n // Check if the variable being created is global or local\n // If local, create a local var on the current editing target, as long\n // as there are no conflicts, and the current target is actually a sprite\n // If global or if the editing target is not present or we somehow got\n // into a state where a local var was requested for the stage,\n // create a stage (global) var after checking for name conflicts\n // on all the sprites.\n if (e.isLocal && editingTarget && !editingTarget.isStage && !e.isCloud) {\n if (!editingTarget.lookupVariableById(e.varId)) {\n editingTarget.createVariable(e.varId, e.varName, e.varType);\n this.emitProjectChanged();\n }\n } else {\n if (stage.lookupVariableById(e.varId)) {\n // Do not re-create a variable if it already exists\n return;\n }\n // Check for name conflicts in all of the targets\n const allTargets = this.runtime.targets.filter(t => t.isOriginal);\n for (const target of allTargets) {\n if (target.lookupVariableByNameAndType(e.varName, e.varType, true)) {\n return;\n }\n }\n stage.createVariable(e.varId, e.varName, e.varType, e.isCloud);\n this.emitProjectChanged();\n }\n break;\n case 'var_rename':\n if (editingTarget && editingTarget.variables.hasOwnProperty(e.varId)) {\n // This is a local variable, rename on the current target\n editingTarget.renameVariable(e.varId, e.newName);\n // Update all the blocks on the current target that use\n // this variable\n editingTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName);\n } else {\n // This is a global variable\n stage.renameVariable(e.varId, e.newName);\n // Update all blocks on all targets that use the renamed variable\n const targets = this.runtime.targets;\n for (let i = 0; i < targets.length; i++) {\n const currTarget = targets[i];\n currTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName);\n }\n }\n this.emitProjectChanged();\n break;\n case 'var_delete': {\n const target = (editingTarget && editingTarget.variables.hasOwnProperty(e.varId)) ?\n editingTarget : stage;\n target.deleteVariable(e.varId);\n this.emitProjectChanged();\n break;\n }\n case 'comment_create':\n if (this.runtime.getEditingTarget()) {\n const currTarget = this.runtime.getEditingTarget();\n currTarget.createComment(e.commentId, e.blockId, e.text,\n e.xy.x, e.xy.y, e.width, e.height, e.minimized);\n\n if (currTarget.comments[e.commentId].x === null &&\n currTarget.comments[e.commentId].y === null) {\n // Block comments imported from 2.0 projects are imported with their\n // x and y coordinates set to null so that scratch-blocks can\n // auto-position them. If we are receiving a create event for these\n // comments, then the auto positioning should have taken place.\n // Update the x and y position of these comments to match the\n // one from the event.\n currTarget.comments[e.commentId].x = e.xy.x;\n currTarget.comments[e.commentId].y = e.xy.y;\n }\n }\n this.emitProjectChanged();\n break;\n case 'comment_change':\n if (this.runtime.getEditingTarget()) {\n const currTarget = this.runtime.getEditingTarget();\n if (!currTarget.comments.hasOwnProperty(e.commentId)) {\n log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`);\n return;\n }\n const comment = currTarget.comments[e.commentId];\n const change = e.newContents_;\n if (change.hasOwnProperty('minimized')) {\n comment.minimized = change.minimized;\n }\n if (change.hasOwnProperty('width') && change.hasOwnProperty('height')){\n comment.width = change.width;\n comment.height = change.height;\n }\n if (change.hasOwnProperty('text')) {\n comment.text = change.text;\n }\n this.emitProjectChanged();\n }\n break;\n case 'comment_move':\n if (this.runtime.getEditingTarget()) {\n const currTarget = this.runtime.getEditingTarget();\n if (currTarget && !currTarget.comments.hasOwnProperty(e.commentId)) {\n log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`);\n return;\n }\n const comment = currTarget.comments[e.commentId];\n const newCoord = e.newCoordinate_;\n comment.x = newCoord.x;\n comment.y = newCoord.y;\n\n this.emitProjectChanged();\n }\n break;\n case 'comment_delete':\n if (this.runtime.getEditingTarget()) {\n const currTarget = this.runtime.getEditingTarget();\n if (!currTarget.comments.hasOwnProperty(e.commentId)) {\n // If we're in this state, we have probably received\n // a delete event from a workspace that we switched from\n // (e.g. a delete event for a comment on sprite a's workspace\n // when switching from sprite a to sprite b)\n return;\n }\n delete currTarget.comments[e.commentId];\n if (e.blockId) {\n const block = currTarget.blocks.getBlock(e.blockId);\n if (!block) {\n log.warn(`Could not find block referenced by comment with id: ${e.commentId}`);\n return;\n }\n delete block.comment;\n }\n\n this.emitProjectChanged();\n }\n break;\n }\n }\n\n // ---------------------------------------------------------------------\n\n /**\n * Reset all runtime caches.\n */\n resetCache () {\n this._cache.inputs = {};\n this._cache.procedureParamNames = {};\n this._cache.procedureDefinitions = {};\n this._cache._executeCached = {};\n this._cache._monitored = null;\n this._cache.scripts = {};\n }\n\n /**\n * Emit a project changed event if this is a block container\n * that can affect the project state.\n */\n emitProjectChanged () {\n if (!this.forceNoGlow) {\n this.runtime.emitProjectChanged();\n }\n }\n\n /**\n * Block management: create blocks and scripts from a `create` event\n * @param {!object} block Blockly create event to be processed\n */\n createBlock (block) {\n // Does the block already exist?\n // Could happen, e.g., for an unobscured shadow.\n if (this._blocks.hasOwnProperty(block.id)) {\n return;\n }\n // Create new block.\n this._blocks[block.id] = block;\n // Push block id to scripts array.\n // Blocks are added as a top-level stack if they are marked as a top-block\n // (if they were top-level XML in the event).\n if (block.topLevel) {\n this._addScript(block.id);\n }\n\n this.resetCache();\n\n // A new block was actually added to the block container,\n // emit a project changed event\n this.emitProjectChanged();\n }\n\n /**\n * Block management: change block field values\n * @param {!object} args Blockly change event to be processed\n */\n changeBlock (args) {\n // Validate\n if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) return;\n let block = this._blocks[args.id];\n if (typeof block === 'undefined') return;\n switch (args.element) {\n case 'field':\n // TODO when the field of a monitored block changes,\n // update the checkbox in the flyout based on whether\n // a monitor for that current combination of selected parameters exists\n // e.g.\n // 1. check (current [v year])\n // 2. switch dropdown in flyout block to (current [v minute])\n // 3. the checkbox should become unchecked if we're not already\n // monitoring current minute\n\n\n // Update block value\n if (!block.fields[args.name]) return;\n if (args.name === 'VARIABLE' || args.name === 'LIST' ||\n args.name === 'BROADCAST_OPTION') {\n // Get variable name using the id in args.value.\n const variable = this.runtime.getEditingTarget().lookupVariableById(args.value);\n if (variable) {\n block.fields[args.name].value = variable.name;\n block.fields[args.name].id = args.value;\n }\n } else {\n // Changing the value in a dropdown\n block.fields[args.name].value = args.value;\n\n // The selected item in the sensing of block menu needs to change based on the\n // selected target. Set it to the first item in the menu list.\n // TODO: (#1787)\n if (block.opcode === 'sensing_of_object_menu') {\n if (block.fields.OBJECT.value === '_stage_') {\n this._blocks[block.parent].fields.PROPERTY.value = 'backdrop #';\n } else {\n this._blocks[block.parent].fields.PROPERTY.value = 'x position';\n }\n this.runtime.requestBlocksUpdate();\n }\n\n const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block;\n if (flyoutBlock.isMonitored) {\n this.runtime.requestUpdateMonitor(Map({\n id: flyoutBlock.id,\n params: this._getBlockParams(flyoutBlock)\n }));\n }\n }\n break;\n case 'mutation':\n block.mutation = mutationAdapter(args.value);\n break;\n case 'checkbox': {\n // A checkbox usually has a one to one correspondence with the monitor\n // block but in the case of monitored reporters that have arguments,\n // map the old id to a new id, creating a new monitor block if necessary\n if (block.fields && Object.keys(block.fields).length > 0 &&\n block.opcode !== 'data_variable' && block.opcode !== 'data_listcontents') {\n\n // This block has an argument which needs to get separated out into\n // multiple monitor blocks with ids based on the selected argument\n const newId = getMonitorIdForBlockWithArgs(block.id, block.fields);\n // Note: we're not just constantly creating a longer and longer id everytime we check\n // the checkbox because we're using the id of the block in the flyout as the base\n\n // check if a block with the new id already exists, otherwise create\n let newBlock = this.runtime.monitorBlocks.getBlock(newId);\n if (!newBlock) {\n newBlock = JSON.parse(JSON.stringify(block));\n newBlock.id = newId;\n this.runtime.monitorBlocks.createBlock(newBlock);\n }\n\n block = newBlock; // Carry on through the rest of this code with newBlock\n }\n\n const wasMonitored = block.isMonitored;\n block.isMonitored = args.value;\n\n // Variable blocks may be sprite specific depending on the owner of the variable\n let isSpriteLocalVariable = false;\n if (block.opcode === 'data_variable') {\n isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.VARIABLE.id]);\n } else if (block.opcode === 'data_listcontents') {\n isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.LIST.id]);\n }\n\n const isSpriteSpecific = isSpriteLocalVariable ||\n (this.runtime.monitorBlockInfo.hasOwnProperty(block.opcode) &&\n this.runtime.monitorBlockInfo[block.opcode].isSpriteSpecific);\n if (isSpriteSpecific) {\n // If creating a new sprite specific monitor, the only possible target is\n // the current editing one b/c you cannot dynamically create monitors.\n // Also, do not change the targetId if it has already been assigned\n block.targetId = block.targetId || this.runtime.getEditingTarget().id;\n } else {\n block.targetId = null;\n }\n\n if (wasMonitored && !block.isMonitored) {\n this.runtime.requestHideMonitor(block.id);\n } else if (!wasMonitored && block.isMonitored) {\n // Tries to show the monitor for specified block. If it doesn't exist, add the monitor.\n if (!this.runtime.requestShowMonitor(block.id)) {\n this.runtime.requestAddMonitor(MonitorRecord({\n id: block.id,\n targetId: block.targetId,\n spriteName: block.targetId ? this.runtime.getTargetById(block.targetId).getName() : null,\n opcode: block.opcode,\n params: this._getBlockParams(block),\n // @todo(vm#565) for numerical values with decimals, some countries use comma\n value: '',\n mode: block.opcode === 'data_listcontents' ? 'list' : 'default'\n }));\n }\n }\n break;\n }\n }\n\n this.emitProjectChanged();\n\n this.resetCache();\n }\n\n /**\n * Block management: move blocks from parent to parent\n * @param {!object} e Blockly move event to be processed\n */\n moveBlock (e) {\n if (!this._blocks.hasOwnProperty(e.id)) {\n return;\n }\n\n const block = this._blocks[e.id];\n // Track whether a change actually occurred\n // ignoring changes like routine re-positioning\n // of a block when loading a workspace\n let didChange = false;\n\n // Move coordinate changes.\n if (e.newCoordinate) {\n\n didChange = (block.x !== e.newCoordinate.x) || (block.y !== e.newCoordinate.y);\n\n block.x = e.newCoordinate.x;\n block.y = e.newCoordinate.y;\n }\n\n // Remove from any old parent.\n if (typeof e.oldParent !== 'undefined') {\n const oldParent = this._blocks[e.oldParent];\n if (typeof e.oldInput !== 'undefined' &&\n oldParent.inputs[e.oldInput].block === e.id) {\n // This block was connected to the old parent's input.\n oldParent.inputs[e.oldInput].block = null;\n } else if (oldParent.next === e.id) {\n // This block was connected to the old parent's next connection.\n oldParent.next = null;\n }\n this._blocks[e.id].parent = null;\n didChange = true;\n }\n\n // Is this block a top-level block?\n if (typeof e.newParent === 'undefined') {\n this._addScript(e.id);\n } else {\n // Remove script, if one exists.\n this._deleteScript(e.id);\n // Otherwise, try to connect it in its new place.\n if (typeof e.newInput === 'undefined') {\n // Moved to the new parent's next connection.\n this._blocks[e.newParent].next = e.id;\n } else {\n // Moved to the new parent's input.\n // Don't obscure the shadow block.\n let oldShadow = null;\n if (this._blocks[e.newParent].inputs.hasOwnProperty(e.newInput)) {\n oldShadow = this._blocks[e.newParent].inputs[e.newInput].shadow;\n }\n\n // If the block being attached is itself a shadow, make sure to set\n // both block and shadow to that blocks ID. This happens when adding\n // inputs to a custom procedure.\n if (this._blocks[e.id].shadow) oldShadow = e.id;\n\n this._blocks[e.newParent].inputs[e.newInput] = {\n name: e.newInput,\n block: e.id,\n shadow: oldShadow\n };\n }\n this._blocks[e.id].parent = e.newParent;\n didChange = true;\n }\n this.resetCache();\n\n if (didChange) this.emitProjectChanged();\n }\n\n\n /**\n * Block management: run all blocks.\n * @param {!object} runtime Runtime to run all blocks in.\n */\n runAllMonitored (runtime) {\n if (this._cache._monitored === null) {\n this._cache._monitored = Object.keys(this._blocks)\n .filter(blockId => this.getBlock(blockId).isMonitored)\n .map(blockId => {\n const targetId = this.getBlock(blockId).targetId;\n return {\n blockId,\n target: targetId ? runtime.getTargetById(targetId) : null\n };\n });\n }\n\n const monitored = this._cache._monitored;\n for (let i = 0; i < monitored.length; i++) {\n const {blockId, target} = monitored[i];\n runtime.addMonitorScript(blockId, target);\n }\n }\n\n /**\n * Block management: delete blocks and their associated scripts. Does nothing if a block\n * with the given ID does not exist.\n * @param {!string} blockId Id of block to delete\n */\n deleteBlock (blockId) {\n // @todo In runtime, stop threads running on this script.\n\n // Get block\n const block = this._blocks[blockId];\n if (!block) {\n // No block with the given ID exists\n return;\n }\n\n // Delete children\n if (block.next !== null) {\n this.deleteBlock(block.next);\n }\n\n // Delete inputs (including branches)\n for (const input in block.inputs) {\n // If it's null, the block in this input moved away.\n if (block.inputs[input].block !== null) {\n this.deleteBlock(block.inputs[input].block);\n }\n // Delete obscured shadow blocks.\n if (block.inputs[input].shadow !== null &&\n block.inputs[input].shadow !== block.inputs[input].block) {\n this.deleteBlock(block.inputs[input].shadow);\n }\n }\n\n // Delete any script starting with this block.\n this._deleteScript(blockId);\n\n // Delete block itself.\n delete this._blocks[blockId];\n\n this.resetCache();\n this.emitProjectChanged();\n }\n\n /**\n * Returns a map of all references to variables or lists from blocks\n * in this block container.\n * @param {Array} optBlocks Optional list of blocks to constrain the search to.\n * This is useful for getting variable/list references for a stack of blocks instead\n * of all blocks on the workspace\n * @param {?boolean} optIncludeBroadcast Optional whether to include broadcast fields.\n * @return {object} A map of variable ID to a list of all variable references\n * for that ID. A variable reference contains the field referencing that variable\n * and also the type of the variable being referenced.\n */\n getAllVariableAndListReferences (optBlocks, optIncludeBroadcast) {\n const blocks = optBlocks ? optBlocks : this._blocks;\n const allReferences = Object.create(null);\n for (const blockId in blocks) {\n let varOrListField = null;\n let varType = null;\n if (blocks[blockId].fields.VARIABLE) {\n varOrListField = blocks[blockId].fields.VARIABLE;\n varType = Variable.SCALAR_TYPE;\n } else if (blocks[blockId].fields.LIST) {\n varOrListField = blocks[blockId].fields.LIST;\n varType = Variable.LIST_TYPE;\n } else if (optIncludeBroadcast && blocks[blockId].fields.BROADCAST_OPTION) {\n varOrListField = blocks[blockId].fields.BROADCAST_OPTION;\n varType = Variable.BROADCAST_MESSAGE_TYPE;\n }\n if (varOrListField) {\n const currVarId = varOrListField.id;\n if (allReferences[currVarId]) {\n allReferences[currVarId].push({\n referencingField: varOrListField,\n type: varType\n });\n } else {\n allReferences[currVarId] = [{\n referencingField: varOrListField,\n type: varType\n }];\n }\n }\n }\n return allReferences;\n }\n\n /**\n * Keep blocks up to date after a variable gets renamed.\n * @param {string} varId The id of the variable that was renamed\n * @param {string} newName The new name of the variable that was renamed\n */\n updateBlocksAfterVarRename (varId, newName) {\n const blocks = this._blocks;\n for (const blockId in blocks) {\n let varOrListField = null;\n if (blocks[blockId].fields.VARIABLE) {\n varOrListField = blocks[blockId].fields.VARIABLE;\n } else if (blocks[blockId].fields.LIST) {\n varOrListField = blocks[blockId].fields.LIST;\n }\n if (varOrListField) {\n const currFieldId = varOrListField.id;\n if (varId === currFieldId) {\n varOrListField.value = newName;\n }\n }\n }\n }\n\n /**\n * Keep blocks up to date after they are shared between targets.\n * @param {boolean} isStage If the new target is a stage.\n */\n updateTargetSpecificBlocks (isStage) {\n const blocks = this._blocks;\n for (const blockId in blocks) {\n if (isStage && blocks[blockId].opcode === 'event_whenthisspriteclicked') {\n blocks[blockId].opcode = 'event_whenstageclicked';\n } else if (!isStage && blocks[blockId].opcode === 'event_whenstageclicked') {\n blocks[blockId].opcode = 'event_whenthisspriteclicked';\n }\n }\n }\n\n /**\n * Update blocks after a sound, costume, or backdrop gets renamed.\n * Any block referring to the old name of the asset should get updated\n * to refer to the new name.\n * @param {string} oldName The old name of the asset that was renamed.\n * @param {string} newName The new name of the asset that was renamed.\n * @param {string} assetType String representation of the kind of asset\n * that was renamed. This can be one of 'sprite','costume', 'sound', or\n * 'backdrop'.\n */\n updateAssetName (oldName, newName, assetType) {\n let getAssetField;\n if (assetType === 'costume') {\n getAssetField = this._getCostumeField.bind(this);\n } else if (assetType === 'sound') {\n getAssetField = this._getSoundField.bind(this);\n } else if (assetType === 'backdrop') {\n getAssetField = this._getBackdropField.bind(this);\n } else if (assetType === 'sprite') {\n getAssetField = this._getSpriteField.bind(this);\n } else {\n return;\n }\n const blocks = this._blocks;\n for (const blockId in blocks) {\n const assetField = getAssetField(blockId);\n if (assetField && assetField.value === oldName) {\n assetField.value = newName;\n }\n }\n }\n\n /**\n * Update sensing_of blocks after a variable gets renamed.\n * @param {string} oldName The old name of the variable that was renamed.\n * @param {string} newName The new name of the variable that was renamed.\n * @param {string} targetName The name of the target the variable belongs to.\n * @return {boolean} Returns true if any of the blocks were updated.\n */\n updateSensingOfReference (oldName, newName, targetName) {\n const blocks = this._blocks;\n let blockUpdated = false;\n for (const blockId in blocks) {\n const block = blocks[blockId];\n if (block.opcode === 'sensing_of' &&\n block.fields.PROPERTY.value === oldName &&\n // If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored.\n block.inputs.OBJECT.block === block.inputs.OBJECT.shadow) {\n const inputBlock = this.getBlock(block.inputs.OBJECT.block);\n if (inputBlock.fields.OBJECT.value === targetName) {\n block.fields.PROPERTY.value = newName;\n blockUpdated = true;\n }\n }\n }\n if (blockUpdated) this.resetCache();\n return blockUpdated;\n }\n\n /**\n * Helper function to retrieve a costume menu field from a block given its id.\n * @param {string} blockId A unique identifier for a block\n * @return {?object} The costume menu field of the block with the given block id.\n * Null if either a block with the given id doesn't exist or if a costume menu field\n * does not exist on the block with the given id.\n */\n _getCostumeField (blockId) {\n const block = this.getBlock(blockId);\n if (block && block.fields.hasOwnProperty('COSTUME')) {\n return block.fields.COSTUME;\n }\n return null;\n }\n\n /**\n * Helper function to retrieve a sound menu field from a block given its id.\n * @param {string} blockId A unique identifier for a block\n * @return {?object} The sound menu field of the block with the given block id.\n * Null, if either a block with the given id doesn't exist or if a sound menu field\n * does not exist on the block with the given id.\n */\n _getSoundField (blockId) {\n const block = this.getBlock(blockId);\n if (block && block.fields.hasOwnProperty('SOUND_MENU')) {\n return block.fields.SOUND_MENU;\n }\n return null;\n }\n\n /**\n * Helper function to retrieve a backdrop menu field from a block given its id.\n * @param {string} blockId A unique identifier for a block\n * @return {?object} The backdrop menu field of the block with the given block id.\n * Null, if either a block with the given id doesn't exist or if a backdrop menu field\n * does not exist on the block with the given id.\n */\n _getBackdropField (blockId) {\n const block = this.getBlock(blockId);\n if (block && block.fields.hasOwnProperty('BACKDROP')) {\n return block.fields.BACKDROP;\n }\n return null;\n }\n\n /**\n * Helper function to retrieve a sprite menu field from a block given its id.\n * @param {string} blockId A unique identifier for a block\n * @return {?object} The sprite menu field of the block with the given block id.\n * Null, if either a block with the given id doesn't exist or if a sprite menu field\n * does not exist on the block with the given id.\n */\n _getSpriteField (blockId) {\n const block = this.getBlock(blockId);\n if (!block) {\n return null;\n }\n const spriteMenuNames = ['TOWARDS', 'TO', 'OBJECT', 'VIDEOONMENU2',\n 'DISTANCETOMENU', 'TOUCHINGOBJECTMENU', 'CLONE_OPTION'];\n for (let i = 0; i < spriteMenuNames.length; i++) {\n const menuName = spriteMenuNames[i];\n if (block.fields.hasOwnProperty(menuName)) {\n return block.fields[menuName];\n }\n }\n return null;\n }\n\n // ---------------------------------------------------------------------\n\n /**\n * Encode all of `this._blocks` as an XML string usable\n * by a Blockly/scratch-blocks workspace.\n * @param {object} comments Map of comments referenced by id\n * @return {string} String of XML representing this object's blocks.\n */\n toXML (comments) {\n return this._scripts.map(script => this.blockToXML(script, comments)).join();\n }\n\n /**\n * Recursively encode an individual block and its children\n * into a Blockly/scratch-blocks XML string.\n * @param {!string} blockId ID of block to encode.\n * @param {object} comments Map of comments referenced by id\n * @return {string} String of XML representing this block and any children.\n */\n blockToXML (blockId, comments) {\n const block = this._blocks[blockId];\n // block should exist, but currently some blocks' next property point\n // to a blockId for non-existent blocks. Until we track down that behavior,\n // this early exit allows the project to load.\n if (!block) return;\n // Encode properties of this block.\n const tagName = (block.shadow) ? 'shadow' : 'block';\n let xmlString =\n `<${tagName}\n id=\"${block.id}\"\n type=\"${block.opcode}\"\n ${block.topLevel ? `x=\"${block.x}\" y=\"${block.y}\"` : ''}\n >`;\n const commentId = block.comment;\n if (commentId) {\n if (comments) {\n if (comments.hasOwnProperty(commentId)) {\n xmlString += comments[commentId].toXML();\n } else {\n log.warn(`Could not find comment with id: ${commentId} in provided comment descriptions.`);\n }\n } else {\n log.warn(`Cannot serialize comment with id: ${commentId}; no comment descriptions provided.`);\n }\n }\n // Add any mutation. Must come before inputs.\n if (block.mutation) {\n xmlString += this.mutationToXML(block.mutation);\n }\n // Add any inputs on this block.\n for (const input in block.inputs) {\n if (!block.inputs.hasOwnProperty(input)) continue;\n const blockInput = block.inputs[input];\n // Only encode a value tag if the value input is occupied.\n if (blockInput.block || blockInput.shadow) {\n xmlString += ``;\n if (blockInput.block) {\n xmlString += this.blockToXML(blockInput.block, comments);\n }\n if (blockInput.shadow && blockInput.shadow !== blockInput.block) {\n // Obscured shadow.\n xmlString += this.blockToXML(blockInput.shadow, comments);\n }\n xmlString += '';\n }\n }\n // Add any fields on this block.\n for (const field in block.fields) {\n if (!block.fields.hasOwnProperty(field)) continue;\n const blockField = block.fields[field];\n xmlString += `${value}`;\n }\n // Add blocks connected to the next connection.\n if (block.next) {\n xmlString += `${this.blockToXML(block.next, comments)}`;\n }\n xmlString += ``;\n return xmlString;\n }\n\n /**\n * Recursively encode a mutation object to XML.\n * @param {!object} mutation Object representing a mutation.\n * @return {string} XML string representing a mutation.\n */\n mutationToXML (mutation) {\n let mutationString = `<${mutation.tagName}`;\n for (const prop in mutation) {\n if (prop === 'children' || prop === 'tagName') continue;\n let mutationValue = (typeof mutation[prop] === 'string') ?\n xmlEscape(mutation[prop]) : mutation[prop];\n\n // Handle dynamic extension blocks\n if (prop === 'blockInfo') {\n mutationValue = xmlEscape(JSON.stringify(mutation[prop]));\n }\n\n mutationString += ` ${prop}=\"${mutationValue}\"`;\n }\n mutationString += '>';\n for (let i = 0; i < mutation.children.length; i++) {\n mutationString += this.mutationToXML(mutation.children[i]);\n }\n mutationString += ``;\n return mutationString;\n }\n\n // ---------------------------------------------------------------------\n /**\n * Helper to serialize block fields and input fields for reporting new monitors\n * @param {!object} block Block to be paramified.\n * @return {!object} object of param key/values.\n */\n _getBlockParams (block) {\n const params = {};\n for (const key in block.fields) {\n params[key] = block.fields[key].value;\n }\n for (const inputKey in block.inputs) {\n const inputBlock = this._blocks[block.inputs[inputKey].block];\n for (const key in inputBlock.fields) {\n params[key] = inputBlock.fields[key].value;\n }\n }\n return params;\n }\n\n /**\n * Helper to get the corresponding internal procedure definition block\n * @param {!object} defineBlock Outer define block.\n * @return {!object} internal definition block which has the mutation.\n */\n _getCustomBlockInternal (defineBlock) {\n if (defineBlock.inputs && defineBlock.inputs.custom_block) {\n return this._blocks[defineBlock.inputs.custom_block.block];\n }\n }\n\n /**\n * Helper to add a stack to `this._scripts`.\n * @param {?string} topBlockId ID of block that starts the script.\n */\n _addScript (topBlockId) {\n const i = this._scripts.indexOf(topBlockId);\n if (i > -1) return; // Already in scripts.\n this._scripts.push(topBlockId);\n // Update `topLevel` property on the top block.\n this._blocks[topBlockId].topLevel = true;\n }\n\n /**\n * Helper to remove a script from `this._scripts`.\n * @param {?string} topBlockId ID of block that starts the script.\n */\n _deleteScript (topBlockId) {\n const i = this._scripts.indexOf(topBlockId);\n if (i > -1) this._scripts.splice(i, 1);\n // Update `topLevel` property on the top block.\n if (this._blocks[topBlockId]) this._blocks[topBlockId].topLevel = false;\n }\n}\n\n/**\n * A private method shared with execute to build an object containing the block\n * information execute needs and that is reset when other cached Blocks info is\n * reset.\n * @param {Blocks} blocks Blocks containing the expected blockId\n * @param {string} blockId blockId for the desired execute cache\n * @param {function} CacheType constructor for cached block information\n * @return {object} execute cache object\n */\nBlocksExecuteCache.getCached = function (blocks, blockId, CacheType) {\n let cached = blocks._cache._executeCached[blockId];\n if (typeof cached !== 'undefined') {\n return cached;\n }\n\n const block = blocks.getBlock(blockId);\n if (typeof block === 'undefined') return null;\n\n if (typeof CacheType === 'undefined') {\n cached = {\n id: blockId,\n opcode: blocks.getOpcode(block),\n fields: blocks.getFields(block),\n inputs: blocks.getInputs(block),\n mutation: blocks.getMutation(block)\n };\n } else {\n cached = new CacheType(blocks, {\n id: blockId,\n opcode: blocks.getOpcode(block),\n fields: blocks.getFields(block),\n inputs: blocks.getInputs(block),\n mutation: blocks.getMutation(block)\n });\n }\n\n blocks._cache._executeCached[blockId] = cached;\n return cached;\n};\n\n/**\n * Cache class constructor for runtime. Used to consider what threads should\n * start based on hat data.\n * @type {function}\n */\nconst RuntimeScriptCache = BlocksRuntimeCache._RuntimeScriptCache;\n\n/**\n * Get an array of scripts from a block container prefiltered to match opcode.\n * @param {Blocks} blocks - Container of blocks\n * @param {string} opcode - Opcode to filter top blocks by\n * @returns {Array.} - Array of RuntimeScriptCache cache\n * objects\n */\nBlocksRuntimeCache.getScripts = function (blocks, opcode) {\n let scripts = blocks._cache.scripts[opcode];\n if (!scripts) {\n scripts = blocks._cache.scripts[opcode] = [];\n\n const allScripts = blocks._scripts;\n for (let i = 0; i < allScripts.length; i++) {\n const topBlockId = allScripts[i];\n const block = blocks.getBlock(topBlockId);\n if (block.opcode === opcode) {\n scripts.push(new RuntimeScriptCache(blocks, topBlockId));\n }\n }\n }\n return scripts;\n};\n\nmodule.exports = Blocks;\n","/**\n * @fileoverview\n * Object representing a Scratch Comment (block or workspace).\n */\n\nconst uid = require('../util/uid');\nconst xmlEscape = require('../util/xml-escape');\n\nclass Comment {\n /**\n * @param {string} id Id of the comment.\n * @param {string} text Text content of the comment.\n * @param {number} x X position of the comment on the workspace.\n * @param {number} y Y position of the comment on the workspace.\n * @param {number} width The width of the comment when it is full size.\n * @param {number} height The height of the comment when it is full size.\n * @param {boolean} minimized Whether the comment is minimized.\n * @constructor\n */\n constructor (id, text, x, y, width, height, minimized) {\n this.id = id || uid();\n this.text = text;\n this.x = x;\n this.y = y;\n this.width = Math.max(Number(width), Comment.MIN_WIDTH);\n this.height = Math.max(Number(height), Comment.MIN_HEIGHT);\n this.minimized = minimized || false;\n this.blockId = null;\n }\n\n toXML () {\n return `${xmlEscape(this.text)}`;\n }\n\n // TODO choose min and defaults for width and height\n static get MIN_WIDTH () {\n return 20;\n }\n\n static get MIN_HEIGHT () {\n return 20;\n }\n\n static get DEFAULT_WIDTH () {\n return 100;\n }\n\n static get DEFAULT_HEIGHT () {\n return 100;\n }\n\n}\n\nmodule.exports = Comment;\n","const BlockUtility = require('./block-utility');\nconst BlocksExecuteCache = require('./blocks-execute-cache');\nconst log = require('../util/log');\nconst Thread = require('./thread');\nconst {Map} = require('immutable');\nconst cast = require('../util/cast');\n\n/**\n * Single BlockUtility instance reused by execute for every pritimive ran.\n * @const\n */\nconst blockUtility = new BlockUtility();\n\n/**\n * Profiler frame name for block functions.\n * @const {string}\n */\nconst blockFunctionProfilerFrame = 'blockFunction';\n\n/**\n * Profiler frame ID for 'blockFunction'.\n * @type {number}\n */\nlet blockFunctionProfilerId = -1;\n\n/**\n * Utility function to determine if a value is a Promise.\n * @param {*} value Value to check for a Promise.\n * @return {boolean} True if the value appears to be a Promise.\n */\nconst isPromise = function (value) {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof value.then === 'function'\n );\n};\n\n/**\n * Handle any reported value from the primitive, either directly returned\n * or after a promise resolves.\n * @param {*} resolvedValue Value eventually returned from the primitive.\n * @param {!Sequencer} sequencer Sequencer stepping the thread for the ran\n * primitive.\n * @param {!Thread} thread Thread containing the primitive.\n * @param {!string} currentBlockId Id of the block in its thread for value from\n * the primitive.\n * @param {!string} opcode opcode used to identify a block function primitive.\n * @param {!boolean} isHat Is the current block a hat?\n */\n// @todo move this to callback attached to the thread when we have performance\n// metrics (dd)\nconst handleReport = function (resolvedValue, sequencer, thread, blockCached, lastOperation) {\n const currentBlockId = blockCached.id;\n const opcode = blockCached.opcode;\n const isHat = blockCached._isHat;\n\n thread.pushReportedValue(resolvedValue);\n if (isHat) {\n // Hat predicate was evaluated.\n if (sequencer.runtime.getIsEdgeActivatedHat(opcode)) {\n // If this is an edge-activated hat, only proceed if the value is\n // true and used to be false, or the stack was activated explicitly\n // via stack click\n if (!thread.stackClick) {\n const hasOldEdgeValue = thread.target.hasEdgeActivatedValue(currentBlockId);\n const oldEdgeValue = thread.target.updateEdgeActivatedValue(\n currentBlockId,\n resolvedValue\n );\n\n const edgeWasActivated = hasOldEdgeValue ? (!oldEdgeValue && resolvedValue) : resolvedValue;\n if (!edgeWasActivated) {\n sequencer.retireThread(thread);\n }\n }\n } else if (!resolvedValue) {\n // Not an edge-activated hat: retire the thread\n // if predicate was false.\n sequencer.retireThread(thread);\n }\n } else {\n // In a non-hat, report the value visually if necessary if\n // at the top of the thread stack.\n if (lastOperation && typeof resolvedValue !== 'undefined' && thread.atStackTop()) {\n if (thread.stackClick) {\n sequencer.runtime.visualReport(currentBlockId, resolvedValue);\n }\n if (thread.updateMonitor) {\n const targetId = sequencer.runtime.monitorBlocks.getBlock(currentBlockId).targetId;\n if (targetId && !sequencer.runtime.getTargetById(targetId)) {\n // Target no longer exists\n return;\n }\n sequencer.runtime.requestUpdateMonitor(Map({\n id: currentBlockId,\n spriteName: targetId ? sequencer.runtime.getTargetById(targetId).getName() : null,\n value: resolvedValue\n }));\n }\n }\n // Finished any yields.\n thread.status = Thread.STATUS_RUNNING;\n }\n};\n\nconst handlePromise = (primitiveReportedValue, sequencer, thread, blockCached, lastOperation) => {\n if (thread.status === Thread.STATUS_RUNNING) {\n // Primitive returned a promise; automatically yield thread.\n thread.status = Thread.STATUS_PROMISE_WAIT;\n }\n // Promise handlers\n primitiveReportedValue.then(resolvedValue => {\n handleReport(resolvedValue, sequencer, thread, blockCached, lastOperation);\n // If it's a command block or a top level reporter in a stackClick.\n if (lastOperation) {\n let stackFrame;\n let nextBlockId;\n do {\n // In the case that the promise is the last block in the current thread stack\n // We need to pop out repeatedly until we find the next block.\n const popped = thread.popStack();\n if (popped === null) {\n return;\n }\n nextBlockId = thread.target.blocks.getNextBlock(popped);\n if (nextBlockId !== null) {\n // A next block exists so break out this loop\n break;\n }\n // Investigate the next block and if not in a loop,\n // then repeat and pop the next item off the stack frame\n stackFrame = thread.peekStackFrame();\n } while (stackFrame !== null && !stackFrame.isLoop);\n\n thread.pushStack(nextBlockId);\n }\n }, rejectionReason => {\n // Promise rejected: the primitive had some error.\n // Log it and proceed.\n log.warn('Primitive rejected promise: ', rejectionReason);\n thread.status = Thread.STATUS_RUNNING;\n thread.popStack();\n });\n};\n\n/**\n * A execute.js internal representation of a block to reduce the time spent in\n * execute as the same blocks are called the most.\n *\n * With the help of the Blocks class create a mutable copy of block\n * information. The members of BlockCached derived values of block information\n * that does not need to be reevaluated until a change in Blocks. Since Blocks\n * handles where the cache instance is stored, it drops all cache versions of a\n * block when any change happens to it. This way we can quickly execute blocks\n * and keep perform the right action according to the current block information\n * in the editor.\n *\n * @param {Blocks} blockContainer the related Blocks instance\n * @param {object} cached default set of cached values\n */\nclass BlockCached {\n constructor (blockContainer, cached) {\n /**\n * Block id in its parent set of blocks.\n * @type {string}\n */\n this.id = cached.id;\n\n /**\n * Block operation code for this block.\n * @type {string}\n */\n this.opcode = cached.opcode;\n\n /**\n * Original block object containing argument values for static fields.\n * @type {object}\n */\n this.fields = cached.fields;\n\n /**\n * Original block object containing argument values for executable inputs.\n * @type {object}\n */\n this.inputs = cached.inputs;\n\n /**\n * Procedure mutation.\n * @type {?object}\n */\n this.mutation = cached.mutation;\n\n /**\n * The profiler the block is configured with.\n * @type {?Profiler}\n */\n this._profiler = null;\n\n /**\n * Profiler information frame.\n * @type {?ProfilerFrame}\n */\n this._profilerFrame = null;\n\n /**\n * Is the opcode a hat (event responder) block.\n * @type {boolean}\n */\n this._isHat = false;\n\n /**\n * The block opcode's implementation function.\n * @type {?function}\n */\n this._blockFunction = null;\n\n /**\n * Is the block function defined for this opcode?\n * @type {boolean}\n */\n this._definedBlockFunction = false;\n\n /**\n * Is this block a block with no function but a static value to return.\n * @type {boolean}\n */\n this._isShadowBlock = false;\n\n /**\n * The static value of this block if it is a shadow block.\n * @type {?any}\n */\n this._shadowValue = null;\n\n /**\n * A copy of the block's fields that may be modified.\n * @type {object}\n */\n this._fields = Object.assign({}, this.fields);\n\n /**\n * A copy of the block's inputs that may be modified.\n * @type {object}\n */\n this._inputs = Object.assign({}, this.inputs);\n\n /**\n * An arguments object for block implementations. All executions of this\n * specific block will use this objecct.\n * @type {object}\n */\n this._argValues = {\n mutation: this.mutation\n };\n\n /**\n * The inputs key the parent refers to this BlockCached by.\n * @type {string}\n */\n this._parentKey = null;\n\n /**\n * The target object where the parent wants the resulting value stored\n * with _parentKey as the key.\n * @type {object}\n */\n this._parentValues = null;\n\n /**\n * A sequence of non-shadow operations that can must be performed. This\n * list recreates the order this block and its children are executed.\n * Since the order is always the same we can safely store that order\n * and iterate over the operations instead of dynamically walking the\n * tree every time.\n * @type {Array}\n */\n this._ops = [];\n\n const {runtime} = blockUtility.sequencer;\n\n const {opcode, fields, inputs} = this;\n\n // Assign opcode isHat and blockFunction data to avoid dynamic lookups.\n this._isHat = runtime.getIsHat(opcode);\n this._blockFunction = runtime.getOpcodeFunction(opcode);\n this._definedBlockFunction = typeof this._blockFunction !== 'undefined';\n\n // Store the current shadow value if there is a shadow value.\n const fieldKeys = Object.keys(fields);\n this._isShadowBlock = (\n !this._definedBlockFunction &&\n fieldKeys.length === 1 &&\n Object.keys(inputs).length === 0\n );\n this._shadowValue = this._isShadowBlock && fields[fieldKeys[0]].value;\n\n // Store the static fields onto _argValues.\n for (const fieldName in fields) {\n if (\n fieldName === 'VARIABLE' ||\n fieldName === 'LIST' ||\n fieldName === 'BROADCAST_OPTION'\n ) {\n this._argValues[fieldName] = {\n id: fields[fieldName].id,\n name: fields[fieldName].value\n };\n } else {\n this._argValues[fieldName] = fields[fieldName].value;\n }\n }\n\n // Remove custom_block. It is not part of block execution.\n delete this._inputs.custom_block;\n\n if ('BROADCAST_INPUT' in this._inputs) {\n // BROADCAST_INPUT is called BROADCAST_OPTION in the args and is an\n // object with an unchanging shape.\n this._argValues.BROADCAST_OPTION = {\n id: null,\n name: null\n };\n\n // We can go ahead and compute BROADCAST_INPUT if it is a shadow\n // value.\n const broadcastInput = this._inputs.BROADCAST_INPUT;\n if (broadcastInput.block === broadcastInput.shadow) {\n // Shadow dropdown menu is being used.\n // Get the appropriate information out of it.\n const shadow = blockContainer.getBlock(broadcastInput.shadow);\n const broadcastField = shadow.fields.BROADCAST_OPTION;\n this._argValues.BROADCAST_OPTION.id = broadcastField.id;\n this._argValues.BROADCAST_OPTION.name = broadcastField.value;\n\n // Evaluating BROADCAST_INPUT here we do not need to do so\n // later.\n delete this._inputs.BROADCAST_INPUT;\n }\n }\n\n // Cache all input children blocks in the operation lists. The\n // operations can later be run in the order they appear in correctly\n // executing the operations quickly in a flat loop instead of needing to\n // recursivly iterate them.\n for (const inputName in this._inputs) {\n const input = this._inputs[inputName];\n if (input.block) {\n const inputCached = BlocksExecuteCache.getCached(blockContainer, input.block, BlockCached);\n\n if (inputCached._isHat) {\n continue;\n }\n\n this._ops.push(...inputCached._ops);\n inputCached._parentKey = inputName;\n inputCached._parentValues = this._argValues;\n\n // Shadow values are static and do not change, go ahead and\n // store their value on args.\n if (inputCached._isShadowBlock) {\n this._argValues[inputName] = inputCached._shadowValue;\n }\n }\n }\n\n // The final operation is this block itself. At the top most block is a\n // command block or a block that is being run as a monitor.\n if (this._definedBlockFunction) {\n this._ops.push(this);\n }\n }\n}\n\n/**\n * Initialize a BlockCached instance so its command/hat\n * block and reporters can be profiled during execution.\n * @param {Profiler} profiler - The profiler that is currently enabled.\n * @param {BlockCached} blockCached - The blockCached instance to profile.\n */\nconst _prepareBlockProfiling = function (profiler, blockCached) {\n blockCached._profiler = profiler;\n\n if (blockFunctionProfilerId === -1) {\n blockFunctionProfilerId = profiler.idByName(blockFunctionProfilerFrame);\n }\n\n const ops = blockCached._ops;\n for (let i = 0; i < ops.length; i++) {\n ops[i]._profilerFrame = profiler.frame(blockFunctionProfilerId, ops[i].opcode);\n }\n};\n\n/**\n * Execute a block.\n * @param {!Sequencer} sequencer Which sequencer is executing.\n * @param {!Thread} thread Thread which to read and execute.\n */\nconst execute = function (sequencer, thread) {\n const runtime = sequencer.runtime;\n\n // store sequencer and thread so block functions can access them through\n // convenience methods.\n blockUtility.sequencer = sequencer;\n blockUtility.thread = thread;\n\n // Current block to execute is the one on the top of the stack.\n const currentBlockId = thread.peekStack();\n const currentStackFrame = thread.peekStackFrame();\n\n let blockContainer = thread.blockContainer;\n let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);\n if (blockCached === null) {\n blockContainer = runtime.flyoutBlocks;\n blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);\n // Stop if block or target no longer exists.\n if (blockCached === null) {\n // No block found: stop the thread; script no longer exists.\n sequencer.retireThread(thread);\n return;\n }\n }\n\n const ops = blockCached._ops;\n const length = ops.length;\n let i = 0;\n\n if (currentStackFrame.reported !== null) {\n const reported = currentStackFrame.reported;\n // Reinstate all the previous values.\n for (; i < reported.length; i++) {\n const {opCached: oldOpCached, inputValue} = reported[i];\n\n const opCached = ops.find(op => op.id === oldOpCached);\n\n if (opCached) {\n const inputName = opCached._parentKey;\n const argValues = opCached._parentValues;\n\n if (inputName === 'BROADCAST_INPUT') {\n // Something is plugged into the broadcast input.\n // Cast it to a string. We don't need an id here.\n argValues.BROADCAST_OPTION.id = null;\n argValues.BROADCAST_OPTION.name = cast.toString(inputValue);\n } else {\n argValues[inputName] = inputValue;\n }\n }\n }\n\n // Find the last reported block that is still in the set of operations.\n // This way if the last operation was removed, we'll find the next\n // candidate. If an earlier block that was performed was removed then\n // we'll find the index where the last operation is now.\n if (reported.length > 0) {\n const lastExisting = reported.reverse().find(report => ops.find(op => op.id === report.opCached));\n if (lastExisting) {\n i = ops.findIndex(opCached => opCached.id === lastExisting.opCached) + 1;\n } else {\n i = 0;\n }\n }\n\n // The reporting block must exist and must be the next one in the sequence of operations.\n if (thread.justReported !== null && ops[i] && ops[i].id === currentStackFrame.reporting) {\n const opCached = ops[i];\n const inputValue = thread.justReported;\n\n thread.justReported = null;\n\n const inputName = opCached._parentKey;\n const argValues = opCached._parentValues;\n\n if (inputName === 'BROADCAST_INPUT') {\n // Something is plugged into the broadcast input.\n // Cast it to a string. We don't need an id here.\n argValues.BROADCAST_OPTION.id = null;\n argValues.BROADCAST_OPTION.name = cast.toString(inputValue);\n } else {\n argValues[inputName] = inputValue;\n }\n\n i += 1;\n }\n\n currentStackFrame.reporting = null;\n currentStackFrame.reported = null;\n }\n\n const start = i;\n\n for (; i < length; i++) {\n const lastOperation = i === length - 1;\n const opCached = ops[i];\n\n const blockFunction = opCached._blockFunction;\n\n // Update values for arguments (inputs).\n const argValues = opCached._argValues;\n\n // Fields are set during opCached initialization.\n\n // Blocks should glow when a script is starting,\n // not after it has finished (see #1404).\n // Only blocks in blockContainers that don't forceNoGlow\n // should request a glow.\n if (!blockContainer.forceNoGlow) {\n thread.requestScriptGlowInFrame = true;\n }\n\n // Inputs are set during previous steps in the loop.\n\n const primitiveReportedValue = blockFunction(argValues, blockUtility);\n\n // If it's a promise, wait until promise resolves.\n if (isPromise(primitiveReportedValue)) {\n handlePromise(primitiveReportedValue, sequencer, thread, opCached, lastOperation);\n\n // Store the already reported values. They will be thawed into the\n // future versions of the same operations by block id. The reporting\n // operation if it is promise waiting will set its parent value at\n // that time.\n thread.justReported = null;\n currentStackFrame.reporting = ops[i].id;\n currentStackFrame.reported = ops.slice(0, i).map(reportedCached => {\n const inputName = reportedCached._parentKey;\n const reportedValues = reportedCached._parentValues;\n\n if (inputName === 'BROADCAST_INPUT') {\n return {\n opCached: reportedCached.id,\n inputValue: reportedValues[inputName].BROADCAST_OPTION.name\n };\n }\n return {\n opCached: reportedCached.id,\n inputValue: reportedValues[inputName]\n };\n });\n\n // We are waiting for a promise. Stop running this set of operations\n // and continue them later after thawing the reported values.\n break;\n } else if (thread.status === Thread.STATUS_RUNNING) {\n if (lastOperation) {\n handleReport(primitiveReportedValue, sequencer, thread, opCached, lastOperation);\n } else {\n // By definition a block that is not last in the list has a\n // parent.\n const inputName = opCached._parentKey;\n const parentValues = opCached._parentValues;\n\n if (inputName === 'BROADCAST_INPUT') {\n // Something is plugged into the broadcast input.\n // Cast it to a string. We don't need an id here.\n parentValues.BROADCAST_OPTION.id = null;\n parentValues.BROADCAST_OPTION.name = cast.toString(primitiveReportedValue);\n } else {\n parentValues[inputName] = primitiveReportedValue;\n }\n }\n }\n }\n\n if (runtime.profiler !== null) {\n if (blockCached._profiler !== runtime.profiler) {\n _prepareBlockProfiling(runtime.profiler, blockCached);\n }\n // Determine the index that is after the last executed block. `i` is\n // currently the block that was just executed. `i + 1` will be the block\n // after that. `length` with the min call makes sure we don't try to\n // reference an operation outside of the set of operations.\n const end = Math.min(i + 1, length);\n for (let p = start; p < end; p++) {\n ops[p]._profilerFrame.count += 1;\n }\n }\n};\n\nmodule.exports = execute;\n","const {Record} = require('immutable');\n\nconst MonitorRecord = Record({\n id: null, // Block Id\n /** Present only if the monitor is sprite-specific, such as x position */\n spriteName: null,\n /** Present only if the monitor is sprite-specific, such as x position */\n targetId: null,\n opcode: null,\n value: null,\n params: null,\n mode: 'default',\n sliderMin: 0,\n sliderMax: 100,\n isDiscrete: true,\n x: null, // (x: null, y: null) Indicates that the monitor should be auto-positioned\n y: null,\n width: 0,\n height: 0,\n visible: true\n});\n\nmodule.exports = MonitorRecord;\n","const html = require('htmlparser2');\nconst decodeHtml = require('decode-html');\n\n/**\n * Convert a part of a mutation DOM to a mutation VM object, recursively.\n * @param {object} dom DOM object for mutation tag.\n * @return {object} Object representing useful parts of this mutation.\n */\nconst mutatorTagToObject = function (dom) {\n const obj = Object.create(null);\n obj.tagName = dom.name;\n obj.children = [];\n for (const prop in dom.attribs) {\n if (prop === 'xmlns') continue;\n obj[prop] = decodeHtml(dom.attribs[prop]);\n // Note: the capitalization of block info in the following lines is important.\n // The lowercase is read in from xml which normalizes case. The VM uses camel case everywhere else.\n if (prop === 'blockinfo') {\n obj.blockInfo = JSON.parse(obj.blockinfo);\n delete obj.blockinfo;\n }\n }\n for (let i = 0; i < dom.children.length; i++) {\n obj.children.push(\n mutatorTagToObject(dom.children[i])\n );\n }\n return obj;\n};\n\n/**\n * Adapter between mutator XML or DOM and block representation which can be\n * used by the Scratch runtime.\n * @param {(object|string)} mutation Mutation XML string or DOM.\n * @return {object} Object representing the mutation.\n */\nconst mutationAdpater = function (mutation) {\n let mutationParsed;\n // Check if the mutation is already parsed; if not, parse it.\n if (typeof mutation === 'object') {\n mutationParsed = mutation;\n } else {\n mutationParsed = html.parseDOM(mutation)[0];\n }\n return mutatorTagToObject(mutationParsed);\n};\n\nmodule.exports = mutationAdpater;\n","/**\n * @fileoverview\n * A way to profile Scratch internal performance. Like what blocks run during a\n * step? How much time do they take? How much time is spent inbetween blocks?\n *\n * Profiler aims for to spend as little time inside its functions while\n * recording. For this it has a simple internal record structure that records a\n * series of values for each START and STOP event in a single array. This lets\n * all the values be pushed in one call for the array. This simplicity allows\n * the contents of the start() and stop() calls to be inlined in areas that are\n * called frequently enough to want even greater performance from Profiler so\n * what is recorded better reflects on the profiled code and not Profiler\n * itself.\n */\n\n/**\n * The next id returned for a new profile'd function.\n * @type {number}\n */\nlet nextId = 0;\n\n/**\n * The mapping of names to ids.\n * @const {Object.}\n */\nconst profilerNames = {};\n\n/**\n * The START event identifier in Profiler records.\n * @const {number}\n */\nconst START = 0;\n\n/**\n * The STOP event identifier in Profiler records.\n * @const {number}\n */\nconst STOP = 1;\n\n/**\n * The number of cells used in the records array by a START event.\n * @const {number}\n */\nconst START_SIZE = 4;\n\n/**\n * The number of cells used in the records array by a STOP event.\n * @const {number}\n */\nconst STOP_SIZE = 2;\n\n/**\n * Stored reference to Performance instance provided by the Browser.\n * @const {Performance}\n */\nconst performance = typeof window === 'object' && window.performance;\n\n\n/**\n * Callback handle called by Profiler for each frame it decodes from its\n * records.\n * @callback FrameCallback\n * @param {ProfilerFrame} frame\n */\n\n/**\n * A set of information about a frame of execution that was recorded.\n */\nclass ProfilerFrame {\n /**\n * @param {number} depth Depth of the frame in the recorded stack.\n */\n constructor (depth) {\n /**\n * The numeric id of a record symbol like Runtime._step or\n * blockFunction.\n * @type {number}\n */\n this.id = -1;\n\n /**\n * The amount of time spent inside the recorded frame and any deeper\n * frames.\n * @type {number}\n */\n this.totalTime = 0;\n\n /**\n * The amount of time spent only inside this record frame. Not\n * including time in any deeper frames.\n * @type {number}\n */\n this.selfTime = 0;\n\n /**\n * An arbitrary argument for the recorded frame. For example a block\n * function might record its opcode as an argument.\n * @type {*}\n */\n this.arg = null;\n\n /**\n * The depth of the recorded frame. This can help compare recursive\n * funtions that are recorded. Each level of recursion with have a\n * different depth value.\n * @type {number}\n */\n this.depth = depth;\n\n /**\n * A summarized count of the number of calls to this frame.\n * @type {number}\n */\n this.count = 0;\n }\n}\n\nclass Profiler {\n /**\n * @param {FrameCallback} onFrame a handle called for each recorded frame.\n * The passed frame value may not be stored as it'll be updated with later\n * frame information. Any information that is further stored by the handler\n * should make copies or reduce the information.\n */\n constructor (onFrame = function () {}) {\n /**\n * A series of START and STOP values followed by arguments. After\n * recording is complete the full set of records is reported back by\n * stepping through the series to connect the relative START and STOP\n * information.\n * @type {Array.<*>}\n */\n this.records = [];\n\n /**\n * An array of frames incremented on demand instead as part of start\n * and stop.\n * @type {Array.}\n */\n this.increments = [];\n\n /**\n * An array of profiler frames separated by counter argument. Generally\n * for Scratch these frames are separated by block function opcode.\n * This tracks each time an opcode is called.\n * @type {Array.}\n */\n this.counters = [];\n\n /**\n * A frame with no id or argument.\n * @type {ProfilerFrame}\n */\n this.nullFrame = new ProfilerFrame(-1);\n\n /**\n * A cache of ProfilerFrames to reuse when reporting the recorded\n * frames in records.\n * @type {Array.}\n */\n this._stack = [new ProfilerFrame(0)];\n\n /**\n * A callback handle called with each decoded frame when reporting back\n * all the recorded times.\n * @type {FrameCallback}\n */\n this.onFrame = onFrame;\n\n /**\n * A reference to the START record id constant.\n * @const {number}\n */\n this.START = START;\n\n /**\n * A reference to the STOP record id constant.\n * @const {number}\n */\n this.STOP = STOP;\n }\n\n /**\n * Start recording a frame of time for an id and optional argument.\n * @param {number} id The id returned by idByName for a name symbol like\n * Runtime._step.\n * @param {?*} arg An arbitrary argument value to store with the frame.\n */\n start (id, arg) {\n this.records.push(START, id, arg, performance.now());\n }\n\n /**\n * Stop the current frame.\n */\n stop () {\n this.records.push(STOP, performance.now());\n }\n\n /**\n * Increment the number of times this symbol is called.\n * @param {number} id The id returned by idByName for a name symbol.\n */\n increment (id) {\n if (!this.increments[id]) {\n this.increments[id] = new ProfilerFrame(-1);\n this.increments[id].id = id;\n }\n this.increments[id].count += 1;\n }\n\n /**\n * Find or create a ProfilerFrame-like object whose counter can be\n * incremented outside of the Profiler.\n * @param {number} id The id returned by idByName for a name symbol.\n * @param {*} arg The argument for a frame that identifies it in addition\n * to the id.\n * @return {{count: number}} A ProfilerFrame-like whose count should be\n * incremented for each call.\n */\n frame (id, arg) {\n for (let i = 0; i < this.counters.length; i++) {\n if (this.counters[i].id === id && this.counters[i].arg === arg) {\n return this.counters[i];\n }\n }\n\n const newCounter = new ProfilerFrame(-1);\n newCounter.id = id;\n newCounter.arg = arg;\n this.counters.push(newCounter);\n return newCounter;\n }\n\n /**\n * Decode records and report all frames to `this.onFrame`.\n */\n reportFrames () {\n const stack = this._stack;\n let depth = 1;\n\n // Step through the records and initialize Frame instances from the\n // START and STOP events. START and STOP events are separated by events\n // for deeper frames run by higher frames. Frames are stored on a stack\n // and reinitialized for each START event. When a stop event is reach\n // the Frame for the current depth has its final values stored and its\n // passed to the current onFrame callback. This way Frames are \"pushed\"\n // for each START event and \"popped\" for each STOP and handed to an\n // outside handle to any desired reduction of the collected data.\n for (let i = 0; i < this.records.length;) {\n if (this.records[i] === START) {\n if (depth >= stack.length) {\n stack.push(new ProfilerFrame(depth));\n }\n\n // Store id, arg, totalTime, and initialize selfTime.\n const frame = stack[depth++];\n frame.id = this.records[i + 1];\n frame.arg = this.records[i + 2];\n // totalTime is first set as the time recorded by this START\n // event. Once the STOP event is reached the stored start time\n // is subtracted from the recorded stop time. The resulting\n // difference is the actual totalTime, and replaces the start\n // time in frame.totalTime.\n //\n // totalTime is used this way as a convenient member to store a\n // value between the two events without needing additional\n // members on the Frame or in a shadow map.\n frame.totalTime = this.records[i + 3];\n // selfTime is decremented until we reach the STOP event for\n // this frame. totalTime will be added to it then to get the\n // time difference.\n frame.selfTime = 0;\n\n i += START_SIZE;\n } else if (this.records[i] === STOP) {\n const now = this.records[i + 1];\n\n const frame = stack[--depth];\n // totalTime is the difference between the start event time\n // stored in totalTime and the stop event time pulled from this\n // record.\n frame.totalTime = now - frame.totalTime;\n // selfTime is the difference of this frame's totalTime and the\n // sum of totalTime of deeper frames.\n frame.selfTime += frame.totalTime;\n\n // Remove this frames totalTime from the parent's selfTime.\n stack[depth - 1].selfTime -= frame.totalTime;\n\n // This frame occured once.\n frame.count = 1;\n\n this.onFrame(frame);\n\n i += STOP_SIZE;\n } else {\n this.records.length = 0;\n throw new Error('Unable to decode Profiler records.');\n }\n }\n\n for (let j = 0; j < this.increments.length; j++) {\n if (this.increments[j] && this.increments[j].count > 0) {\n this.onFrame(this.increments[j]);\n this.increments[j].count = 0;\n }\n }\n\n for (let k = 0; k < this.counters.length; k++) {\n if (this.counters[k].count > 0) {\n this.onFrame(this.counters[k]);\n this.counters[k].count = 0;\n }\n }\n\n this.records.length = 0;\n }\n\n /**\n * Lookup or create an id for a frame name.\n * @param {string} name The name to return an id for.\n * @return {number} The id for the passed name.\n */\n idByName (name) {\n return Profiler.idByName(name);\n }\n\n /**\n * Reverse lookup the name from a given frame id.\n * @param {number} id The id to search for.\n * @return {string} The name for the given id.\n */\n nameById (id) {\n return Profiler.nameById(id);\n }\n\n /**\n * Lookup or create an id for a frame name.\n * @static\n * @param {string} name The name to return an id for.\n * @return {number} The id for the passed name.\n */\n static idByName (name) {\n if (typeof profilerNames[name] !== 'number') {\n profilerNames[name] = nextId++;\n }\n return profilerNames[name];\n }\n\n /**\n * Reverse lookup the name from a given frame id.\n * @static\n * @param {number} id The id to search for.\n * @return {string} The name for the given id.\n */\n static nameById (id) {\n for (const name in profilerNames) {\n if (profilerNames[name] === id) {\n return name;\n }\n }\n return null;\n }\n\n /**\n * Profiler is only available on platforms with the Performance API.\n * @return {boolean} Can the Profiler run in this browser?\n */\n static available () {\n return (\n typeof window === 'object' &&\n typeof window.performance !== 'undefined');\n }\n}\n\n\n/**\n * A reference to the START record id constant.\n * @const {number}\n */\nProfiler.START = START;\n\n/**\n * A reference to the STOP record id constant.\n * @const {number}\n */\nProfiler.STOP = STOP;\n\nmodule.exports = Profiler;\n","const EventEmitter = require('events');\nconst {OrderedMap} = require('immutable');\n\nconst ArgumentType = require('../extension-support/argument-type');\nconst Blocks = require('./blocks');\nconst BlocksRuntimeCache = require('./blocks-runtime-cache');\nconst BlockType = require('../extension-support/block-type');\nconst Profiler = require('./profiler');\nconst Sequencer = require('./sequencer');\nconst execute = require('./execute.js');\nconst ScratchBlocksConstants = require('./scratch-blocks-constants');\nconst TargetType = require('../extension-support/target-type');\nconst Thread = require('./thread');\nconst log = require('../util/log');\nconst maybeFormatMessage = require('../util/maybe-format-message');\nconst StageLayering = require('./stage-layering');\nconst Variable = require('./variable');\nconst xmlEscape = require('../util/xml-escape');\nconst ScratchLinkWebSocket = require('../util/scratch-link-websocket');\n\n// Virtual I/O devices.\nconst Clock = require('../io/clock');\nconst Cloud = require('../io/cloud');\nconst Keyboard = require('../io/keyboard');\nconst Mouse = require('../io/mouse');\nconst MouseWheel = require('../io/mouseWheel');\nconst UserData = require('../io/userData');\nconst Video = require('../io/video');\n\nconst StringUtil = require('../util/string-util');\nconst uid = require('../util/uid');\n\nconst defaultBlockPackages = {\n scratch3_control: require('../blocks/scratch3_control'),\n scratch3_event: require('../blocks/scratch3_event'),\n scratch3_looks: require('../blocks/scratch3_looks'),\n scratch3_motion: require('../blocks/scratch3_motion'),\n scratch3_operators: require('../blocks/scratch3_operators'),\n scratch3_sound: require('../blocks/scratch3_sound'),\n scratch3_sensing: require('../blocks/scratch3_sensing'),\n scratch3_data: require('../blocks/scratch3_data'),\n scratch3_procedures: require('../blocks/scratch3_procedures')\n};\n\nconst defaultExtensionColors = ['#0FBD8C', '#0DA57A', '#0B8E69'];\n\n/**\n * Information used for converting Scratch argument types into scratch-blocks data.\n * @type {object.}\n */\nconst ArgumentTypeMap = (() => {\n const map = {};\n map[ArgumentType.ANGLE] = {\n shadow: {\n type: 'math_angle',\n // We specify fieldNames here so that we can pick\n // create and populate a field with the defaultValue\n // specified in the extension.\n // When the `fieldName` property is not specified,\n // the will be left out of the XML and\n // the scratch-blocks defaults for that field will be\n // used instead (e.g. default of 0 for number fields)\n fieldName: 'NUM'\n }\n };\n map[ArgumentType.COLOR] = {\n shadow: {\n type: 'colour_picker',\n fieldName: 'COLOUR'\n }\n };\n map[ArgumentType.NUMBER] = {\n shadow: {\n type: 'math_number',\n fieldName: 'NUM'\n }\n };\n map[ArgumentType.STRING] = {\n shadow: {\n type: 'text',\n fieldName: 'TEXT'\n }\n };\n map[ArgumentType.BOOLEAN] = {\n check: 'Boolean'\n };\n map[ArgumentType.MATRIX] = {\n shadow: {\n type: 'matrix',\n fieldName: 'MATRIX'\n }\n };\n map[ArgumentType.NOTE] = {\n shadow: {\n type: 'note',\n fieldName: 'NOTE'\n }\n };\n map[ArgumentType.IMAGE] = {\n // Inline images are weird because they're not actually \"arguments\".\n // They are more analagous to the label on a block.\n fieldType: 'field_image'\n };\n return map;\n})();\n\n/**\n * A pair of functions used to manage the cloud variable limit,\n * to be used when adding (or attempting to add) or removing a cloud variable.\n * @typedef {object} CloudDataManager\n * @property {function} canAddCloudVariable A function to call to check that\n * a cloud variable can be added.\n * @property {function} addCloudVariable A function to call to track a new\n * cloud variable on the runtime.\n * @property {function} removeCloudVariable A function to call when\n * removing an existing cloud variable.\n * @property {function} hasCloudVariables A function to call to check that\n * the runtime has any cloud variables.\n */\n\n/**\n * Creates and manages cloud variable limit in a project,\n * and returns two functions to be used to add a new\n * cloud variable (while checking that it can be added)\n * and remove an existing cloud variable.\n * These are to be called whenever attempting to create or delete\n * a cloud variable.\n * @return {CloudDataManager} The functions to be used when adding or removing a\n * cloud variable.\n */\nconst cloudDataManager = () => {\n const limit = 10;\n let count = 0;\n\n const canAddCloudVariable = () => count < limit;\n\n const addCloudVariable = () => {\n count++;\n };\n\n const removeCloudVariable = () => {\n count--;\n };\n\n const hasCloudVariables = () => count > 0;\n\n return {\n canAddCloudVariable,\n addCloudVariable,\n removeCloudVariable,\n hasCloudVariables\n };\n};\n\n/**\n * Numeric ID for Runtime._step in Profiler instances.\n * @type {number}\n */\nlet stepProfilerId = -1;\n\n/**\n * Numeric ID for Sequencer.stepThreads in Profiler instances.\n * @type {number}\n */\nlet stepThreadsProfilerId = -1;\n\n/**\n * Numeric ID for RenderWebGL.draw in Profiler instances.\n * @type {number}\n */\nlet rendererDrawProfilerId = -1;\n\n/**\n * Manages targets, scripts, and the sequencer.\n * @constructor\n */\nclass Runtime extends EventEmitter {\n constructor () {\n super();\n\n /**\n * Target management and storage.\n * @type {Array.}\n */\n this.targets = [];\n\n /**\n * Targets in reverse order of execution. Shares its order with drawables.\n * @type {Array.}\n */\n this.executableTargets = [];\n\n /**\n * A list of threads that are currently running in the VM.\n * Threads are added when execution starts and pruned when execution ends.\n * @type {Array.}\n */\n this.threads = [];\n\n /** @type {!Sequencer} */\n this.sequencer = new Sequencer(this);\n\n /**\n * Storage container for flyout blocks.\n * These will execute on `_editingTarget.`\n * @type {!Blocks}\n */\n this.flyoutBlocks = new Blocks(this, true /* force no glow */);\n\n /**\n * Storage container for monitor blocks.\n * These will execute on a target maybe\n * @type {!Blocks}\n */\n this.monitorBlocks = new Blocks(this, true /* force no glow */);\n\n /**\n * Currently known editing target for the VM.\n * @type {?Target}\n */\n this._editingTarget = null;\n\n /**\n * Map to look up a block primitive's implementation function by its opcode.\n * This is a two-step lookup: package name first, then primitive name.\n * @type {Object.}\n */\n this._primitives = {};\n\n /**\n * Map to look up all block information by extended opcode.\n * @type {Array.}\n * @private\n */\n this._blockInfo = [];\n\n /**\n * Map to look up hat blocks' metadata.\n * Keys are opcode for hat, values are metadata objects.\n * @type {Object.}\n */\n this._hats = {};\n\n /**\n * A list of script block IDs that were glowing during the previous frame.\n * @type {!Array.}\n */\n this._scriptGlowsPreviousFrame = [];\n\n /**\n * Number of non-monitor threads running during the previous frame.\n * @type {number}\n */\n this._nonMonitorThreadCount = 0;\n\n /**\n * All threads that finished running and were removed from this.threads\n * by behaviour in Sequencer.stepThreads.\n * @type {Array}\n */\n this._lastStepDoneThreads = null;\n\n /**\n * Currently known number of clones, used to enforce clone limit.\n * @type {number}\n */\n this._cloneCounter = 0;\n\n /**\n * Flag to emit a targets update at the end of a step. When target data\n * changes, this flag is set to true.\n * @type {boolean}\n */\n this._refreshTargets = false;\n\n /**\n * Map to look up all monitor block information by opcode.\n * @type {object}\n * @private\n */\n this.monitorBlockInfo = {};\n\n /**\n * Ordered map of all monitors, which are MonitorReporter objects.\n */\n this._monitorState = OrderedMap({});\n\n /**\n * Monitor state from last tick\n */\n this._prevMonitorState = OrderedMap({});\n\n /**\n * Whether the project is in \"turbo mode.\"\n * @type {Boolean}\n */\n this.turboMode = false;\n\n /**\n * Whether the project is in \"compatibility mode\" (30 TPS).\n * @type {Boolean}\n */\n this.compatibilityMode = false;\n\n /**\n * A reference to the current runtime stepping interval, set\n * by a `setInterval`.\n * @type {!number}\n */\n this._steppingInterval = null;\n\n /**\n * Current length of a step.\n * Changes as mode switches, and used by the sequencer to calculate\n * WORK_TIME.\n * @type {!number}\n */\n this.currentStepTime = null;\n\n // Set an intial value for this.currentMSecs\n this.updateCurrentMSecs();\n\n /**\n * Whether any primitive has requested a redraw.\n * Affects whether `Sequencer.stepThreads` will yield\n * after stepping each thread.\n * Reset on every frame.\n * @type {boolean}\n */\n this.redrawRequested = false;\n\n // Register all given block packages.\n this._registerBlockPackages();\n\n // Register and initialize \"IO devices\", containers for processing\n // I/O related data.\n /** @type {Object.} */\n this.ioDevices = {\n clock: new Clock(this),\n cloud: new Cloud(this),\n keyboard: new Keyboard(this),\n mouse: new Mouse(this),\n mouseWheel: new MouseWheel(this),\n userData: new UserData(),\n video: new Video(this)\n };\n\n /**\n * A list of extensions, used to manage hardware connection.\n */\n this.peripheralExtensions = {};\n\n /**\n * A runtime profiler that records timed events for later playback to\n * diagnose Scratch performance.\n * @type {Profiler}\n */\n this.profiler = null;\n\n const newCloudDataManager = cloudDataManager();\n\n /**\n * Check wether the runtime has any cloud data.\n * @type {function}\n * @return {boolean} Whether or not the runtime currently has any\n * cloud variables.\n */\n this.hasCloudData = newCloudDataManager.hasCloudVariables;\n\n /**\n * A function which checks whether a new cloud variable can be added\n * to the runtime.\n * @type {function}\n * @return {boolean} Whether or not a new cloud variable can be added\n * to the runtime.\n */\n this.canAddCloudVariable = newCloudDataManager.canAddCloudVariable;\n\n /**\n * A function that tracks a new cloud variable in the runtime,\n * updating the cloud variable limit. Calling this function will\n * emit a cloud data update event if this is the first cloud variable\n * being added.\n * @type {function}\n */\n this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager);\n\n /**\n * A function which updates the runtime's cloud variable limit\n * when removing a cloud variable and emits a cloud update event\n * if the last of the cloud variables is being removed.\n * @type {function}\n */\n this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager);\n }\n\n /**\n * Width of the stage, in pixels.\n * @const {number}\n */\n static get STAGE_WIDTH () {\n return 480;\n }\n\n /**\n * Height of the stage, in pixels.\n * @const {number}\n */\n static get STAGE_HEIGHT () {\n return 360;\n }\n\n /**\n * Event name for glowing a script.\n * @const {string}\n */\n static get SCRIPT_GLOW_ON () {\n return 'SCRIPT_GLOW_ON';\n }\n\n /**\n * Event name for unglowing a script.\n * @const {string}\n */\n static get SCRIPT_GLOW_OFF () {\n return 'SCRIPT_GLOW_OFF';\n }\n\n /**\n * Event name for glowing a block.\n * @const {string}\n */\n static get BLOCK_GLOW_ON () {\n return 'BLOCK_GLOW_ON';\n }\n\n /**\n * Event name for unglowing a block.\n * @const {string}\n */\n static get BLOCK_GLOW_OFF () {\n return 'BLOCK_GLOW_OFF';\n }\n\n /**\n * Event name for a cloud data update\n * to this project.\n * @const {string}\n */\n static get HAS_CLOUD_DATA_UPDATE () {\n return 'HAS_CLOUD_DATA_UPDATE';\n }\n\n /**\n * Event name for turning on turbo mode.\n * @const {string}\n */\n static get TURBO_MODE_ON () {\n return 'TURBO_MODE_ON';\n }\n\n /**\n * Event name for turning off turbo mode.\n * @const {string}\n */\n static get TURBO_MODE_OFF () {\n return 'TURBO_MODE_OFF';\n }\n\n /**\n * Event name when the project is started (threads may not necessarily be\n * running).\n * @const {string}\n */\n static get PROJECT_START () {\n return 'PROJECT_START';\n }\n\n /**\n * Event name when threads start running.\n * Used by the UI to indicate running status.\n * @const {string}\n */\n static get PROJECT_RUN_START () {\n return 'PROJECT_RUN_START';\n }\n\n /**\n * Event name when threads stop running\n * Used by the UI to indicate not-running status.\n * @const {string}\n */\n static get PROJECT_RUN_STOP () {\n return 'PROJECT_RUN_STOP';\n }\n\n /**\n * Event name for project being stopped or restarted by the user.\n * Used by blocks that need to reset state.\n * @const {string}\n */\n static get PROJECT_STOP_ALL () {\n return 'PROJECT_STOP_ALL';\n }\n\n /**\n * Event name for target being stopped by a stop for target call.\n * Used by blocks that need to stop individual targets.\n * @const {string}\n */\n static get STOP_FOR_TARGET () {\n return 'STOP_FOR_TARGET';\n }\n\n /**\n * Event name for visual value report.\n * @const {string}\n */\n static get VISUAL_REPORT () {\n return 'VISUAL_REPORT';\n }\n\n /**\n * Event name for project loaded report.\n * @const {string}\n */\n static get PROJECT_LOADED () {\n return 'PROJECT_LOADED';\n }\n\n /**\n * Event name for report that a change was made that can be saved\n * @const {string}\n */\n static get PROJECT_CHANGED () {\n return 'PROJECT_CHANGED';\n }\n\n /**\n * Event name for report that a change was made to an extension in the toolbox.\n * @const {string}\n */\n static get TOOLBOX_EXTENSIONS_NEED_UPDATE () {\n return 'TOOLBOX_EXTENSIONS_NEED_UPDATE';\n }\n\n /**\n * Event name for targets update report.\n * @const {string}\n */\n static get TARGETS_UPDATE () {\n return 'TARGETS_UPDATE';\n }\n\n /**\n * Event name for monitors update.\n * @const {string}\n */\n static get MONITORS_UPDATE () {\n return 'MONITORS_UPDATE';\n }\n\n /**\n * Event name for block drag update.\n * @const {string}\n */\n static get BLOCK_DRAG_UPDATE () {\n return 'BLOCK_DRAG_UPDATE';\n }\n\n /**\n * Event name for block drag end.\n * @const {string}\n */\n static get BLOCK_DRAG_END () {\n return 'BLOCK_DRAG_END';\n }\n\n /**\n * Event name for reporting that an extension was added.\n * @const {string}\n */\n static get EXTENSION_ADDED () {\n return 'EXTENSION_ADDED';\n }\n\n /**\n * Event name for reporting that an extension as asked for a custom field to be added\n * @const {string}\n */\n static get EXTENSION_FIELD_ADDED () {\n return 'EXTENSION_FIELD_ADDED';\n }\n\n /**\n * Event name for updating the available set of peripheral devices.\n * This causes the peripheral connection modal to update a list of\n * available peripherals.\n * @const {string}\n */\n static get PERIPHERAL_LIST_UPDATE () {\n return 'PERIPHERAL_LIST_UPDATE';\n }\n \n /**\n * Event name for when the user picks a bluetooth device to connect to\n * via Companion Device Manager (CDM)\n * @const {string}\n */\n static get USER_PICKED_PERIPHERAL () {\n return 'USER_PICKED_PERIPHERAL';\n }\n\n /**\n * Event name for reporting that a peripheral has connected.\n * This causes the status button in the blocks menu to indicate 'connected'.\n * @const {string}\n */\n static get PERIPHERAL_CONNECTED () {\n return 'PERIPHERAL_CONNECTED';\n }\n\n /**\n * Event name for reporting that a peripheral has been intentionally disconnected.\n * This causes the status button in the blocks menu to indicate 'disconnected'.\n * @const {string}\n */\n static get PERIPHERAL_DISCONNECTED () {\n return 'PERIPHERAL_DISCONNECTED';\n }\n\n /**\n * Event name for reporting that a peripheral has encountered a request error.\n * This causes the peripheral connection modal to switch to an error state.\n * @const {string}\n */\n static get PERIPHERAL_REQUEST_ERROR () {\n return 'PERIPHERAL_REQUEST_ERROR';\n }\n\n /**\n * Event name for reporting that a peripheral connection has been lost.\n * This causes a 'peripheral connection lost' error alert to display.\n * @const {string}\n */\n static get PERIPHERAL_CONNECTION_LOST_ERROR () {\n return 'PERIPHERAL_CONNECTION_LOST_ERROR';\n }\n\n /**\n * Event name for reporting that a peripheral has not been discovered.\n * This causes the peripheral connection modal to show a timeout state.\n * @const {string}\n */\n static get PERIPHERAL_SCAN_TIMEOUT () {\n return 'PERIPHERAL_SCAN_TIMEOUT';\n }\n\n /**\n * Event name to indicate that the microphone is being used to stream audio.\n * @const {string}\n */\n static get MIC_LISTENING () {\n return 'MIC_LISTENING';\n }\n\n /**\n * Event name for reporting that blocksInfo was updated.\n * @const {string}\n */\n static get BLOCKSINFO_UPDATE () {\n return 'BLOCKSINFO_UPDATE';\n }\n\n /**\n * Event name when the runtime tick loop has been started.\n * @const {string}\n */\n static get RUNTIME_STARTED () {\n return 'RUNTIME_STARTED';\n }\n\n /**\n * Event name when the runtime dispose has been called.\n * @const {string}\n */\n static get RUNTIME_DISPOSED () {\n return 'RUNTIME_DISPOSED';\n }\n\n /**\n * Event name for reporting that a block was updated and needs to be rerendered.\n * @const {string}\n */\n static get BLOCKS_NEED_UPDATE () {\n return 'BLOCKS_NEED_UPDATE';\n }\n\n /**\n * How rapidly we try to step threads by default, in ms.\n */\n static get THREAD_STEP_INTERVAL () {\n return 1000 / 60;\n }\n\n /**\n * In compatibility mode, how rapidly we try to step threads, in ms.\n */\n static get THREAD_STEP_INTERVAL_COMPATIBILITY () {\n return 1000 / 30;\n }\n\n /**\n * How many clones can be created at a time.\n * @const {number}\n */\n static get MAX_CLONES () {\n return 300;\n }\n\n // -----------------------------------------------------------------------------\n // -----------------------------------------------------------------------------\n\n // Helper function for initializing the addCloudVariable function\n _initializeAddCloudVariable (newCloudDataManager) {\n // The addCloudVariable function\n return (() => {\n const hadCloudVarsBefore = this.hasCloudData();\n newCloudDataManager.addCloudVariable();\n if (!hadCloudVarsBefore && this.hasCloudData()) {\n this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, true);\n }\n });\n }\n\n // Helper function for initializing the removeCloudVariable function\n _initializeRemoveCloudVariable (newCloudDataManager) {\n return (() => {\n const hadCloudVarsBefore = this.hasCloudData();\n newCloudDataManager.removeCloudVariable();\n if (hadCloudVarsBefore && !this.hasCloudData()) {\n this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, false);\n }\n });\n }\n\n /**\n * Register default block packages with this runtime.\n * @todo Prefix opcodes with package name.\n * @private\n */\n _registerBlockPackages () {\n for (const packageName in defaultBlockPackages) {\n if (defaultBlockPackages.hasOwnProperty(packageName)) {\n // @todo pass a different runtime depending on package privilege?\n const packageObject = new (defaultBlockPackages[packageName])(this);\n // Collect primitives from package.\n if (packageObject.getPrimitives) {\n const packagePrimitives = packageObject.getPrimitives();\n for (const op in packagePrimitives) {\n if (packagePrimitives.hasOwnProperty(op)) {\n this._primitives[op] =\n packagePrimitives[op].bind(packageObject);\n }\n }\n }\n // Collect hat metadata from package.\n if (packageObject.getHats) {\n const packageHats = packageObject.getHats();\n for (const hatName in packageHats) {\n if (packageHats.hasOwnProperty(hatName)) {\n this._hats[hatName] = packageHats[hatName];\n }\n }\n }\n // Collect monitored from package.\n if (packageObject.getMonitored) {\n this.monitorBlockInfo = Object.assign({}, this.monitorBlockInfo, packageObject.getMonitored());\n }\n }\n }\n }\n\n getMonitorState () {\n return this._monitorState;\n }\n\n /**\n * Generate an extension-specific menu ID.\n * @param {string} menuName - the name of the menu.\n * @param {string} extensionId - the ID of the extension hosting the menu.\n * @returns {string} - the constructed ID.\n * @private\n */\n _makeExtensionMenuId (menuName, extensionId) {\n return `${extensionId}_menu_${xmlEscape(menuName)}`;\n }\n\n /**\n * Create a context (\"args\") object for use with `formatMessage` on messages which might be target-specific.\n * @param {Target} [target] - the target to use as context. If a target is not provided, default to the current\n * editing target or the stage.\n */\n makeMessageContextForTarget (target) {\n const context = {};\n target = target || this.getEditingTarget() || this.getTargetForStage();\n if (target) {\n context.targetType = (target.isStage ? TargetType.STAGE : TargetType.SPRITE);\n }\n }\n\n /**\n * Register the primitives provided by an extension.\n * @param {ExtensionMetadata} extensionInfo - information about the extension (id, blocks, etc.)\n * @private\n */\n _registerExtensionPrimitives (extensionInfo) {\n const categoryInfo = {\n id: extensionInfo.id,\n name: maybeFormatMessage(extensionInfo.name),\n showStatusButton: extensionInfo.showStatusButton,\n blockIconURI: extensionInfo.blockIconURI,\n menuIconURI: extensionInfo.menuIconURI\n };\n\n if (extensionInfo.color1) {\n categoryInfo.color1 = extensionInfo.color1;\n categoryInfo.color2 = extensionInfo.color2;\n categoryInfo.color3 = extensionInfo.color3;\n } else {\n categoryInfo.color1 = defaultExtensionColors[0];\n categoryInfo.color2 = defaultExtensionColors[1];\n categoryInfo.color3 = defaultExtensionColors[2];\n }\n\n this._blockInfo.push(categoryInfo);\n\n this._fillExtensionCategory(categoryInfo, extensionInfo);\n\n for (const fieldTypeName in categoryInfo.customFieldTypes) {\n if (extensionInfo.customFieldTypes.hasOwnProperty(fieldTypeName)) {\n const fieldTypeInfo = categoryInfo.customFieldTypes[fieldTypeName];\n\n // Emit events for custom field types from extension\n this.emit(Runtime.EXTENSION_FIELD_ADDED, {\n name: `field_${fieldTypeInfo.extendedName}`,\n implementation: fieldTypeInfo.fieldImplementation\n });\n }\n }\n\n this.emit(Runtime.EXTENSION_ADDED, categoryInfo);\n }\n\n /**\n * Reregister the primitives for an extension\n * @param {ExtensionMetadata} extensionInfo - new info (results of running getInfo) for an extension\n * @private\n */\n _refreshExtensionPrimitives (extensionInfo) {\n const categoryInfo = this._blockInfo.find(info => info.id === extensionInfo.id);\n if (categoryInfo) {\n categoryInfo.name = maybeFormatMessage(extensionInfo.name);\n this._fillExtensionCategory(categoryInfo, extensionInfo);\n\n this.emit(Runtime.BLOCKSINFO_UPDATE, categoryInfo);\n }\n }\n\n /**\n * Read extension information, convert menus, blocks and custom field types\n * and store the results in the provided category object.\n * @param {CategoryInfo} categoryInfo - the category to be filled\n * @param {ExtensionMetadata} extensionInfo - the extension metadata to read\n * @private\n */\n _fillExtensionCategory (categoryInfo, extensionInfo) {\n categoryInfo.blocks = [];\n categoryInfo.customFieldTypes = {};\n categoryInfo.menus = [];\n categoryInfo.menuInfo = {};\n\n for (const menuName in extensionInfo.menus) {\n if (extensionInfo.menus.hasOwnProperty(menuName)) {\n const menuInfo = extensionInfo.menus[menuName];\n const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo);\n categoryInfo.menus.push(convertedMenu);\n categoryInfo.menuInfo[menuName] = menuInfo;\n }\n }\n for (const fieldTypeName in extensionInfo.customFieldTypes) {\n if (extensionInfo.customFieldTypes.hasOwnProperty(fieldTypeName)) {\n const fieldType = extensionInfo.customFieldTypes[fieldTypeName];\n const fieldTypeInfo = this._buildCustomFieldInfo(\n fieldTypeName,\n fieldType,\n extensionInfo.id,\n categoryInfo\n );\n\n categoryInfo.customFieldTypes[fieldTypeName] = fieldTypeInfo;\n }\n }\n\n for (const blockInfo of extensionInfo.blocks) {\n try {\n const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo);\n categoryInfo.blocks.push(convertedBlock);\n if (convertedBlock.json) {\n const opcode = convertedBlock.json.type;\n if (blockInfo.blockType !== BlockType.EVENT) {\n this._primitives[opcode] = convertedBlock.info.func;\n }\n if (blockInfo.blockType === BlockType.EVENT || blockInfo.blockType === BlockType.HAT) {\n this._hats[opcode] = {\n edgeActivated: blockInfo.isEdgeActivated,\n restartExistingThreads: blockInfo.shouldRestartExistingThreads\n };\n }\n }\n } catch (e) {\n log.error('Error parsing block: ', {block: blockInfo, error: e});\n }\n }\n }\n\n /**\n * Convert the given extension menu items into the scratch-blocks style of list of pairs.\n * If the menu is dynamic (e.g. the passed in argument is a function), return the input unmodified.\n * @param {object} menuItems - an array of menu items or a function to retrieve such an array\n * @returns {object} - an array of 2 element arrays or the original input function\n * @private\n */\n _convertMenuItems (menuItems) {\n if (typeof menuItems !== 'function') {\n const extensionMessageContext = this.makeMessageContextForTarget();\n return menuItems.map(item => {\n const formattedItem = maybeFormatMessage(item, extensionMessageContext);\n switch (typeof formattedItem) {\n case 'string':\n return [formattedItem, formattedItem];\n case 'object':\n return [maybeFormatMessage(item.text, extensionMessageContext), item.value];\n default:\n throw new Error(`Can't interpret menu item: ${JSON.stringify(item)}`);\n }\n });\n }\n return menuItems;\n }\n\n /**\n * Build the scratch-blocks JSON for a menu. Note that scratch-blocks treats menus as a special kind of block.\n * @param {string} menuName - the name of the menu\n * @param {object} menuInfo - a description of this menu and its items\n * @property {*} items - an array of menu items or a function to retrieve such an array\n * @property {boolean} [acceptReporters] - if true, allow dropping reporters onto this menu\n * @param {CategoryInfo} categoryInfo - the category for this block\n * @returns {object} - a JSON-esque object ready for scratch-blocks' consumption\n * @private\n */\n _buildMenuForScratchBlocks (menuName, menuInfo, categoryInfo) {\n const menuId = this._makeExtensionMenuId(menuName, categoryInfo.id);\n const menuItems = this._convertMenuItems(menuInfo.items);\n return {\n json: {\n message0: '%1',\n type: menuId,\n inputsInline: true,\n output: 'String',\n colour: categoryInfo.color1,\n colourSecondary: categoryInfo.color2,\n colourTertiary: categoryInfo.color3,\n outputShape: menuInfo.acceptReporters ?\n ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,\n args0: [\n {\n type: 'field_dropdown',\n name: menuName,\n options: menuItems\n }\n ]\n }\n };\n }\n\n _buildCustomFieldInfo (fieldName, fieldInfo, extensionId, categoryInfo) {\n const extendedName = `${extensionId}_${fieldName}`;\n return {\n fieldName: fieldName,\n extendedName: extendedName,\n argumentTypeInfo: {\n shadow: {\n type: extendedName,\n fieldName: `field_${extendedName}`\n }\n },\n scratchBlocksDefinition: this._buildCustomFieldTypeForScratchBlocks(\n extendedName,\n fieldInfo.output,\n fieldInfo.outputShape,\n categoryInfo\n ),\n fieldImplementation: fieldInfo.implementation\n };\n }\n\n /**\n * Build the scratch-blocks JSON needed for a fieldType.\n * Custom field types need to be namespaced to the extension so that extensions can't interfere with each other\n * @param {string} fieldName - The name of the field\n * @param {string} output - The output of the field\n * @param {number} outputShape - Shape of the field (from ScratchBlocksConstants)\n * @param {object} categoryInfo - The category the field belongs to (Used to set its colors)\n * @returns {object} - Object to be inserted into scratch-blocks\n */\n _buildCustomFieldTypeForScratchBlocks (fieldName, output, outputShape, categoryInfo) {\n return {\n json: {\n type: fieldName,\n message0: '%1',\n inputsInline: true,\n output: output,\n colour: categoryInfo.color1,\n colourSecondary: categoryInfo.color2,\n colourTertiary: categoryInfo.color3,\n outputShape: outputShape,\n args0: [\n {\n name: `field_${fieldName}`,\n type: `field_${fieldName}`\n }\n ]\n }\n };\n }\n\n /**\n * Convert ExtensionBlockMetadata into data ready for scratch-blocks.\n * @param {ExtensionBlockMetadata} blockInfo - the block info to convert\n * @param {CategoryInfo} categoryInfo - the category for this block\n * @returns {ConvertedBlockInfo} - the converted & original block information\n * @private\n */\n _convertForScratchBlocks (blockInfo, categoryInfo) {\n if (blockInfo === '---') {\n return this._convertSeparatorForScratchBlocks(blockInfo);\n }\n\n if (blockInfo.blockType === BlockType.BUTTON) {\n return this._convertButtonForScratchBlocks(blockInfo);\n }\n\n return this._convertBlockForScratchBlocks(blockInfo, categoryInfo);\n }\n\n /**\n * Convert ExtensionBlockMetadata into scratch-blocks JSON & XML, and generate a proxy function.\n * @param {ExtensionBlockMetadata} blockInfo - the block to convert\n * @param {CategoryInfo} categoryInfo - the category for this block\n * @returns {ConvertedBlockInfo} - the converted & original block information\n * @private\n */\n _convertBlockForScratchBlocks (blockInfo, categoryInfo) {\n const extendedOpcode = `${categoryInfo.id}_${blockInfo.opcode}`;\n\n const blockJSON = {\n type: extendedOpcode,\n inputsInline: true,\n category: categoryInfo.name,\n colour: categoryInfo.color1,\n colourSecondary: categoryInfo.color2,\n colourTertiary: categoryInfo.color3\n };\n const context = {\n // TODO: store this somewhere so that we can map args appropriately after translation.\n // This maps an arg name to its relative position in the original (usually English) block text.\n // When displaying a block in another language we'll need to run a `replace` action similar to the one\n // below, but each `[ARG]` will need to be replaced with the number in this map.\n argsMap: {},\n blockJSON,\n categoryInfo,\n blockInfo,\n inputList: []\n };\n\n // If an icon for the extension exists, prepend it to each block, with a vertical separator.\n // We can overspecify an icon for each block, but if no icon exists on a block, fall back to\n // the category block icon.\n const iconURI = blockInfo.blockIconURI || categoryInfo.blockIconURI;\n\n if (iconURI) {\n blockJSON.extensions = ['scratch_extension'];\n blockJSON.message0 = '%1 %2';\n const iconJSON = {\n type: 'field_image',\n src: iconURI,\n width: 40,\n height: 40\n };\n const separatorJSON = {\n type: 'field_vertical_separator'\n };\n blockJSON.args0 = [\n iconJSON,\n separatorJSON\n ];\n }\n\n switch (blockInfo.blockType) {\n case BlockType.COMMAND:\n blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;\n blockJSON.previousStatement = null; // null = available connection; undefined = hat\n if (!blockInfo.isTerminal) {\n blockJSON.nextStatement = null; // null = available connection; undefined = terminal\n }\n break;\n case BlockType.REPORTER:\n blockJSON.output = 'String'; // TODO: distinguish number & string here?\n blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_ROUND;\n break;\n case BlockType.BOOLEAN:\n blockJSON.output = 'Boolean';\n blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL;\n break;\n case BlockType.HAT:\n case BlockType.EVENT:\n if (!blockInfo.hasOwnProperty('isEdgeActivated')) {\n // if absent, this property defaults to true\n blockInfo.isEdgeActivated = true;\n }\n blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;\n blockJSON.nextStatement = null; // null = available connection; undefined = terminal\n break;\n case BlockType.CONDITIONAL:\n case BlockType.LOOP:\n blockInfo.branchCount = blockInfo.branchCount || 1;\n blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;\n blockJSON.previousStatement = null; // null = available connection; undefined = hat\n if (!blockInfo.isTerminal) {\n blockJSON.nextStatement = null; // null = available connection; undefined = terminal\n }\n break;\n }\n\n const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];\n let inTextNum = 0; // text for the next block \"arm\" is blockText[inTextNum]\n let inBranchNum = 0; // how many branches have we placed into the JSON so far?\n let outLineNum = 0; // used for scratch-blocks `message${outLineNum}` and `args${outLineNum}`\n const convertPlaceholders = this._convertPlaceholders.bind(this, context);\n const extensionMessageContext = this.makeMessageContextForTarget();\n\n // alternate between a block \"arm\" with text on it and an open slot for a substack\n while (inTextNum < blockText.length || inBranchNum < blockInfo.branchCount) {\n if (inTextNum < blockText.length) {\n context.outLineNum = outLineNum;\n const lineText = maybeFormatMessage(blockText[inTextNum], extensionMessageContext);\n const convertedText = lineText.replace(/\\[(.+?)]/g, convertPlaceholders);\n if (blockJSON[`message${outLineNum}`]) {\n blockJSON[`message${outLineNum}`] += convertedText;\n } else {\n blockJSON[`message${outLineNum}`] = convertedText;\n }\n ++inTextNum;\n ++outLineNum;\n }\n if (inBranchNum < blockInfo.branchCount) {\n blockJSON[`message${outLineNum}`] = '%1';\n blockJSON[`args${outLineNum}`] = [{\n type: 'input_statement',\n name: `SUBSTACK${inBranchNum > 0 ? inBranchNum + 1 : ''}`\n }];\n ++inBranchNum;\n ++outLineNum;\n }\n }\n\n if (blockInfo.blockType === BlockType.REPORTER) {\n if (!blockInfo.disableMonitor && context.inputList.length === 0) {\n blockJSON.checkboxInFlyout = true;\n }\n } else if (blockInfo.blockType === BlockType.LOOP) {\n // Add icon to the bottom right of a loop block\n blockJSON[`lastDummyAlign${outLineNum}`] = 'RIGHT';\n blockJSON[`message${outLineNum}`] = '%1';\n blockJSON[`args${outLineNum}`] = [{\n type: 'field_image',\n src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable?\n width: 24,\n height: 24,\n alt: '*', // TODO remove this since we don't use collapsed blocks in scratch\n flip_rtl: true\n }];\n ++outLineNum;\n }\n\n const mutation = blockInfo.isDynamic ? `` : '';\n const inputs = context.inputList.join('');\n const blockXML = `${mutation}${inputs}`;\n\n return {\n info: context.blockInfo,\n json: context.blockJSON,\n xml: blockXML\n };\n }\n\n /**\n * Generate a separator between blocks categories or sub-categories.\n * @param {ExtensionBlockMetadata} blockInfo - the block to convert\n * @param {CategoryInfo} categoryInfo - the category for this block\n * @returns {ConvertedBlockInfo} - the converted & original block information\n * @private\n */\n _convertSeparatorForScratchBlocks (blockInfo) {\n return {\n info: blockInfo,\n xml: ''\n };\n }\n\n /**\n * Convert a button for scratch-blocks. A button has no opcode but specifies a callback name in the `func` field.\n * @param {ExtensionBlockMetadata} buttonInfo - the button to convert\n * @property {string} func - the callback name\n * @param {CategoryInfo} categoryInfo - the category for this button\n * @returns {ConvertedBlockInfo} - the converted & original button information\n * @private\n */\n _convertButtonForScratchBlocks (buttonInfo) {\n // for now we only support these pre-defined callbacks handled in scratch-blocks\n const supportedCallbackKeys = ['MAKE_A_LIST', 'MAKE_A_PROCEDURE', 'MAKE_A_VARIABLE'];\n if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) {\n log.error(`Custom button callbacks not supported yet: ${buttonInfo.func}`);\n }\n\n const extensionMessageContext = this.makeMessageContextForTarget();\n const buttonText = maybeFormatMessage(buttonInfo.text, extensionMessageContext);\n return {\n info: buttonInfo,\n xml: ``\n };\n }\n\n /**\n * Helper for _convertPlaceholdes which handles inline images which are a specialized case of block \"arguments\".\n * @param {object} argInfo Metadata about the inline image as specified by the extension\n * @return {object} JSON blob for a scratch-blocks image field.\n * @private\n */\n _constructInlineImageJson (argInfo) {\n if (!argInfo.dataURI) {\n log.warn('Missing data URI in extension block with argument type IMAGE');\n }\n return {\n type: 'field_image',\n src: argInfo.dataURI || '',\n // TODO these probably shouldn't be hardcoded...?\n width: 24,\n height: 24,\n // Whether or not the inline image should be flipped horizontally\n // in RTL languages. Defaults to false, indicating that the\n // image will not be flipped.\n flip_rtl: argInfo.flipRTL || false\n };\n }\n\n /**\n * Helper for _convertForScratchBlocks which handles linearization of argument placeholders. Called as a callback\n * from string#replace. In addition to the return value the JSON and XML items in the context will be filled.\n * @param {object} context - information shared with _convertForScratchBlocks about the block, etc.\n * @param {string} match - the overall string matched by the placeholder regex, including brackets: '[FOO]'.\n * @param {string} placeholder - the name of the placeholder being matched: 'FOO'.\n * @return {string} scratch-blocks placeholder for the argument: '%1'.\n * @private\n */\n _convertPlaceholders (context, match, placeholder) {\n // Sanitize the placeholder to ensure valid XML\n placeholder = placeholder.replace(/[<\"&]/, '_');\n\n // Determine whether the argument type is one of the known standard field types\n const argInfo = context.blockInfo.arguments[placeholder] || {};\n let argTypeInfo = ArgumentTypeMap[argInfo.type] || {};\n\n // Field type not a standard field type, see if extension has registered custom field type\n if (!ArgumentTypeMap[argInfo.type] && context.categoryInfo.customFieldTypes[argInfo.type]) {\n argTypeInfo = context.categoryInfo.customFieldTypes[argInfo.type].argumentTypeInfo;\n }\n\n // Start to construct the scratch-blocks style JSON defining how the block should be\n // laid out\n let argJSON;\n\n // Most field types are inputs (slots on the block that can have other blocks plugged into them)\n // check if this is not one of those cases. E.g. an inline image on a block.\n if (argTypeInfo.fieldType === 'field_image') {\n argJSON = this._constructInlineImageJson(argInfo);\n } else {\n // Construct input value\n\n // Layout a block argument (e.g. an input slot on the block)\n argJSON = {\n type: 'input_value',\n name: placeholder\n };\n\n const defaultValue =\n typeof argInfo.defaultValue === 'undefined' ? '' :\n xmlEscape(maybeFormatMessage(argInfo.defaultValue, this.makeMessageContextForTarget()).toString());\n\n if (argTypeInfo.check) {\n // Right now the only type of 'check' we have specifies that the\n // input slot on the block accepts Boolean reporters, so it should be\n // shaped like a hexagon\n argJSON.check = argTypeInfo.check;\n }\n\n let valueName;\n let shadowType;\n let fieldName;\n if (argInfo.menu) {\n const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];\n if (menuInfo.acceptReporters) {\n valueName = placeholder;\n shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);\n fieldName = argInfo.menu;\n } else {\n argJSON.type = 'field_dropdown';\n argJSON.options = this._convertMenuItems(menuInfo.items);\n valueName = null;\n shadowType = null;\n fieldName = placeholder;\n }\n } else {\n valueName = placeholder;\n shadowType = (argTypeInfo.shadow && argTypeInfo.shadow.type) || null;\n fieldName = (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || null;\n }\n\n // is the ScratchBlocks name for a block input.\n if (valueName) {\n context.inputList.push(``);\n }\n\n // The is a placeholder for a reporter and is visible when there's no reporter in this input.\n // Boolean inputs don't need to specify a shadow in the XML.\n if (shadowType) {\n context.inputList.push(``);\n }\n\n // A displays a dynamic value: a user-editable text field, a drop-down menu, etc.\n // Leave out the field if defaultValue or fieldName are not specified\n if (defaultValue && fieldName) {\n context.inputList.push(`${defaultValue}`);\n }\n\n if (shadowType) {\n context.inputList.push('');\n }\n\n if (valueName) {\n context.inputList.push('');\n }\n }\n\n const argsName = `args${context.outLineNum}`;\n const blockArgs = (context.blockJSON[argsName] = context.blockJSON[argsName] || []);\n if (argJSON) blockArgs.push(argJSON);\n const argNum = blockArgs.length;\n context.argsMap[placeholder] = argNum;\n\n return `%${argNum}`;\n }\n\n /**\n * @returns {Array.} scratch-blocks XML for each category of extension blocks, in category order.\n * @param {?Target} [target] - the active editing target (optional)\n * @property {string} id - the category / extension ID\n * @property {string} xml - the XML text for this category, starting with `` and ending with ``\n */\n getBlocksXML (target) {\n return this._blockInfo.map(categoryInfo => {\n const {name, color1, color2} = categoryInfo;\n // Filter out blocks that aren't supposed to be shown on this target, as determined by the block info's\n // `hideFromPalette` and `filter` properties.\n const paletteBlocks = categoryInfo.blocks.filter(block => {\n let blockFilterIncludesTarget = true;\n // If an editing target is not passed, include all blocks\n // If the block info doesn't include a `filter` property, always include it\n if (target && block.info.filter) {\n blockFilterIncludesTarget = block.info.filter.includes(\n target.isStage ? TargetType.STAGE : TargetType.SPRITE\n );\n }\n // If the block info's `hideFromPalette` is true, then filter out this block\n return blockFilterIncludesTarget && !block.info.hideFromPalette;\n });\n\n const colorXML = `colour=\"${color1}\" secondaryColour=\"${color2}\"`;\n\n // Use a menu icon if there is one. Otherwise, use the block icon. If there's no icon,\n // the category menu will show its default colored circle.\n let menuIconURI = '';\n if (categoryInfo.menuIconURI) {\n menuIconURI = categoryInfo.menuIconURI;\n } else if (categoryInfo.blockIconURI) {\n menuIconURI = categoryInfo.blockIconURI;\n }\n const menuIconXML = menuIconURI ?\n `iconURI=\"${menuIconURI}\"` : '';\n\n let statusButtonXML = '';\n if (categoryInfo.showStatusButton) {\n statusButtonXML = 'showStatusButton=\"true\"';\n }\n\n return {\n id: categoryInfo.id,\n xml: `${\n paletteBlocks.map(block => block.xml).join('')}`\n };\n });\n }\n\n /**\n * @returns {Array.} - an array containing the scratch-blocks JSON information for each dynamic block.\n */\n getBlocksJSON () {\n return this._blockInfo.reduce(\n (result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []);\n }\n\n /**\n * Get a scratch link socket.\n * @param {string} type Either BLE or BT\n * @returns {ScratchLinkSocket} The scratch link socket.\n */\n getScratchLinkSocket (type) {\n const factory = this._linkSocketFactory || this._defaultScratchLinkSocketFactory;\n return factory(type);\n }\n\n /**\n * Configure how ScratchLink sockets are created. Factory must consume a \"type\" parameter\n * either BT or BLE.\n * @param {Function} factory The new factory for creating ScratchLink sockets.\n */\n configureScratchLinkSocketFactory (factory) {\n this._linkSocketFactory = factory;\n }\n\n /**\n * The default scratch link socket creator, using websockets to the installed device manager.\n * @param {string} type Either BLE or BT\n * @returns {ScratchLinkSocket} The new scratch link socket (a WebSocket object)\n */\n _defaultScratchLinkSocketFactory (type) {\n return new ScratchLinkWebSocket(type);\n }\n\n /**\n * Register an extension that communications with a hardware peripheral by id,\n * to have access to it and its peripheral functions in the future.\n * @param {string} extensionId - the id of the extension.\n * @param {object} extension - the extension to register.\n */\n registerPeripheralExtension (extensionId, extension) {\n this.peripheralExtensions[extensionId] = extension;\n }\n\n /**\n * Tell the specified extension to scan for a peripheral.\n * @param {string} extensionId - the id of the extension.\n */\n scanForPeripheral (extensionId) {\n if (this.peripheralExtensions[extensionId]) {\n this.peripheralExtensions[extensionId].scan();\n }\n }\n\n /**\n * Connect to the extension's specified peripheral.\n * @param {string} extensionId - the id of the extension.\n * @param {number} peripheralId - the id of the peripheral.\n */\n connectPeripheral (extensionId, peripheralId) {\n if (this.peripheralExtensions[extensionId]) {\n this.peripheralExtensions[extensionId].connect(peripheralId);\n }\n }\n\n /**\n * Disconnect from the extension's connected peripheral.\n * @param {string} extensionId - the id of the extension.\n */\n disconnectPeripheral (extensionId) {\n if (this.peripheralExtensions[extensionId]) {\n this.peripheralExtensions[extensionId].disconnect();\n }\n }\n\n /**\n * Returns whether the extension has a currently connected peripheral.\n * @param {string} extensionId - the id of the extension.\n * @return {boolean} - whether the extension has a connected peripheral.\n */\n getPeripheralIsConnected (extensionId) {\n let isConnected = false;\n if (this.peripheralExtensions[extensionId]) {\n isConnected = this.peripheralExtensions[extensionId].isConnected();\n }\n return isConnected;\n }\n\n /**\n * Emit an event to indicate that the microphone is being used to stream audio.\n * @param {boolean} listening - true if the microphone is currently listening.\n */\n emitMicListening (listening) {\n this.emit(Runtime.MIC_LISTENING, listening);\n }\n\n /**\n * Retrieve the function associated with the given opcode.\n * @param {!string} opcode The opcode to look up.\n * @return {Function} The function which implements the opcode.\n */\n getOpcodeFunction (opcode) {\n return this._primitives[opcode];\n }\n\n /**\n * Return whether an opcode represents a hat block.\n * @param {!string} opcode The opcode to look up.\n * @return {boolean} True if the op is known to be a hat.\n */\n getIsHat (opcode) {\n return this._hats.hasOwnProperty(opcode);\n }\n\n /**\n * Return whether an opcode represents an edge-activated hat block.\n * @param {!string} opcode The opcode to look up.\n * @return {boolean} True if the op is known to be a edge-activated hat.\n */\n getIsEdgeActivatedHat (opcode) {\n return this._hats.hasOwnProperty(opcode) &&\n this._hats[opcode].edgeActivated;\n }\n\n\n /**\n * Attach the audio engine\n * @param {!AudioEngine} audioEngine The audio engine to attach\n */\n attachAudioEngine (audioEngine) {\n this.audioEngine = audioEngine;\n }\n\n /**\n * Attach the renderer\n * @param {!RenderWebGL} renderer The renderer to attach\n */\n attachRenderer (renderer) {\n this.renderer = renderer;\n this.renderer.setLayerGroupOrdering(StageLayering.LAYER_GROUPS);\n }\n\n /**\n * Set the svg adapter, which converts scratch 2 svgs to scratch 3 svgs\n * @param {!SvgRenderer} svgAdapter The adapter to attach\n */\n attachV2SVGAdapter (svgAdapter) {\n this.v2SvgAdapter = svgAdapter;\n }\n\n /**\n * Set the bitmap adapter for the VM/runtime, which converts scratch 2\n * bitmaps to scratch 3 bitmaps. (Scratch 3 bitmaps are all bitmap resolution 2)\n * @param {!function} bitmapAdapter The adapter to attach\n */\n attachV2BitmapAdapter (bitmapAdapter) {\n this.v2BitmapAdapter = bitmapAdapter;\n }\n\n /**\n * Attach the storage module\n * @param {!ScratchStorage} storage The storage module to attach\n */\n attachStorage (storage) {\n this.storage = storage;\n }\n\n // -----------------------------------------------------------------------------\n // -----------------------------------------------------------------------------\n\n /**\n * Create a thread and push it to the list of threads.\n * @param {!string} id ID of block that starts the stack.\n * @param {!Target} target Target to run thread on.\n * @param {?object} opts optional arguments\n * @param {?boolean} opts.stackClick true if the script was activated by clicking on the stack\n * @param {?boolean} opts.updateMonitor true if the script should update a monitor value\n * @return {!Thread} The newly created thread.\n */\n _pushThread (id, target, opts) {\n const thread = new Thread(id);\n thread.target = target;\n thread.stackClick = Boolean(opts && opts.stackClick);\n thread.updateMonitor = Boolean(opts && opts.updateMonitor);\n thread.blockContainer = thread.updateMonitor ?\n this.monitorBlocks :\n target.blocks;\n\n thread.pushStack(id);\n this.threads.push(thread);\n return thread;\n }\n\n /**\n * Stop a thread: stop running it immediately, and remove it from the thread list later.\n * @param {!Thread} thread Thread object to remove from actives\n */\n _stopThread (thread) {\n // Mark the thread for later removal\n thread.isKilled = true;\n // Inform sequencer to stop executing that thread.\n this.sequencer.retireThread(thread);\n }\n\n /**\n * Restart a thread in place, maintaining its position in the list of threads.\n * This is used by `startHats` to and is necessary to ensure 2.0-like execution order.\n * Test project: https://scratch.mit.edu/projects/130183108/\n * @param {!Thread} thread Thread object to restart.\n * @return {Thread} The restarted thread.\n */\n _restartThread (thread) {\n const newThread = new Thread(thread.topBlock);\n newThread.target = thread.target;\n newThread.stackClick = thread.stackClick;\n newThread.updateMonitor = thread.updateMonitor;\n newThread.blockContainer = thread.blockContainer;\n newThread.pushStack(thread.topBlock);\n const i = this.threads.indexOf(thread);\n if (i > -1) {\n this.threads[i] = newThread;\n return newThread;\n }\n this.threads.push(thread);\n return thread;\n }\n\n /**\n * Return whether a thread is currently active/running.\n * @param {?Thread} thread Thread object to check.\n * @return {boolean} True if the thread is active/running.\n */\n isActiveThread (thread) {\n return (\n (\n thread.stack.length > 0 &&\n thread.status !== Thread.STATUS_DONE) &&\n this.threads.indexOf(thread) > -1);\n }\n\n /**\n * Return whether a thread is waiting for more information or done.\n * @param {?Thread} thread Thread object to check.\n * @return {boolean} True if the thread is waiting\n */\n isWaitingThread (thread) {\n return (\n thread.status === Thread.STATUS_PROMISE_WAIT ||\n thread.status === Thread.STATUS_YIELD_TICK ||\n !this.isActiveThread(thread)\n );\n }\n\n /**\n * Toggle a script.\n * @param {!string} topBlockId ID of block that starts the script.\n * @param {?object} opts optional arguments to toggle script\n * @param {?string} opts.target target ID for target to run script on. If not supplied, uses editing target.\n * @param {?boolean} opts.stackClick true if the user activated the stack by clicking, false if not. This\n * determines whether we show a visual report when turning on the script.\n */\n toggleScript (topBlockId, opts) {\n opts = Object.assign({\n target: this._editingTarget,\n stackClick: false\n }, opts);\n // Remove any existing thread.\n for (let i = 0; i < this.threads.length; i++) {\n // Toggling a script that's already running turns it off\n if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE) {\n const blockContainer = opts.target.blocks;\n const opcode = blockContainer.getOpcode(blockContainer.getBlock(topBlockId));\n\n if (this.getIsEdgeActivatedHat(opcode) && this.threads[i].stackClick !== opts.stackClick) {\n // Allow edge activated hat thread stack click to coexist with\n // edge activated hat thread that runs every frame\n continue;\n }\n this._stopThread(this.threads[i]);\n return;\n }\n }\n // Otherwise add it.\n this._pushThread(topBlockId, opts.target, opts);\n }\n\n /**\n * Enqueue a script that when finished will update the monitor for the block.\n * @param {!string} topBlockId ID of block that starts the script.\n * @param {?Target} optTarget target Target to run script on. If not supplied, uses editing target.\n */\n addMonitorScript (topBlockId, optTarget) {\n if (!optTarget) optTarget = this._editingTarget;\n for (let i = 0; i < this.threads.length; i++) {\n // Don't re-add the script if it's already running\n if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE &&\n this.threads[i].updateMonitor) {\n return;\n }\n }\n // Otherwise add it.\n this._pushThread(topBlockId, optTarget, {updateMonitor: true});\n }\n\n /**\n * Run a function `f` for all scripts in a workspace.\n * `f` will be called with two parameters:\n * - the top block ID of the script.\n * - the target that owns the script.\n * @param {!Function} f Function to call for each script.\n * @param {Target=} optTarget Optionally, a target to restrict to.\n */\n allScriptsDo (f, optTarget) {\n let targets = this.executableTargets;\n if (optTarget) {\n targets = [optTarget];\n }\n for (let t = targets.length - 1; t >= 0; t--) {\n const target = targets[t];\n const scripts = target.blocks.getScripts();\n for (let j = 0; j < scripts.length; j++) {\n const topBlockId = scripts[j];\n f(topBlockId, target);\n }\n }\n }\n\n allScriptsByOpcodeDo (opcode, f, optTarget) {\n let targets = this.executableTargets;\n if (optTarget) {\n targets = [optTarget];\n }\n for (let t = targets.length - 1; t >= 0; t--) {\n const target = targets[t];\n const scripts = BlocksRuntimeCache.getScripts(target.blocks, opcode);\n for (let j = 0; j < scripts.length; j++) {\n f(scripts[j], target);\n }\n }\n }\n\n /**\n * Start all relevant hats.\n * @param {!string} requestedHatOpcode Opcode of hats to start.\n * @param {object=} optMatchFields Optionally, fields to match on the hat.\n * @param {Target=} optTarget Optionally, a target to restrict to.\n * @return {Array.} List of threads started by this function.\n */\n startHats (requestedHatOpcode,\n optMatchFields, optTarget) {\n if (!this._hats.hasOwnProperty(requestedHatOpcode)) {\n // No known hat with this opcode.\n return;\n }\n const instance = this;\n const newThreads = [];\n // Look up metadata for the relevant hat.\n const hatMeta = instance._hats[requestedHatOpcode];\n\n for (const opts in optMatchFields) {\n if (!optMatchFields.hasOwnProperty(opts)) continue;\n optMatchFields[opts] = optMatchFields[opts].toUpperCase();\n }\n\n // Consider all scripts, looking for hats with opcode `requestedHatOpcode`.\n this.allScriptsByOpcodeDo(requestedHatOpcode, (script, target) => {\n const {\n blockId: topBlockId,\n fieldsOfInputs: hatFields\n } = script;\n\n // Match any requested fields.\n // For example: ensures that broadcasts match.\n // This needs to happen before the block is evaluated\n // (i.e., before the predicate can be run) because \"broadcast and wait\"\n // needs to have a precise collection of started threads.\n for (const matchField in optMatchFields) {\n if (hatFields[matchField].value !== optMatchFields[matchField]) {\n // Field mismatch.\n return;\n }\n }\n\n if (hatMeta.restartExistingThreads) {\n // If `restartExistingThreads` is true, we should stop\n // any existing threads starting with the top block.\n for (let i = 0; i < this.threads.length; i++) {\n if (this.threads[i].target === target &&\n this.threads[i].topBlock === topBlockId &&\n // stack click threads and hat threads can coexist\n !this.threads[i].stackClick) {\n newThreads.push(this._restartThread(this.threads[i]));\n return;\n }\n }\n } else {\n // If `restartExistingThreads` is false, we should\n // give up if any threads with the top block are running.\n for (let j = 0; j < this.threads.length; j++) {\n if (this.threads[j].target === target &&\n this.threads[j].topBlock === topBlockId &&\n // stack click threads and hat threads can coexist\n !this.threads[j].stackClick &&\n this.threads[j].status !== Thread.STATUS_DONE) {\n // Some thread is already running.\n return;\n }\n }\n }\n // Start the thread with this top block.\n newThreads.push(this._pushThread(topBlockId, target));\n }, optTarget);\n // For compatibility with Scratch 2, edge triggered hats need to be processed before\n // threads are stepped. See ScratchRuntime.as for original implementation\n newThreads.forEach(thread => {\n execute(this.sequencer, thread);\n thread.goToNextBlock();\n });\n return newThreads;\n }\n\n\n /**\n * Dispose all targets. Return to clean state.\n */\n dispose () {\n this.stopAll();\n // Deleting each target's variable's monitors.\n this.targets.forEach(target => {\n if (target.isOriginal) target.deleteMonitors();\n });\n\n this.targets.map(this.disposeTarget, this);\n this._monitorState = OrderedMap({});\n this.emit(Runtime.RUNTIME_DISPOSED);\n this.ioDevices.clock.resetProjectTimer();\n // @todo clear out extensions? turboMode? etc.\n\n // *********** Cloud *******************\n\n // If the runtime currently has cloud data,\n // emit a has cloud data update event resetting\n // it to false\n if (this.hasCloudData()) {\n this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, false);\n }\n\n this.ioDevices.cloud.clear();\n\n // Reset runtime cloud data info\n const newCloudDataManager = cloudDataManager();\n this.hasCloudData = newCloudDataManager.hasCloudVariables;\n this.canAddCloudVariable = newCloudDataManager.canAddCloudVariable;\n this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager);\n this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager);\n }\n\n /**\n * Add a target to the runtime. This tracks the sprite pane\n * ordering of the target. The target still needs to be put\n * into the correct execution order after calling this function.\n * @param {Target} target target to add\n */\n addTarget (target) {\n this.targets.push(target);\n this.executableTargets.push(target);\n }\n\n /**\n * Move a target in the execution order by a relative amount.\n *\n * A positve number will make the target execute earlier. A negative number\n * will make the target execute later in the order.\n *\n * @param {Target} executableTarget target to move\n * @param {number} delta number of positions to move target by\n * @returns {number} new position in execution order\n */\n moveExecutable (executableTarget, delta) {\n const oldIndex = this.executableTargets.indexOf(executableTarget);\n this.executableTargets.splice(oldIndex, 1);\n let newIndex = oldIndex + delta;\n if (newIndex > this.executableTargets.length) {\n newIndex = this.executableTargets.length;\n }\n if (newIndex <= 0) {\n if (this.executableTargets.length > 0 && this.executableTargets[0].isStage) {\n newIndex = 1;\n } else {\n newIndex = 0;\n }\n }\n this.executableTargets.splice(newIndex, 0, executableTarget);\n return newIndex;\n }\n\n /**\n * Set a target to execute at a specific position in the execution order.\n *\n * Infinity will set the target to execute first. 0 will set the target to\n * execute last (before the stage).\n *\n * @param {Target} executableTarget target to move\n * @param {number} newIndex position in execution order to place the target\n * @returns {number} new position in the execution order\n */\n setExecutablePosition (executableTarget, newIndex) {\n const oldIndex = this.executableTargets.indexOf(executableTarget);\n return this.moveExecutable(executableTarget, newIndex - oldIndex);\n }\n\n /**\n * Remove a target from the execution set.\n * @param {Target} executableTarget target to remove\n */\n removeExecutable (executableTarget) {\n const oldIndex = this.executableTargets.indexOf(executableTarget);\n if (oldIndex > -1) {\n this.executableTargets.splice(oldIndex, 1);\n }\n }\n\n /**\n * Dispose of a target.\n * @param {!Target} disposingTarget Target to dispose of.\n */\n disposeTarget (disposingTarget) {\n this.targets = this.targets.filter(target => {\n if (disposingTarget !== target) return true;\n // Allow target to do dispose actions.\n target.dispose();\n // Remove from list of targets.\n return false;\n });\n }\n\n /**\n * Stop any threads acting on the target.\n * @param {!Target} target Target to stop threads for.\n * @param {Thread=} optThreadException Optional thread to skip.\n */\n stopForTarget (target, optThreadException) {\n // Emit stop event to allow blocks to clean up any state.\n this.emit(Runtime.STOP_FOR_TARGET, target, optThreadException);\n\n // Stop any threads on the target.\n for (let i = 0; i < this.threads.length; i++) {\n if (this.threads[i] === optThreadException) {\n continue;\n }\n if (this.threads[i].target === target) {\n this._stopThread(this.threads[i]);\n }\n }\n }\n\n /**\n * Start all threads that start with the green flag.\n */\n greenFlag () {\n this.stopAll();\n this.emit(Runtime.PROJECT_START);\n this.ioDevices.clock.resetProjectTimer();\n this.targets.forEach(target => target.clearEdgeActivatedValues());\n // Inform all targets of the green flag.\n for (let i = 0; i < this.targets.length; i++) {\n this.targets[i].onGreenFlag();\n }\n this.startHats('event_whenflagclicked');\n }\n\n /**\n * Stop \"everything.\"\n */\n stopAll () {\n // Emit stop event to allow blocks to clean up any state.\n this.emit(Runtime.PROJECT_STOP_ALL);\n\n // Dispose all clones.\n const newTargets = [];\n for (let i = 0; i < this.targets.length; i++) {\n this.targets[i].onStopAll();\n if (this.targets[i].hasOwnProperty('isOriginal') &&\n !this.targets[i].isOriginal) {\n this.targets[i].dispose();\n } else {\n newTargets.push(this.targets[i]);\n }\n }\n this.targets = newTargets;\n // Dispose of the active thread.\n if (this.sequencer.activeThread !== null) {\n this._stopThread(this.sequencer.activeThread);\n }\n // Remove all remaining threads from executing in the next tick.\n this.threads = [];\n }\n\n /**\n * Repeatedly run `sequencer.stepThreads` and filter out\n * inactive threads after each iteration.\n */\n _step () {\n if (this.profiler !== null) {\n if (stepProfilerId === -1) {\n stepProfilerId = this.profiler.idByName('Runtime._step');\n }\n this.profiler.start(stepProfilerId);\n }\n\n // Clean up threads that were told to stop during or since the last step\n this.threads = this.threads.filter(thread => !thread.isKilled);\n\n // Find all edge-activated hats, and add them to threads to be evaluated.\n for (const hatType in this._hats) {\n if (!this._hats.hasOwnProperty(hatType)) continue;\n const hat = this._hats[hatType];\n if (hat.edgeActivated) {\n this.startHats(hatType);\n }\n }\n this.redrawRequested = false;\n this._pushMonitors();\n if (this.profiler !== null) {\n if (stepThreadsProfilerId === -1) {\n stepThreadsProfilerId = this.profiler.idByName('Sequencer.stepThreads');\n }\n this.profiler.start(stepThreadsProfilerId);\n }\n const doneThreads = this.sequencer.stepThreads();\n if (this.profiler !== null) {\n this.profiler.stop();\n }\n this._updateGlows(doneThreads);\n // Add done threads so that even if a thread finishes within 1 frame, the green\n // flag will still indicate that a script ran.\n this._emitProjectRunStatus(\n this.threads.length + doneThreads.length -\n this._getMonitorThreadCount([...this.threads, ...doneThreads]));\n // Store threads that completed this iteration for testing and other\n // internal purposes.\n this._lastStepDoneThreads = doneThreads;\n if (this.renderer) {\n // @todo: Only render when this.redrawRequested or clones rendered.\n if (this.profiler !== null) {\n if (rendererDrawProfilerId === -1) {\n rendererDrawProfilerId = this.profiler.idByName('RenderWebGL.draw');\n }\n this.profiler.start(rendererDrawProfilerId);\n }\n this.renderer.draw();\n if (this.profiler !== null) {\n this.profiler.stop();\n }\n }\n\n if (this._refreshTargets) {\n this.emit(Runtime.TARGETS_UPDATE, false /* Don't emit project changed */);\n this._refreshTargets = false;\n }\n\n if (!this._prevMonitorState.equals(this._monitorState)) {\n this.emit(Runtime.MONITORS_UPDATE, this._monitorState);\n this._prevMonitorState = this._monitorState;\n }\n\n if (this.profiler !== null) {\n this.profiler.stop();\n this.profiler.reportFrames();\n }\n }\n\n /**\n * Get the number of threads in the given array that are monitor threads (threads\n * that update monitor values, and don't count as running a script).\n * @param {!Array.} threads The set of threads to look through.\n * @return {number} The number of monitor threads in threads.\n */\n _getMonitorThreadCount (threads) {\n let count = 0;\n threads.forEach(thread => {\n if (thread.updateMonitor) count++;\n });\n return count;\n }\n\n /**\n * Queue monitor blocks to sequencer to be run.\n */\n _pushMonitors () {\n this.monitorBlocks.runAllMonitored(this);\n }\n\n /**\n * Set the current editing target known by the runtime.\n * @param {!Target} editingTarget New editing target.\n */\n setEditingTarget (editingTarget) {\n const oldEditingTarget = this._editingTarget;\n this._editingTarget = editingTarget;\n // Script glows must be cleared.\n this._scriptGlowsPreviousFrame = [];\n this._updateGlows();\n\n if (oldEditingTarget !== this._editingTarget) {\n this.requestToolboxExtensionsUpdate();\n }\n }\n\n /**\n * Set whether we are in 30 TPS compatibility mode.\n * @param {boolean} compatibilityModeOn True iff in compatibility mode.\n */\n setCompatibilityMode (compatibilityModeOn) {\n this.compatibilityMode = compatibilityModeOn;\n if (this._steppingInterval) {\n clearInterval(this._steppingInterval);\n this._steppingInterval = null;\n this.start();\n }\n }\n\n /**\n * Emit glows/glow clears for scripts after a single tick.\n * Looks at `this.threads` and notices which have turned on/off new glows.\n * @param {Array.=} optExtraThreads Optional list of inactive threads.\n */\n _updateGlows (optExtraThreads) {\n const searchThreads = [];\n searchThreads.push.apply(searchThreads, this.threads);\n if (optExtraThreads) {\n searchThreads.push.apply(searchThreads, optExtraThreads);\n }\n // Set of scripts that request a glow this frame.\n const requestedGlowsThisFrame = [];\n // Final set of scripts glowing during this frame.\n const finalScriptGlows = [];\n // Find all scripts that should be glowing.\n for (let i = 0; i < searchThreads.length; i++) {\n const thread = searchThreads[i];\n const target = thread.target;\n if (target === this._editingTarget) {\n const blockForThread = thread.blockGlowInFrame;\n if (thread.requestScriptGlowInFrame || thread.stackClick) {\n let script = target.blocks.getTopLevelScript(blockForThread);\n if (!script) {\n // Attempt to find in flyout blocks.\n script = this.flyoutBlocks.getTopLevelScript(\n blockForThread\n );\n }\n if (script) {\n requestedGlowsThisFrame.push(script);\n }\n }\n }\n }\n // Compare to previous frame.\n for (let j = 0; j < this._scriptGlowsPreviousFrame.length; j++) {\n const previousFrameGlow = this._scriptGlowsPreviousFrame[j];\n if (requestedGlowsThisFrame.indexOf(previousFrameGlow) < 0) {\n // Glow turned off.\n this.glowScript(previousFrameGlow, false);\n } else {\n // Still glowing.\n finalScriptGlows.push(previousFrameGlow);\n }\n }\n for (let k = 0; k < requestedGlowsThisFrame.length; k++) {\n const currentFrameGlow = requestedGlowsThisFrame[k];\n if (this._scriptGlowsPreviousFrame.indexOf(currentFrameGlow) < 0) {\n // Glow turned on.\n this.glowScript(currentFrameGlow, true);\n finalScriptGlows.push(currentFrameGlow);\n }\n }\n this._scriptGlowsPreviousFrame = finalScriptGlows;\n }\n\n /**\n * Emit run start/stop after each tick. Emits when `this.threads.length` goes\n * between non-zero and zero\n *\n * @param {number} nonMonitorThreadCount The new nonMonitorThreadCount\n */\n _emitProjectRunStatus (nonMonitorThreadCount) {\n if (this._nonMonitorThreadCount === 0 && nonMonitorThreadCount > 0) {\n this.emit(Runtime.PROJECT_RUN_START);\n }\n if (this._nonMonitorThreadCount > 0 && nonMonitorThreadCount === 0) {\n this.emit(Runtime.PROJECT_RUN_STOP);\n }\n this._nonMonitorThreadCount = nonMonitorThreadCount;\n }\n\n /**\n * \"Quiet\" a script's glow: stop the VM from generating glow/unglow events\n * about that script. Use when a script has just been deleted, but we may\n * still be tracking glow data about it.\n * @param {!string} scriptBlockId Id of top-level block in script to quiet.\n */\n quietGlow (scriptBlockId) {\n const index = this._scriptGlowsPreviousFrame.indexOf(scriptBlockId);\n if (index > -1) {\n this._scriptGlowsPreviousFrame.splice(index, 1);\n }\n }\n\n /**\n * Emit feedback for block glowing (used in the sequencer).\n * @param {?string} blockId ID for the block to update glow\n * @param {boolean} isGlowing True to turn on glow; false to turn off.\n */\n glowBlock (blockId, isGlowing) {\n if (isGlowing) {\n this.emit(Runtime.BLOCK_GLOW_ON, {id: blockId});\n } else {\n this.emit(Runtime.BLOCK_GLOW_OFF, {id: blockId});\n }\n }\n\n /**\n * Emit feedback for script glowing.\n * @param {?string} topBlockId ID for the top block to update glow\n * @param {boolean} isGlowing True to turn on glow; false to turn off.\n */\n glowScript (topBlockId, isGlowing) {\n if (isGlowing) {\n this.emit(Runtime.SCRIPT_GLOW_ON, {id: topBlockId});\n } else {\n this.emit(Runtime.SCRIPT_GLOW_OFF, {id: topBlockId});\n }\n }\n\n /**\n * Emit whether blocks are being dragged over gui\n * @param {boolean} areBlocksOverGui True if blocks are dragged out of blocks workspace, false otherwise\n */\n emitBlockDragUpdate (areBlocksOverGui) {\n this.emit(Runtime.BLOCK_DRAG_UPDATE, areBlocksOverGui);\n }\n\n /**\n * Emit event to indicate that the block drag has ended with the blocks outside the blocks workspace\n * @param {Array.} blocks The set of blocks dragged to the GUI\n * @param {string} topBlockId The original id of the top block being dragged\n */\n emitBlockEndDrag (blocks, topBlockId) {\n this.emit(Runtime.BLOCK_DRAG_END, blocks, topBlockId);\n }\n\n /**\n * Emit value for reporter to show in the blocks.\n * @param {string} blockId ID for the block.\n * @param {string} value Value to show associated with the block.\n */\n visualReport (blockId, value) {\n this.emit(Runtime.VISUAL_REPORT, {id: blockId, value: String(value)});\n }\n\n /**\n * Add a monitor to the state. If the monitor already exists in the state,\n * updates those properties that are defined in the given monitor record.\n * @param {!MonitorRecord} monitor Monitor to add.\n */\n requestAddMonitor (monitor) {\n const id = monitor.get('id');\n if (!this.requestUpdateMonitor(monitor)) { // update monitor if it exists in the state\n // if the monitor did not exist in the state, add it\n this._monitorState = this._monitorState.set(id, monitor);\n }\n }\n\n /**\n * Update a monitor in the state and report success/failure of update.\n * @param {!Map} monitor Monitor values to update. Values on the monitor with overwrite\n * values on the old monitor with the same ID. If a value isn't defined on the new monitor,\n * the old monitor will keep its old value.\n * @return {boolean} true if monitor exists in the state and was updated, false if it did not exist.\n */\n requestUpdateMonitor (monitor) {\n const id = monitor.get('id');\n if (this._monitorState.has(id)) {\n this._monitorState =\n // Use mergeWith here to prevent undefined values from overwriting existing ones\n this._monitorState.set(id, this._monitorState.get(id).mergeWith((prev, next) => {\n if (typeof next === 'undefined' || next === null) {\n return prev;\n }\n return next;\n }, monitor));\n return true;\n }\n return false;\n }\n\n /**\n * Removes a monitor from the state. Does nothing if the monitor already does\n * not exist in the state.\n * @param {!string} monitorId ID of the monitor to remove.\n */\n requestRemoveMonitor (monitorId) {\n this._monitorState = this._monitorState.delete(monitorId);\n }\n\n /**\n * Hides a monitor and returns success/failure of action.\n * @param {!string} monitorId ID of the monitor to hide.\n * @return {boolean} true if monitor exists and was updated, false otherwise\n */\n requestHideMonitor (monitorId) {\n return this.requestUpdateMonitor(new Map([\n ['id', monitorId],\n ['visible', false]\n ]));\n }\n\n /**\n * Shows a monitor and returns success/failure of action.\n * not exist in the state.\n * @param {!string} monitorId ID of the monitor to show.\n * @return {boolean} true if monitor exists and was updated, false otherwise\n */\n requestShowMonitor (monitorId) {\n return this.requestUpdateMonitor(new Map([\n ['id', monitorId],\n ['visible', true]\n ]));\n }\n\n /**\n * Removes all monitors with the given target ID from the state. Does nothing if\n * the monitor already does not exist in the state.\n * @param {!string} targetId Remove all monitors with given target ID.\n */\n requestRemoveMonitorByTargetId (targetId) {\n this._monitorState = this._monitorState.filterNot(value => value.targetId === targetId);\n }\n\n /**\n * Get a target by its id.\n * @param {string} targetId Id of target to find.\n * @return {?Target} The target, if found.\n */\n getTargetById (targetId) {\n for (let i = 0; i < this.targets.length; i++) {\n const target = this.targets[i];\n if (target.id === targetId) {\n return target;\n }\n }\n }\n\n /**\n * Get the first original (non-clone-block-created) sprite given a name.\n * @param {string} spriteName Name of sprite to look for.\n * @return {?Target} Target representing a sprite of the given name.\n */\n getSpriteTargetByName (spriteName) {\n for (let i = 0; i < this.targets.length; i++) {\n const target = this.targets[i];\n if (target.isStage) {\n continue;\n }\n if (target.sprite && target.sprite.name === spriteName) {\n return target;\n }\n }\n }\n\n /**\n * Get a target by its drawable id.\n * @param {number} drawableID drawable id of target to find\n * @return {?Target} The target, if found\n */\n getTargetByDrawableId (drawableID) {\n for (let i = 0; i < this.targets.length; i++) {\n const target = this.targets[i];\n if (target.drawableID === drawableID) return target;\n }\n }\n\n /**\n * Update the clone counter to track how many clones are created.\n * @param {number} changeAmount How many clones have been created/destroyed.\n */\n changeCloneCounter (changeAmount) {\n this._cloneCounter += changeAmount;\n }\n\n /**\n * Return whether there are clones available.\n * @return {boolean} True until the number of clones hits Runtime.MAX_CLONES.\n */\n clonesAvailable () {\n return this._cloneCounter < Runtime.MAX_CLONES;\n }\n\n /**\n * Report that the project has loaded in the Virtual Machine.\n */\n emitProjectLoaded () {\n this.emit(Runtime.PROJECT_LOADED);\n }\n\n /**\n * Report that the project has changed in a way that would affect serialization\n */\n emitProjectChanged () {\n this.emit(Runtime.PROJECT_CHANGED);\n }\n\n /**\n * Report that a new target has been created, possibly by cloning an existing target.\n * @param {Target} newTarget - the newly created target.\n * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.\n * @fires Runtime#targetWasCreated\n */\n fireTargetWasCreated (newTarget, sourceTarget) {\n this.emit('targetWasCreated', newTarget, sourceTarget);\n }\n\n /**\n * Report that a clone target is being removed.\n * @param {Target} target - the target being removed\n * @fires Runtime#targetWasRemoved\n */\n fireTargetWasRemoved (target) {\n this.emit('targetWasRemoved', target);\n }\n\n /**\n * Get a target representing the Scratch stage, if one exists.\n * @return {?Target} The target, if found.\n */\n getTargetForStage () {\n for (let i = 0; i < this.targets.length; i++) {\n const target = this.targets[i];\n if (target.isStage) {\n return target;\n }\n }\n }\n\n /**\n * Get the editing target.\n * @return {?Target} The editing target.\n */\n getEditingTarget () {\n return this._editingTarget;\n }\n\n getAllVarNamesOfType (varType) {\n let varNames = [];\n for (const target of this.targets) {\n const targetVarNames = target.getAllVariableNamesInScopeByType(varType, true);\n varNames = varNames.concat(targetVarNames);\n }\n return varNames;\n }\n\n /**\n * Get the label or label function for an opcode\n * @param {string} extendedOpcode - the opcode you want a label for\n * @return {object} - object with label and category\n * @property {string} category - the category for this opcode\n * @property {Function} [labelFn] - function to generate the label for this opcode\n * @property {string} [label] - the label for this opcode if `labelFn` is absent\n */\n getLabelForOpcode (extendedOpcode) {\n const [category, opcode] = StringUtil.splitFirst(extendedOpcode, '_');\n if (!(category && opcode)) return;\n\n const categoryInfo = this._blockInfo.find(ci => ci.id === category);\n if (!categoryInfo) return;\n\n const block = categoryInfo.blocks.find(b => b.info.opcode === opcode);\n if (!block) return;\n\n // TODO: we may want to format the label in a locale-specific way.\n return {\n category: 'extension', // This assumes that all extensions have the same monitor color.\n label: `${categoryInfo.name}: ${block.info.text}`\n };\n }\n\n /**\n * Create a new global variable avoiding conflicts with other variable names.\n * @param {string} variableName The desired variable name for the new global variable.\n * This can be turned into a fresh name as necessary.\n * @param {string} optVarId An optional ID to use for the variable. A new one will be generated\n * if a falsey value for this parameter is provided.\n * @param {string} optVarType The type of the variable to create. Defaults to Variable.SCALAR_TYPE.\n * @return {Variable} The new variable that was created.\n */\n createNewGlobalVariable (variableName, optVarId, optVarType) {\n const varType = (typeof optVarType === 'string') ? optVarType : Variable.SCALAR_TYPE;\n const allVariableNames = this.getAllVarNamesOfType(varType);\n const newName = StringUtil.unusedName(variableName, allVariableNames);\n const variable = new Variable(optVarId || uid(), newName, varType);\n const stage = this.getTargetForStage();\n stage.variables[variable.id] = variable;\n return variable;\n }\n\n /**\n * Tell the runtime to request a redraw.\n * Use after a clone/sprite has completed some visible operation on the stage.\n */\n requestRedraw () {\n this.redrawRequested = true;\n }\n\n /**\n * Emit a targets update at the end of the step if the provided target is\n * the original sprite\n * @param {!Target} target Target requesting the targets update\n */\n requestTargetsUpdate (target) {\n if (!target.isOriginal) return;\n this._refreshTargets = true;\n }\n\n /**\n * Emit an event that indicates that the blocks on the workspace need updating.\n */\n requestBlocksUpdate () {\n this.emit(Runtime.BLOCKS_NEED_UPDATE);\n }\n\n /**\n * Emit an event that indicates that the toolbox extension blocks need updating.\n */\n requestToolboxExtensionsUpdate () {\n this.emit(Runtime.TOOLBOX_EXTENSIONS_NEED_UPDATE);\n }\n\n /**\n * Set up timers to repeatedly step in a browser.\n */\n start () {\n // Do not start if we are already running\n if (this._steppingInterval) return;\n\n let interval = Runtime.THREAD_STEP_INTERVAL;\n if (this.compatibilityMode) {\n interval = Runtime.THREAD_STEP_INTERVAL_COMPATIBILITY;\n }\n this.currentStepTime = interval;\n this._steppingInterval = setInterval(() => {\n this._step();\n }, interval);\n this.emit(Runtime.RUNTIME_STARTED);\n }\n\n /**\n * Turn on profiling.\n * @param {Profiler/FrameCallback} onFrame A callback handle passed a\n * profiling frame when the profiler reports its collected data.\n */\n enableProfiling (onFrame) {\n if (Profiler.available()) {\n this.profiler = new Profiler(onFrame);\n }\n }\n\n /**\n * Turn off profiling.\n */\n disableProfiling () {\n this.profiler = null;\n }\n\n /**\n * Update a millisecond timestamp value that is saved on the Runtime.\n * This value is helpful in certain instances for compatibility with Scratch 2,\n * which sometimes uses a `currentMSecs` timestamp value in Interpreter.as\n */\n updateCurrentMSecs () {\n this.currentMSecs = Date.now();\n }\n}\n\n/**\n * Event fired after a new target has been created, possibly by cloning an existing target.\n *\n * @event Runtime#targetWasCreated\n * @param {Target} newTarget - the newly created target.\n * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.\n */\n\nmodule.exports = Runtime;\n","/**\n * These constants are copied from scratch-blocks/core/constants.js\n * @TODO find a way to require() these straight from scratch-blocks... maybe make a scratch-blocks/dist/constants.js?\n * @readonly\n * @enum {int}\n */\nconst ScratchBlocksConstants = {\n /**\n * ENUM for output shape: hexagonal (booleans/predicates).\n * @const\n */\n OUTPUT_SHAPE_HEXAGONAL: 1,\n\n /**\n * ENUM for output shape: rounded (numbers).\n * @const\n */\n OUTPUT_SHAPE_ROUND: 2,\n\n /**\n * ENUM for output shape: squared (any/all values; strings).\n * @const\n */\n OUTPUT_SHAPE_SQUARE: 3\n};\n\nmodule.exports = ScratchBlocksConstants;\n","const Timer = require('../util/timer');\nconst Thread = require('./thread');\nconst execute = require('./execute.js');\n\n/**\n * Profiler frame name for stepping a single thread.\n * @const {string}\n */\nconst stepThreadProfilerFrame = 'Sequencer.stepThread';\n\n/**\n * Profiler frame name for the inner loop of stepThreads.\n * @const {string}\n */\nconst stepThreadsInnerProfilerFrame = 'Sequencer.stepThreads#inner';\n\n/**\n * Profiler frame name for execute.\n * @const {string}\n */\nconst executeProfilerFrame = 'execute';\n\n/**\n * Profiler frame ID for stepThreadProfilerFrame.\n * @type {number}\n */\nlet stepThreadProfilerId = -1;\n\n/**\n * Profiler frame ID for stepThreadsInnerProfilerFrame.\n * @type {number}\n */\nlet stepThreadsInnerProfilerId = -1;\n\n/**\n * Profiler frame ID for executeProfilerFrame.\n * @type {number}\n */\nlet executeProfilerId = -1;\n\nclass Sequencer {\n constructor (runtime) {\n /**\n * A utility timer for timing thread sequencing.\n * @type {!Timer}\n */\n this.timer = new Timer();\n\n /**\n * Reference to the runtime owning this sequencer.\n * @type {!Runtime}\n */\n this.runtime = runtime;\n\n this.activeThread = null;\n }\n\n /**\n * Time to run a warp-mode thread, in ms.\n * @type {number}\n */\n static get WARP_TIME () {\n return 500;\n }\n\n /**\n * Step through all threads in `this.runtime.threads`, running them in order.\n * @return {Array.} List of inactive threads after stepping.\n */\n stepThreads () {\n // Work time is 75% of the thread stepping interval.\n const WORK_TIME = 0.75 * this.runtime.currentStepTime;\n // For compatibility with Scatch 2, update the millisecond clock\n // on the Runtime once per step (see Interpreter.as in Scratch 2\n // for original use of `currentMSecs`)\n this.runtime.updateCurrentMSecs();\n // Start counting toward WORK_TIME.\n this.timer.start();\n // Count of active threads.\n let numActiveThreads = Infinity;\n // Whether `stepThreads` has run through a full single tick.\n let ranFirstTick = false;\n const doneThreads = [];\n // Conditions for continuing to stepping threads:\n // 1. We must have threads in the list, and some must be active.\n // 2. Time elapsed must be less than WORK_TIME.\n // 3. Either turbo mode, or no redraw has been requested by a primitive.\n while (this.runtime.threads.length > 0 &&\n numActiveThreads > 0 &&\n this.timer.timeElapsed() < WORK_TIME &&\n (this.runtime.turboMode || !this.runtime.redrawRequested)) {\n if (this.runtime.profiler !== null) {\n if (stepThreadsInnerProfilerId === -1) {\n stepThreadsInnerProfilerId = this.runtime.profiler.idByName(stepThreadsInnerProfilerFrame);\n }\n this.runtime.profiler.start(stepThreadsInnerProfilerId);\n }\n\n numActiveThreads = 0;\n let stoppedThread = false;\n // Attempt to run each thread one time.\n const threads = this.runtime.threads;\n for (let i = 0; i < threads.length; i++) {\n const activeThread = this.activeThread = threads[i];\n // Check if the thread is done so it is not executed.\n if (activeThread.stack.length === 0 ||\n activeThread.status === Thread.STATUS_DONE) {\n // Finished with this thread.\n stoppedThread = true;\n continue;\n }\n if (activeThread.status === Thread.STATUS_YIELD_TICK &&\n !ranFirstTick) {\n // Clear single-tick yield from the last call of `stepThreads`.\n activeThread.status = Thread.STATUS_RUNNING;\n }\n if (activeThread.status === Thread.STATUS_RUNNING ||\n activeThread.status === Thread.STATUS_YIELD) {\n // Normal-mode thread: step.\n if (this.runtime.profiler !== null) {\n if (stepThreadProfilerId === -1) {\n stepThreadProfilerId = this.runtime.profiler.idByName(stepThreadProfilerFrame);\n }\n\n // Increment the number of times stepThread is called.\n this.runtime.profiler.increment(stepThreadProfilerId);\n }\n this.stepThread(activeThread);\n activeThread.warpTimer = null;\n if (activeThread.isKilled) {\n i--; // if the thread is removed from the list (killed), do not increase index\n }\n }\n if (activeThread.status === Thread.STATUS_RUNNING) {\n numActiveThreads++;\n }\n // Check if the thread completed while it just stepped to make\n // sure we remove it before the next iteration of all threads.\n if (activeThread.stack.length === 0 ||\n activeThread.status === Thread.STATUS_DONE) {\n // Finished with this thread.\n stoppedThread = true;\n }\n }\n // We successfully ticked once. Prevents running STATUS_YIELD_TICK\n // threads on the next tick.\n ranFirstTick = true;\n\n if (this.runtime.profiler !== null) {\n this.runtime.profiler.stop();\n }\n\n // Filter inactive threads from `this.runtime.threads`.\n if (stoppedThread) {\n let nextActiveThread = 0;\n for (let i = 0; i < this.runtime.threads.length; i++) {\n const thread = this.runtime.threads[i];\n if (thread.stack.length !== 0 &&\n thread.status !== Thread.STATUS_DONE) {\n this.runtime.threads[nextActiveThread] = thread;\n nextActiveThread++;\n } else {\n doneThreads.push(thread);\n }\n }\n this.runtime.threads.length = nextActiveThread;\n }\n }\n\n this.activeThread = null;\n\n return doneThreads;\n }\n\n /**\n * Step the requested thread for as long as necessary.\n * @param {!Thread} thread Thread object to step.\n */\n stepThread (thread) {\n let currentBlockId = thread.peekStack();\n if (!currentBlockId) {\n // A \"null block\" - empty branch.\n thread.popStack();\n\n // Did the null follow a hat block?\n if (thread.stack.length === 0) {\n thread.status = Thread.STATUS_DONE;\n return;\n }\n }\n // Save the current block ID to notice if we did control flow.\n while ((currentBlockId = thread.peekStack())) {\n let isWarpMode = thread.peekStackFrame().warpMode;\n if (isWarpMode && !thread.warpTimer) {\n // Initialize warp-mode timer if it hasn't been already.\n // This will start counting the thread toward `Sequencer.WARP_TIME`.\n thread.warpTimer = new Timer();\n thread.warpTimer.start();\n }\n // Execute the current block.\n if (this.runtime.profiler !== null) {\n if (executeProfilerId === -1) {\n executeProfilerId = this.runtime.profiler.idByName(executeProfilerFrame);\n }\n\n // Increment the number of times execute is called.\n this.runtime.profiler.increment(executeProfilerId);\n }\n if (thread.target === null) {\n this.retireThread(thread);\n } else {\n execute(this, thread);\n }\n thread.blockGlowInFrame = currentBlockId;\n // If the thread has yielded or is waiting, yield to other threads.\n if (thread.status === Thread.STATUS_YIELD) {\n // Mark as running for next iteration.\n thread.status = Thread.STATUS_RUNNING;\n // In warp mode, yielded blocks are re-executed immediately.\n if (isWarpMode &&\n thread.warpTimer.timeElapsed() <= Sequencer.WARP_TIME) {\n continue;\n }\n return;\n } else if (thread.status === Thread.STATUS_PROMISE_WAIT) {\n // A promise was returned by the primitive. Yield the thread\n // until the promise resolves. Promise resolution should reset\n // thread.status to Thread.STATUS_RUNNING.\n return;\n } else if (thread.status === Thread.STATUS_YIELD_TICK) {\n // stepThreads will reset the thread to Thread.STATUS_RUNNING\n return;\n }\n // If no control flow has happened, switch to next block.\n if (thread.peekStack() === currentBlockId) {\n thread.goToNextBlock();\n }\n // If no next block has been found at this point, look on the stack.\n while (!thread.peekStack()) {\n thread.popStack();\n\n if (thread.stack.length === 0) {\n // No more stack to run!\n thread.status = Thread.STATUS_DONE;\n return;\n }\n\n const stackFrame = thread.peekStackFrame();\n isWarpMode = stackFrame.warpMode;\n\n if (stackFrame.isLoop) {\n // The current level of the stack is marked as a loop.\n // Return to yield for the frame/tick in general.\n // Unless we're in warp mode - then only return if the\n // warp timer is up.\n if (!isWarpMode ||\n thread.warpTimer.timeElapsed() > Sequencer.WARP_TIME) {\n // Don't do anything to the stack, since loops need\n // to be re-executed.\n return;\n }\n // Don't go to the next block for this level of the stack,\n // since loops need to be re-executed.\n continue;\n\n } else if (stackFrame.waitingReporter) {\n // This level of the stack was waiting for a value.\n // This means a reporter has just returned - so don't go\n // to the next block for this level of the stack.\n return;\n }\n // Get next block of existing block on the stack.\n thread.goToNextBlock();\n }\n }\n }\n\n /**\n * Step a thread into a block's branch.\n * @param {!Thread} thread Thread object to step to branch.\n * @param {number} branchNum Which branch to step to (i.e., 1, 2).\n * @param {boolean} isLoop Whether this block is a loop.\n */\n stepToBranch (thread, branchNum, isLoop) {\n if (!branchNum) {\n branchNum = 1;\n }\n const currentBlockId = thread.peekStack();\n const branchId = thread.target.blocks.getBranch(\n currentBlockId,\n branchNum\n );\n thread.peekStackFrame().isLoop = isLoop;\n if (branchId) {\n // Push branch ID to the thread's stack.\n thread.pushStack(branchId);\n } else {\n thread.pushStack(null);\n }\n }\n\n /**\n * Step a procedure.\n * @param {!Thread} thread Thread object to step to procedure.\n * @param {!string} procedureCode Procedure code of procedure to step to.\n */\n stepToProcedure (thread, procedureCode) {\n const definition = thread.target.blocks.getProcedureDefinition(procedureCode);\n if (!definition) {\n return;\n }\n // Check if the call is recursive.\n // If so, set the thread to yield after pushing.\n const isRecursive = thread.isRecursiveCall(procedureCode);\n // To step to a procedure, we put its definition on the stack.\n // Execution for the thread will proceed through the definition hat\n // and on to the main definition of the procedure.\n // When that set of blocks finishes executing, it will be popped\n // from the stack by the sequencer, returning control to the caller.\n thread.pushStack(definition);\n // In known warp-mode threads, only yield when time is up.\n if (thread.peekStackFrame().warpMode &&\n thread.warpTimer.timeElapsed() > Sequencer.WARP_TIME) {\n thread.status = Thread.STATUS_YIELD;\n } else {\n // Look for warp-mode flag on definition, and set the thread\n // to warp-mode if needed.\n const definitionBlock = thread.target.blocks.getBlock(definition);\n const innerBlock = thread.target.blocks.getBlock(\n definitionBlock.inputs.custom_block.block);\n let doWarp = false;\n if (innerBlock && innerBlock.mutation) {\n const warp = innerBlock.mutation.warp;\n if (typeof warp === 'boolean') {\n doWarp = warp;\n } else if (typeof warp === 'string') {\n doWarp = JSON.parse(warp);\n }\n }\n if (doWarp) {\n thread.peekStackFrame().warpMode = true;\n } else if (isRecursive) {\n // In normal-mode threads, yield any time we have a recursive call.\n thread.status = Thread.STATUS_YIELD;\n }\n }\n }\n\n /**\n * Retire a thread in the middle, without considering further blocks.\n * @param {!Thread} thread Thread object to retire.\n */\n retireThread (thread) {\n thread.stack = [];\n thread.stackFrame = [];\n thread.requestScriptGlowInFrame = false;\n thread.status = Thread.STATUS_DONE;\n }\n}\n\nmodule.exports = Sequencer;\n","class StageLayering {\n static get BACKGROUND_LAYER () {\n return 'background';\n }\n\n static get VIDEO_LAYER () {\n return 'video';\n }\n\n static get PEN_LAYER () {\n return 'pen';\n }\n\n static get SPRITE_LAYER () {\n return 'sprite';\n }\n\n // Order of layer groups relative to each other,\n static get LAYER_GROUPS () {\n return [\n StageLayering.BACKGROUND_LAYER,\n StageLayering.VIDEO_LAYER,\n StageLayering.PEN_LAYER,\n StageLayering.SPRITE_LAYER\n ];\n }\n}\n\nmodule.exports = StageLayering;\n","const EventEmitter = require('events');\n\nconst Blocks = require('./blocks');\nconst Variable = require('../engine/variable');\nconst Comment = require('../engine/comment');\nconst uid = require('../util/uid');\nconst {Map} = require('immutable');\nconst log = require('../util/log');\nconst StringUtil = require('../util/string-util');\nconst VariableUtil = require('../util/variable-util');\n\n/**\n * @fileoverview\n * A Target is an abstract \"code-running\" object for the Scratch VM.\n * Examples include sprites/clones or potentially physical-world devices.\n */\n\nclass Target extends EventEmitter {\n\n /**\n * @param {Runtime} runtime Reference to the runtime.\n * @param {?Blocks} blocks Blocks instance for the blocks owned by this target.\n * @constructor\n */\n constructor (runtime, blocks) {\n super();\n\n if (!blocks) {\n blocks = new Blocks(runtime);\n }\n\n /**\n * Reference to the runtime.\n * @type {Runtime}\n */\n this.runtime = runtime;\n /**\n * A unique ID for this target.\n * @type {string}\n */\n this.id = uid();\n /**\n * Blocks run as code for this target.\n * @type {!Blocks}\n */\n this.blocks = blocks;\n /**\n * Dictionary of variables and their values for this target.\n * Key is the variable id.\n * @type {Object.}\n */\n this.variables = {};\n /**\n * Dictionary of comments for this target.\n * Key is the comment id.\n * @type {Object.}\n */\n this.comments = {};\n /**\n * Dictionary of custom state for this target.\n * This can be used to store target-specific custom state for blocks which need it.\n * TODO: do we want to persist this in SB3 files?\n * @type {Object.}\n */\n this._customState = {};\n\n /**\n * Currently known values for edge-activated hats.\n * Keys are block ID for the hat; values are the currently known values.\n * @type {Object.}\n */\n this._edgeActivatedHatValues = {};\n }\n\n /**\n * Called when the project receives a \"green flag.\"\n * @abstract\n */\n onGreenFlag () {}\n\n /**\n * Return a human-readable name for this target.\n * Target implementations should override this.\n * @abstract\n * @returns {string} Human-readable name for the target.\n */\n getName () {\n return this.id;\n }\n\n /**\n * Update an edge-activated hat block value.\n * @param {!string} blockId ID of hat to store value for.\n * @param {*} newValue Value to store for edge-activated hat.\n * @return {*} The old value for the edge-activated hat.\n */\n updateEdgeActivatedValue (blockId, newValue) {\n const oldValue = this._edgeActivatedHatValues[blockId];\n this._edgeActivatedHatValues[blockId] = newValue;\n return oldValue;\n }\n\n hasEdgeActivatedValue (blockId) {\n return this._edgeActivatedHatValues.hasOwnProperty(blockId);\n }\n\n /**\n * Clear all edge-activaed hat values.\n */\n clearEdgeActivatedValues () {\n this._edgeActivatedHatValues = {};\n }\n\n /**\n * Look up a variable object, first by id, and then by name if the id is not found.\n * Create a new variable if both lookups fail.\n * @param {string} id Id of the variable.\n * @param {string} name Name of the variable.\n * @return {!Variable} Variable object.\n */\n lookupOrCreateVariable (id, name) {\n let variable = this.lookupVariableById(id);\n if (variable) return variable;\n\n variable = this.lookupVariableByNameAndType(name, Variable.SCALAR_TYPE);\n if (variable) return variable;\n\n // No variable with this name exists - create it locally.\n const newVariable = new Variable(id, name, Variable.SCALAR_TYPE, false);\n this.variables[id] = newVariable;\n return newVariable;\n }\n\n /**\n * Look up a broadcast message object with the given id and return it\n * if it exists.\n * @param {string} id Id of the variable.\n * @param {string} name Name of the variable.\n * @return {?Variable} Variable object.\n */\n lookupBroadcastMsg (id, name) {\n let broadcastMsg;\n if (id) {\n broadcastMsg = this.lookupVariableById(id);\n } else if (name) {\n broadcastMsg = this.lookupBroadcastByInputValue(name);\n } else {\n log.error('Cannot find broadcast message if neither id nor name are provided.');\n }\n if (broadcastMsg) {\n if (name && (broadcastMsg.name.toLowerCase() !== name.toLowerCase())) {\n log.error(`Found broadcast message with id: ${id}, but` +\n `its name, ${broadcastMsg.name} did not match expected name ${name}.`);\n }\n if (broadcastMsg.type !== Variable.BROADCAST_MESSAGE_TYPE) {\n log.error(`Found variable with id: ${id}, but its type ${broadcastMsg.type}` +\n `did not match expected type ${Variable.BROADCAST_MESSAGE_TYPE}`);\n }\n return broadcastMsg;\n }\n }\n\n /**\n * Look up a broadcast message with the given name and return the variable\n * if it exists. Does not create a new broadcast message variable if\n * it doesn't exist.\n * @param {string} name Name of the variable.\n * @return {?Variable} Variable object.\n */\n lookupBroadcastByInputValue (name) {\n const vars = this.variables;\n for (const propName in vars) {\n if ((vars[propName].type === Variable.BROADCAST_MESSAGE_TYPE) &&\n (vars[propName].name.toLowerCase() === name.toLowerCase())) {\n return vars[propName];\n }\n }\n }\n\n /**\n * Look up a variable object.\n * Search begins for local variables; then look for globals.\n * @param {string} id Id of the variable.\n * @param {string} name Name of the variable.\n * @return {!Variable} Variable object.\n */\n lookupVariableById (id) {\n // If we have a local copy, return it.\n if (this.variables.hasOwnProperty(id)) {\n return this.variables[id];\n }\n // If the stage has a global copy, return it.\n if (this.runtime && !this.isStage) {\n const stage = this.runtime.getTargetForStage();\n if (stage && stage.variables.hasOwnProperty(id)) {\n return stage.variables[id];\n }\n }\n }\n\n /**\n * Look up a variable object by its name and variable type.\n * Search begins with local variables; then global variables if a local one\n * was not found.\n * @param {string} name Name of the variable.\n * @param {string} type Type of the variable. Defaults to Variable.SCALAR_TYPE.\n * @param {?bool} skipStage Optional flag to skip checking the stage\n * @return {?Variable} Variable object if found, or null if not.\n */\n lookupVariableByNameAndType (name, type, skipStage) {\n if (typeof name !== 'string') return;\n if (typeof type !== 'string') type = Variable.SCALAR_TYPE;\n skipStage = skipStage || false;\n\n for (const varId in this.variables) {\n const currVar = this.variables[varId];\n if (currVar.name === name && currVar.type === type) {\n return currVar;\n }\n }\n\n if (!skipStage && this.runtime && !this.isStage) {\n const stage = this.runtime.getTargetForStage();\n if (stage) {\n for (const varId in stage.variables) {\n const currVar = stage.variables[varId];\n if (currVar.name === name && currVar.type === type) {\n return currVar;\n }\n }\n }\n }\n\n return null;\n }\n\n /**\n * Look up a list object for this target, and create it if one doesn't exist.\n * Search begins for local lists; then look for globals.\n * @param {!string} id Id of the list.\n * @param {!string} name Name of the list.\n * @return {!Varible} Variable object representing the found/created list.\n */\n lookupOrCreateList (id, name) {\n let list = this.lookupVariableById(id);\n if (list) return list;\n\n list = this.lookupVariableByNameAndType(name, Variable.LIST_TYPE);\n if (list) return list;\n\n // No variable with this name exists - create it locally.\n const newList = new Variable(id, name, Variable.LIST_TYPE, false);\n this.variables[id] = newList;\n return newList;\n }\n\n /**\n * Creates a variable with the given id and name and adds it to the\n * dictionary of variables.\n * @param {string} id Id of variable\n * @param {string} name Name of variable.\n * @param {string} type Type of variable, '', 'broadcast_msg', or 'list'\n * @param {boolean} isCloud Whether the variable to create has the isCloud flag set.\n * Additional checks are made that the variable can be created as a cloud variable.\n */\n createVariable (id, name, type, isCloud) {\n if (!this.variables.hasOwnProperty(id)) {\n const newVariable = new Variable(id, name, type, false);\n if (isCloud && this.isStage && this.runtime.canAddCloudVariable()) {\n newVariable.isCloud = true;\n this.runtime.addCloudVariable();\n this.runtime.ioDevices.cloud.requestCreateVariable(newVariable);\n }\n this.variables[id] = newVariable;\n }\n }\n\n /**\n * Creates a comment with the given properties.\n * @param {string} id Id of the comment.\n * @param {string} blockId Optional id of the block the comment is attached\n * to if it is a block comment.\n * @param {string} text The text the comment contains.\n * @param {number} x The x coordinate of the comment on the workspace.\n * @param {number} y The y coordinate of the comment on the workspace.\n * @param {number} width The width of the comment when it is full size\n * @param {number} height The height of the comment when it is full size\n * @param {boolean} minimized Whether the comment is minimized.\n */\n createComment (id, blockId, text, x, y, width, height, minimized) {\n if (!this.comments.hasOwnProperty(id)) {\n const newComment = new Comment(id, text, x, y,\n width, height, minimized);\n if (blockId) {\n newComment.blockId = blockId;\n const blockWithComment = this.blocks.getBlock(blockId);\n if (blockWithComment) {\n blockWithComment.comment = id;\n } else {\n log.warn(`Could not find block with id ${blockId\n } associated with commentId: ${id}`);\n }\n }\n this.comments[id] = newComment;\n }\n }\n\n /**\n * Renames the variable with the given id to newName.\n * @param {string} id Id of variable to rename.\n * @param {string} newName New name for the variable.\n */\n renameVariable (id, newName) {\n if (this.variables.hasOwnProperty(id)) {\n const variable = this.variables[id];\n if (variable.id === id) {\n const oldName = variable.name;\n variable.name = newName;\n\n if (this.runtime) {\n if (variable.isCloud && this.isStage) {\n this.runtime.ioDevices.cloud.requestRenameVariable(oldName, newName);\n }\n\n if (variable.type === Variable.SCALAR_TYPE) {\n // sensing__of may be referencing to this variable.\n // Change the reference.\n let blockUpdated = false;\n this.runtime.targets.forEach(t => {\n blockUpdated = t.blocks.updateSensingOfReference(\n oldName,\n newName,\n this.isStage ? '_stage_' : this.getName()\n ) || blockUpdated;\n });\n // Request workspace change only if sensing_of blocks were actually updated.\n if (blockUpdated) this.runtime.requestBlocksUpdate();\n }\n\n const blocks = this.runtime.monitorBlocks;\n blocks.changeBlock({\n id: id,\n element: 'field',\n name: variable.type === Variable.LIST_TYPE ? 'LIST' : 'VARIABLE',\n value: id\n }, this.runtime);\n const monitorBlock = blocks.getBlock(variable.id);\n if (monitorBlock) {\n this.runtime.requestUpdateMonitor(Map({\n id: id,\n params: blocks._getBlockParams(monitorBlock)\n }));\n }\n }\n\n }\n }\n }\n\n /**\n * Removes the variable with the given id from the dictionary of variables.\n * @param {string} id Id of variable to delete.\n */\n deleteVariable (id) {\n if (this.variables.hasOwnProperty(id)) {\n // Get info about the variable before deleting it\n const deletedVariableName = this.variables[id].name;\n const deletedVariableWasCloud = this.variables[id].isCloud;\n delete this.variables[id];\n if (this.runtime) {\n if (deletedVariableWasCloud && this.isStage) {\n this.runtime.ioDevices.cloud.requestDeleteVariable(deletedVariableName);\n this.runtime.removeCloudVariable();\n }\n this.runtime.monitorBlocks.deleteBlock(id);\n this.runtime.requestRemoveMonitor(id);\n }\n }\n }\n\n /**\n * Remove this target's monitors from the runtime state and remove the\n * target-specific monitored blocks (e.g. local variables, global variables for the stage, x-position).\n * NOTE: This does not delete any of the stage monitors like backdrop name.\n */\n deleteMonitors () {\n this.runtime.requestRemoveMonitorByTargetId(this.id);\n let targetSpecificMonitorBlockIds;\n if (this.isStage) {\n // This only deletes global variables and not other stage monitors like backdrop number.\n targetSpecificMonitorBlockIds = Object.keys(this.variables);\n } else {\n targetSpecificMonitorBlockIds = Object.keys(this.runtime.monitorBlocks._blocks)\n .filter(key => this.runtime.monitorBlocks._blocks[key].targetId === this.id);\n }\n for (const blockId of targetSpecificMonitorBlockIds) {\n this.runtime.monitorBlocks.deleteBlock(blockId);\n }\n }\n\n /**\n * Create a clone of the variable with the given id from the dictionary of\n * this target's variables.\n * @param {string} id Id of variable to duplicate.\n * @param {boolean=} optKeepOriginalId Optional flag to keep the original variable ID\n * for the duplicate variable. This is necessary when cloning a sprite, for example.\n * @return {?Variable} The duplicated variable, or null if\n * the original variable was not found.\n */\n duplicateVariable (id, optKeepOriginalId) {\n if (this.variables.hasOwnProperty(id)) {\n const originalVariable = this.variables[id];\n const newVariable = new Variable(\n optKeepOriginalId ? id : null, // conditionally keep original id or generate a new one\n originalVariable.name,\n originalVariable.type,\n originalVariable.isCloud\n );\n if (newVariable.type === Variable.LIST_TYPE) {\n newVariable.value = originalVariable.value.slice(0);\n } else {\n newVariable.value = originalVariable.value;\n }\n return newVariable;\n }\n return null;\n }\n\n /**\n * Duplicate the dictionary of this target's variables as part of duplicating.\n * this target or making a clone.\n * @param {object=} optBlocks Optional block container for the target being duplicated.\n * If provided, new variables will be generated with new UIDs and any variable references\n * in this blocks container will be updated to refer to the corresponding new IDs.\n * @return {object} The duplicated dictionary of variables\n */\n duplicateVariables (optBlocks) {\n let allVarRefs;\n if (optBlocks) {\n allVarRefs = optBlocks.getAllVariableAndListReferences();\n }\n return Object.keys(this.variables).reduce((accum, varId) => {\n const newVariable = this.duplicateVariable(varId, !optBlocks);\n accum[newVariable.id] = newVariable;\n if (optBlocks && allVarRefs) {\n const currVarRefs = allVarRefs[varId];\n if (currVarRefs) {\n this.mergeVariables(varId, newVariable.id, currVarRefs);\n }\n }\n return accum;\n }, {});\n }\n\n /**\n * Post/edit sprite info.\n * @param {object} data An object with sprite info data to set.\n * @abstract\n */\n postSpriteInfo () {}\n\n /**\n * Retrieve custom state associated with this target and the provided state ID.\n * @param {string} stateId - specify which piece of state to retrieve.\n * @returns {*} the associated state, if any was found.\n */\n getCustomState (stateId) {\n return this._customState[stateId];\n }\n\n /**\n * Store custom state associated with this target and the provided state ID.\n * @param {string} stateId - specify which piece of state to store on this target.\n * @param {*} newValue - the state value to store.\n */\n setCustomState (stateId, newValue) {\n this._customState[stateId] = newValue;\n }\n\n /**\n * Call to destroy a target.\n * @abstract\n */\n dispose () {\n this._customState = {};\n\n if (this.runtime) {\n this.runtime.removeExecutable(this);\n }\n }\n\n // Variable Conflict Resolution Helpers\n\n /**\n * Get the names of all the variables of the given type that are in scope for this target.\n * For targets that are not the stage, this includes any target-specific\n * variables as well as any stage variables unless the skipStage flag is true.\n * For the stage, this is all stage variables.\n * @param {string} type The variable type to search for; defaults to Variable.SCALAR_TYPE\n * @param {?bool} skipStage Optional flag to skip the stage.\n * @return {Array} A list of variable names\n */\n getAllVariableNamesInScopeByType (type, skipStage) {\n if (typeof type !== 'string') type = Variable.SCALAR_TYPE;\n skipStage = skipStage || false;\n const targetVariables = Object.values(this.variables)\n .filter(v => v.type === type)\n .map(variable => variable.name);\n if (skipStage || this.isStage || !this.runtime) {\n return targetVariables;\n }\n const stage = this.runtime.getTargetForStage();\n const stageVariables = stage.getAllVariableNamesInScopeByType(type);\n return targetVariables.concat(stageVariables);\n }\n\n /**\n * Merge variable references with another variable.\n * @param {string} idToBeMerged ID of the variable whose references need to be updated\n * @param {string} idToMergeWith ID of the variable that the old references should be replaced with\n * @param {?Array} optReferencesToUpdate Optional context of the change.\n * Defaults to all the blocks in this target.\n * @param {?string} optNewName New variable name to merge with. The old\n * variable name in the references being updated should be replaced with this new name.\n * If this parameter is not provided or is '', no name change occurs.\n */\n mergeVariables (idToBeMerged, idToMergeWith, optReferencesToUpdate, optNewName) {\n const referencesToChange = optReferencesToUpdate ||\n // TODO should there be a separate helper function that traverses the blocks\n // for all references for a given ID instead of doing the below..?\n this.blocks.getAllVariableAndListReferences()[idToBeMerged];\n\n VariableUtil.updateVariableIdentifiers(referencesToChange, idToMergeWith, optNewName);\n }\n\n /**\n * Share a local variable (and given references for that variable) to the stage.\n * @param {string} varId The ID of the variable to share.\n * @param {Array} varRefs The list of variable references being shared,\n * that reference the given variable ID. The names and IDs of these variable\n * references will be updated to refer to the new (or pre-existing) global variable.\n */\n shareLocalVariableToStage (varId, varRefs) {\n if (!this.runtime) return;\n const variable = this.variables[varId];\n if (!variable) {\n log.warn(`Cannot share a local variable to the stage if it's not local.`);\n return;\n }\n const stage = this.runtime.getTargetForStage();\n // If a local var is being shared with the stage,\n // sharing will make the variable global, resulting in a conflict\n // with the existing local variable. Preemptively Resolve this conflict\n // by renaming the new global variable.\n\n // First check if we've already done the local to global transition for this\n // variable. If we have, merge it with the global variable we've already created.\n const varIdForStage = `StageVarFromLocal_${varId}`;\n let stageVar = stage.lookupVariableById(varIdForStage);\n // If a global var doesn't already exist, create a new one with a fresh name.\n // Use the ID we created above so that we can lookup this new variable in the\n // future if we decide to share this same variable again.\n if (!stageVar) {\n const varName = variable.name;\n const varType = variable.type;\n\n const newStageName = `Stage: ${varName}`;\n stageVar = this.runtime.createNewGlobalVariable(newStageName, varIdForStage, varType);\n }\n // Update all variable references to use the new name and ID\n this.mergeVariables(varId, stageVar.id, varRefs, stageVar.name);\n }\n\n /**\n * Share a local variable with a sprite, merging with one of the same name and\n * type if it already exists on the sprite, or create a new one.\n * @param {string} varId Id of the variable to share\n * @param {Target} sprite The sprite to share the variable with\n * @param {Array} varRefs A list of all the variable references currently being shared.\n */\n shareLocalVariableToSprite (varId, sprite, varRefs) {\n if (!this.runtime) return;\n if (this.isStage) return;\n const variable = this.variables[varId];\n if (!variable) {\n log.warn(`Tried to call 'shareLocalVariableToSprite' with a non-local variable.`);\n return;\n }\n const varName = variable.name;\n const varType = variable.type;\n // Check if the receiving sprite already has a variable of the same name and type\n // and use the existing variable, otherwise create a new one.\n const existingLocalVar = sprite.lookupVariableByNameAndType(varName, varType);\n let newVarId;\n if (existingLocalVar) {\n newVarId = existingLocalVar.id;\n } else {\n const newVar = new Variable(null, varName, varType);\n newVarId = newVar.id;\n sprite.variables[newVarId] = newVar;\n }\n\n // Merge with the local variable on the new sprite.\n this.mergeVariables(varId, newVarId, varRefs);\n }\n\n /**\n * Given a list of variable referencing fields, shares those variables with\n * the target with the provided id, resolving any variable conflicts that arise\n * using the following rules:\n *\n * If this target is the stage, exit. There are no conflicts that arise\n * from sharing variables from the stage to another sprite. The variables\n * already exist globally, so no further action is needed.\n *\n * If a variable being referenced is a global variable, do nothing. The\n * global variable already exists so no further action is needed.\n *\n * If a variable being referenced is local, and\n * 1) The receiving target is a sprite:\n * create a new local variable or merge with an existing local variable\n * of the same name and type. Update all the referencing fields\n * for the original variable to reference the new variable.\n * 2) The receiving target is the stage:\n * Create a new global variable with a fresh name and update all the referencing\n * fields to reference the new variable.\n *\n * @param {Array} blocks The blocks containing\n * potential conflicting references to variables.\n * @param {Target} receivingTarget The target receiving the variables\n */\n resolveVariableSharingConflictsWithTarget (blocks, receivingTarget) {\n if (this.isStage) return;\n\n // Get all the variable references in the given list of blocks\n const allVarListRefs = this.blocks.getAllVariableAndListReferences(blocks);\n\n // For all the variables being referenced, check for which ones are local\n // to this target, and resolve conflicts based on whether the receiving target\n // is a sprite (with a conflicting local variable) or whether it is\n // the stage (which cannot have local variables)\n for (const varId in allVarListRefs) {\n const currVar = this.variables[varId];\n if (!currVar) continue; // The current variable is global, there shouldn't be any conflicts here, skip it.\n\n // Get the list of references for the current variable id\n const currVarListRefs = allVarListRefs[varId];\n\n if (receivingTarget.isStage) {\n this.shareLocalVariableToStage(varId, currVarListRefs);\n } else {\n this.shareLocalVariableToSprite(varId, receivingTarget, currVarListRefs);\n }\n }\n }\n\n /**\n * Fixes up variable references in this target avoiding conflicts with\n * pre-existing variables in the same scope.\n * This is used when uploading this target as a new sprite into an existing\n * project, where the new sprite may contain references\n * to variable names that already exist as global variables in the project\n * (and thus are in scope for variable references in the given sprite).\n *\n * If this target has a block that references an existing global variable and that\n * variable *does not* exist in this target (e.g. it was a global variable in the\n * project the sprite was originally exported from), merge the variables. This entails\n * fixing the variable references in this sprite to reference the id of the pre-existing global variable.\n *\n * If this target has a block that references an existing global variable and that\n * variable does exist in the target itself (e.g. it's a local variable in the sprite being uploaded),\n * then the local variable is renamed to distinguish itself from the pre-existing variable.\n * All blocks that reference the local variable will be updated to use the new name.\n */\n // TODO (#1360) This function is too long, add some helpers for the different chunks and cases...\n fixUpVariableReferences () {\n if (!this.runtime) return; // There's no runtime context to conflict with\n if (this.isStage) return; // Stage can't have variable conflicts with itself (and also can't be uploaded)\n const stage = this.runtime.getTargetForStage();\n if (!stage || !stage.variables) return;\n\n const renameConflictingLocalVar = (id, name, type) => {\n const conflict = stage.lookupVariableByNameAndType(name, type);\n if (conflict) {\n const newName = StringUtil.unusedName(\n `${this.getName()}: ${name}`,\n this.getAllVariableNamesInScopeByType(type));\n this.renameVariable(id, newName);\n return newName;\n }\n return null;\n };\n\n const allReferences = this.blocks.getAllVariableAndListReferences();\n const unreferencedLocalVarIds = [];\n if (Object.keys(this.variables).length > 0) {\n for (const localVarId in this.variables) {\n if (!this.variables.hasOwnProperty(localVarId)) continue;\n if (!allReferences[localVarId]) unreferencedLocalVarIds.push(localVarId);\n }\n }\n const conflictIdsToReplace = Object.create(null);\n const conflictNamesToReplace = Object.create(null);\n\n // Cache the list of all variable names by type so that we don't need to\n // re-calculate this in every iteration of the following loop.\n const varNamesByType = {};\n const allVarNames = type => {\n const namesOfType = varNamesByType[type];\n if (namesOfType) return namesOfType;\n varNamesByType[type] = this.runtime.getAllVarNamesOfType(type);\n return varNamesByType[type];\n };\n\n for (const varId in allReferences) {\n // We don't care about which var ref we get, they should all have the same var info\n const varRef = allReferences[varId][0];\n const varName = varRef.referencingField.value;\n const varType = varRef.type;\n if (this.lookupVariableById(varId)) {\n // Found a variable with the id in either the target or the stage,\n // figure out which one.\n if (this.variables.hasOwnProperty(varId)) {\n // If the target has the variable, then check whether the stage\n // has one with the same name and type. If it does, then rename\n // this target specific variable so that there is a distinction.\n const newVarName = renameConflictingLocalVar(varId, varName, varType);\n\n if (newVarName) {\n // We are not calling this.blocks.updateBlocksAfterVarRename\n // here because it will search through all the blocks. We already\n // have access to all the references for this var id.\n allReferences[varId].map(ref => {\n ref.referencingField.value = newVarName;\n return ref;\n });\n }\n }\n } else {\n // We didn't find the referenced variable id anywhere,\n // Treat it as a reference to a global variable (from the original\n // project this sprite was exported from).\n // Check for whether a global variable of the same name and type exists,\n // and if so, track it to merge with the existing global in a second pass of the blocks.\n const existingVar = stage.lookupVariableByNameAndType(varName, varType);\n if (existingVar) {\n if (!conflictIdsToReplace[varId]) {\n conflictIdsToReplace[varId] = existingVar.id;\n }\n } else {\n // A global variable with the same name did not already exist,\n // create a new one such that it does not conflict with any\n // names of local variables of the same type.\n const allNames = allVarNames(varType);\n const freshName = StringUtil.unusedName(varName, allNames);\n stage.createVariable(varId, freshName, varType);\n if (!conflictNamesToReplace[varId]) {\n conflictNamesToReplace[varId] = freshName;\n }\n }\n }\n }\n // Rename any local variables that were missed above because they aren't\n // referenced by any blocks\n for (const id in unreferencedLocalVarIds) {\n const varId = unreferencedLocalVarIds[id];\n const name = this.variables[varId].name;\n const type = this.variables[varId].type;\n renameConflictingLocalVar(varId, name, type);\n }\n // Handle global var conflicts with existing global vars (e.g. a sprite is uploaded, and has\n // blocks referencing some variable that the sprite does not own, and this\n // variable conflicts with a global var)\n // In this case, we want to merge the new variable referenes with the\n // existing global variable\n for (const conflictId in conflictIdsToReplace) {\n const existingId = conflictIdsToReplace[conflictId];\n const referencesToUpdate = allReferences[conflictId];\n this.mergeVariables(conflictId, existingId, referencesToUpdate);\n }\n\n // Handle global var conflicts existing local vars (e.g a sprite is uploaded,\n // and has blocks referencing some variable that the sprite does not own, and this\n // variable conflcits with another sprite's local var).\n // In this case, we want to go through the variable references and update\n // the name of the variable in that reference.\n for (const conflictId in conflictNamesToReplace) {\n const newName = conflictNamesToReplace[conflictId];\n const referencesToUpdate = allReferences[conflictId];\n referencesToUpdate.map(ref => {\n ref.referencingField.value = newName;\n return ref;\n });\n }\n }\n\n}\n\nmodule.exports = Target;\n","/**\n * Recycle bin for empty stackFrame objects\n * @type Array<_StackFrame>\n */\nconst _stackFrameFreeList = [];\n\n/**\n * A frame used for each level of the stack. A general purpose\n * place to store a bunch of execution context and parameters\n * @param {boolean} warpMode Whether this level of the stack is warping\n * @constructor\n * @private\n */\nclass _StackFrame {\n constructor (warpMode) {\n /**\n * Whether this level of the stack is a loop.\n * @type {boolean}\n */\n this.isLoop = false;\n\n /**\n * Whether this level is in warp mode. Is set by some legacy blocks and\n * \"turbo mode\"\n * @type {boolean}\n */\n this.warpMode = warpMode;\n\n /**\n * Reported value from just executed block.\n * @type {Any}\n */\n this.justReported = null;\n\n /**\n * The active block that is waiting on a promise.\n * @type {string}\n */\n this.reporting = '';\n\n /**\n * Persists reported inputs during async block.\n * @type {Object}\n */\n this.reported = null;\n\n /**\n * Name of waiting reporter.\n * @type {string}\n */\n this.waitingReporter = null;\n\n /**\n * Procedure parameters.\n * @type {Object}\n */\n this.params = null;\n\n /**\n * A context passed to block implementations.\n * @type {Object}\n */\n this.executionContext = null;\n }\n\n /**\n * Reset all properties of the frame to pristine null and false states.\n * Used to recycle.\n * @return {_StackFrame} this\n */\n reset () {\n\n this.isLoop = false;\n this.warpMode = false;\n this.justReported = null;\n this.reported = null;\n this.waitingReporter = null;\n this.params = null;\n this.executionContext = null;\n\n return this;\n }\n\n /**\n * Reuse an active stack frame in the stack.\n * @param {?boolean} warpMode defaults to current warpMode\n * @returns {_StackFrame} this\n */\n reuse (warpMode = this.warpMode) {\n this.reset();\n this.warpMode = Boolean(warpMode);\n return this;\n }\n\n /**\n * Create or recycle a stack frame object.\n * @param {boolean} warpMode Enable warpMode on this frame.\n * @returns {_StackFrame} The clean stack frame with correct warpMode setting.\n */\n static create (warpMode) {\n const stackFrame = _stackFrameFreeList.pop();\n if (typeof stackFrame !== 'undefined') {\n stackFrame.warpMode = Boolean(warpMode);\n return stackFrame;\n }\n return new _StackFrame(warpMode);\n }\n\n /**\n * Put a stack frame object into the recycle bin for reuse.\n * @param {_StackFrame} stackFrame The frame to reset and recycle.\n */\n static release (stackFrame) {\n if (typeof stackFrame !== 'undefined') {\n _stackFrameFreeList.push(stackFrame.reset());\n }\n }\n}\n\n/**\n * A thread is a running stack context and all the metadata needed.\n * @param {?string} firstBlock First block to execute in the thread.\n * @constructor\n */\nclass Thread {\n constructor (firstBlock) {\n /**\n * ID of top block of the thread\n * @type {!string}\n */\n this.topBlock = firstBlock;\n\n /**\n * Stack for the thread. When the sequencer enters a control structure,\n * the block is pushed onto the stack so we know where to exit.\n * @type {Array.}\n */\n this.stack = [];\n\n /**\n * Stack frames for the thread. Store metadata for the executing blocks.\n * @type {Array.<_StackFrame>}\n */\n this.stackFrames = [];\n\n /**\n * Status of the thread, one of three states (below)\n * @type {number}\n */\n this.status = 0; /* Thread.STATUS_RUNNING */\n\n /**\n * Whether the thread is killed in the middle of execution.\n * @type {boolean}\n */\n this.isKilled = false;\n\n /**\n * Target of this thread.\n * @type {?Target}\n */\n this.target = null;\n\n /**\n * The Blocks this thread will execute.\n * @type {Blocks}\n */\n this.blockContainer = null;\n\n /**\n * Whether the thread requests its script to glow during this frame.\n * @type {boolean}\n */\n this.requestScriptGlowInFrame = false;\n\n /**\n * Which block ID should glow during this frame, if any.\n * @type {?string}\n */\n this.blockGlowInFrame = null;\n\n /**\n * A timer for when the thread enters warp mode.\n * Substitutes the sequencer's count toward WORK_TIME on a per-thread basis.\n * @type {?Timer}\n */\n this.warpTimer = null;\n\n this.justReported = null;\n }\n\n /**\n * Thread status for initialized or running thread.\n * This is the default state for a thread - execution should run normally,\n * stepping from block to block.\n * @const\n */\n static get STATUS_RUNNING () {\n return 0;\n }\n\n /**\n * Threads are in this state when a primitive is waiting on a promise;\n * execution is paused until the promise changes thread status.\n * @const\n */\n static get STATUS_PROMISE_WAIT () {\n return 1;\n }\n\n /**\n * Thread status for yield.\n * @const\n */\n static get STATUS_YIELD () {\n return 2;\n }\n\n /**\n * Thread status for a single-tick yield. This will be cleared when the\n * thread is resumed.\n * @const\n */\n static get STATUS_YIELD_TICK () {\n return 3;\n }\n\n /**\n * Thread status for a finished/done thread.\n * Thread is in this state when there are no more blocks to execute.\n * @const\n */\n static get STATUS_DONE () {\n return 4;\n }\n\n /**\n * Push stack and update stack frames appropriately.\n * @param {string} blockId Block ID to push to stack.\n */\n pushStack (blockId) {\n this.stack.push(blockId);\n // Push an empty stack frame, if we need one.\n // Might not, if we just popped the stack.\n if (this.stack.length > this.stackFrames.length) {\n const parent = this.stackFrames[this.stackFrames.length - 1];\n this.stackFrames.push(_StackFrame.create(typeof parent !== 'undefined' && parent.warpMode));\n }\n }\n\n /**\n * Reset the stack frame for use by the next block.\n * (avoids popping and re-pushing a new stack frame - keeps the warpmode the same\n * @param {string} blockId Block ID to push to stack.\n */\n reuseStackForNextBlock (blockId) {\n this.stack[this.stack.length - 1] = blockId;\n this.stackFrames[this.stackFrames.length - 1].reuse();\n }\n\n /**\n * Pop last block on the stack and its stack frame.\n * @return {string} Block ID popped from the stack.\n */\n popStack () {\n _StackFrame.release(this.stackFrames.pop());\n return this.stack.pop();\n }\n\n /**\n * Pop back down the stack frame until we hit a procedure call or the stack frame is emptied\n */\n stopThisScript () {\n let blockID = this.peekStack();\n while (blockID !== null) {\n const block = this.target.blocks.getBlock(blockID);\n if (typeof block !== 'undefined' && block.opcode === 'procedures_call') {\n break;\n }\n this.popStack();\n blockID = this.peekStack();\n }\n\n if (this.stack.length === 0) {\n // Clean up!\n this.requestScriptGlowInFrame = false;\n this.status = Thread.STATUS_DONE;\n }\n }\n\n /**\n * Get top stack item.\n * @return {?string} Block ID on top of stack.\n */\n peekStack () {\n return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;\n }\n\n\n /**\n * Get top stack frame.\n * @return {?object} Last stack frame stored on this thread.\n */\n peekStackFrame () {\n return this.stackFrames.length > 0 ? this.stackFrames[this.stackFrames.length - 1] : null;\n }\n\n /**\n * Get stack frame above the current top.\n * @return {?object} Second to last stack frame stored on this thread.\n */\n peekParentStackFrame () {\n return this.stackFrames.length > 1 ? this.stackFrames[this.stackFrames.length - 2] : null;\n }\n\n /**\n * Push a reported value to the parent of the current stack frame.\n * @param {*} value Reported value to push.\n */\n pushReportedValue (value) {\n this.justReported = typeof value === 'undefined' ? null : value;\n }\n\n /**\n * Initialize procedure parameters on this stack frame.\n */\n initParams () {\n const stackFrame = this.peekStackFrame();\n if (stackFrame.params === null) {\n stackFrame.params = {};\n }\n }\n\n /**\n * Add a parameter to the stack frame.\n * Use when calling a procedure with parameter values.\n * @param {!string} paramName Name of parameter.\n * @param {*} value Value to set for parameter.\n */\n pushParam (paramName, value) {\n const stackFrame = this.peekStackFrame();\n stackFrame.params[paramName] = value;\n }\n\n /**\n * Get a parameter at the lowest possible level of the stack.\n * @param {!string} paramName Name of parameter.\n * @return {*} value Value for parameter.\n */\n getParam (paramName) {\n for (let i = this.stackFrames.length - 1; i >= 0; i--) {\n const frame = this.stackFrames[i];\n if (frame.params === null) {\n continue;\n }\n if (frame.params.hasOwnProperty(paramName)) {\n return frame.params[paramName];\n }\n return null;\n }\n return null;\n }\n\n /**\n * Whether the current execution of a thread is at the top of the stack.\n * @return {boolean} True if execution is at top of the stack.\n */\n atStackTop () {\n return this.peekStack() === this.topBlock;\n }\n\n\n /**\n * Switch the thread to the next block at the current level of the stack.\n * For example, this is used in a standard sequence of blocks,\n * where execution proceeds from one block to the next.\n */\n goToNextBlock () {\n const nextBlockId = this.target.blocks.getNextBlock(this.peekStack());\n this.reuseStackForNextBlock(nextBlockId);\n }\n\n /**\n * Attempt to determine whether a procedure call is recursive,\n * by examining the stack.\n * @param {!string} procedureCode Procedure code of procedure being called.\n * @return {boolean} True if the call appears recursive.\n */\n isRecursiveCall (procedureCode) {\n let callCount = 5; // Max number of enclosing procedure calls to examine.\n const sp = this.stack.length - 1;\n for (let i = sp - 1; i >= 0; i--) {\n const block = this.target.blocks.getBlock(this.stack[i]);\n if (block.opcode === 'procedures_call' &&\n block.mutation.proccode === procedureCode) {\n return true;\n }\n if (--callCount < 0) return false;\n }\n return false;\n }\n}\n\nmodule.exports = Thread;\n","/**\n * @fileoverview\n * Object representing a Scratch variable.\n */\n\nconst uid = require('../util/uid');\nconst xmlEscape = require('../util/xml-escape');\n\nclass Variable {\n /**\n * @param {string} id Id of the variable.\n * @param {string} name Name of the variable.\n * @param {string} type Type of the variable, one of '' or 'list'\n * @param {boolean} isCloud Whether the variable is stored in the cloud.\n * @constructor\n */\n constructor (id, name, type, isCloud) {\n this.id = id || uid();\n this.name = name;\n this.type = type;\n this.isCloud = isCloud;\n switch (this.type) {\n case Variable.SCALAR_TYPE:\n this.value = 0;\n break;\n case Variable.LIST_TYPE:\n this.value = [];\n break;\n case Variable.BROADCAST_MESSAGE_TYPE:\n this.value = this.name;\n break;\n default:\n throw new Error(`Invalid variable type: ${this.type}`);\n }\n }\n\n toXML (isLocal) {\n isLocal = (isLocal === true);\n return `${xmlEscape(this.name)}`;\n }\n\n /**\n * Type representation for scalar variables.\n * This is currently represented as ''\n * for compatibility with blockly.\n * @const {string}\n */\n static get SCALAR_TYPE () {\n return '';\n }\n\n /**\n * Type representation for list variables.\n * @const {string}\n */\n static get LIST_TYPE () {\n return 'list';\n }\n\n /**\n * Type representation for list variables.\n * @const {string}\n */\n static get BROADCAST_MESSAGE_TYPE () {\n return 'broadcast_msg';\n }\n}\n\nmodule.exports = Variable;\n","/**\n * Block argument types\n * @enum {string}\n */\nconst ArgumentType = {\n /**\n * Numeric value with angle picker\n */\n ANGLE: 'angle',\n\n /**\n * Boolean value with hexagonal placeholder\n */\n BOOLEAN: 'Boolean',\n\n /**\n * Numeric value with color picker\n */\n COLOR: 'color',\n\n /**\n * Numeric value with text field\n */\n NUMBER: 'number',\n\n /**\n * String value with text field\n */\n STRING: 'string',\n\n /**\n * String value with matrix field\n */\n MATRIX: 'matrix',\n\n /**\n * MIDI note number with note picker (piano) field\n */\n NOTE: 'note',\n\n /**\n * Inline image on block (as part of the label)\n */\n IMAGE: 'image'\n};\n\nmodule.exports = ArgumentType;\n","/**\n * Types of block\n * @enum {string}\n */\nconst BlockType = {\n /**\n * Boolean reporter with hexagonal shape\n */\n BOOLEAN: 'Boolean',\n\n /**\n * A button (not an actual block) for some special action, like making a variable\n */\n BUTTON: 'button',\n\n /**\n * Command block\n */\n COMMAND: 'command',\n\n /**\n * Specialized command block which may or may not run a child branch\n * The thread continues with the next block whether or not a child branch ran.\n */\n CONDITIONAL: 'conditional',\n\n /**\n * Specialized hat block with no implementation function\n * This stack only runs if the corresponding event is emitted by other code.\n */\n EVENT: 'event',\n\n /**\n * Hat block which conditionally starts a block stack\n */\n HAT: 'hat',\n\n /**\n * Specialized command block which may or may not run a child branch\n * If a child branch runs, the thread evaluates the loop block again.\n */\n LOOP: 'loop',\n\n /**\n * General reporter with numeric or string value\n */\n REPORTER: 'reporter'\n};\n\nmodule.exports = BlockType;\n","const dispatch = require('../dispatch/central-dispatch');\nconst log = require('../util/log');\nconst maybeFormatMessage = require('../util/maybe-format-message');\n\nconst BlockType = require('./block-type');\n\n// These extensions are currently built into the VM repository but should not be loaded at startup.\n// TODO: move these out into a separate repository?\n// TODO: change extension spec so that library info, including extension ID, can be collected through static methods\n\nconst builtinExtensions = {\n // This is an example that isn't loaded with the other core blocks,\n // but serves as a reference for loading core blocks as extensions.\n coreExample: () => require('../blocks/scratch3_core_example'),\n // These are the non-core built-in extensions.\n pen: () => require('../extensions/scratch3_pen'),\n wedo2: () => require('../extensions/scratch3_wedo2'),\n music: () => require('../extensions/scratch3_music'),\n microbit: () => require('../extensions/scratch3_microbit'),\n text2speech: () => require('../extensions/scratch3_text2speech'),\n translate: () => require('../extensions/scratch3_translate'),\n videoSensing: () => require('../extensions/scratch3_video_sensing'),\n ev3: () => require('../extensions/scratch3_ev3'),\n makeymakey: () => require('../extensions/scratch3_makeymakey'),\n boost: () => require('../extensions/scratch3_boost'),\n gdxfor: () => require('../extensions/scratch3_gdx_for')\n};\n\nbuiltinExtensions.microbitMore = () => require('../extensions/microbitMore');\n\n/**\n * @typedef {object} ArgumentInfo - Information about an extension block argument\n * @property {ArgumentType} type - the type of value this argument can take\n * @property {*|undefined} default - the default value of this argument (default: blank)\n */\n\n/**\n * @typedef {object} ConvertedBlockInfo - Raw extension block data paired with processed data ready for scratch-blocks\n * @property {ExtensionBlockMetadata} info - the raw block info\n * @property {object} json - the scratch-blocks JSON definition for this block\n * @property {string} xml - the scratch-blocks XML definition for this block\n */\n\n/**\n * @typedef {object} CategoryInfo - Information about a block category\n * @property {string} id - the unique ID of this category\n * @property {string} name - the human-readable name of this category\n * @property {string|undefined} blockIconURI - optional URI for the block icon image\n * @property {string} color1 - the primary color for this category, in '#rrggbb' format\n * @property {string} color2 - the secondary color for this category, in '#rrggbb' format\n * @property {string} color3 - the tertiary color for this category, in '#rrggbb' format\n * @property {Array.} blocks - the blocks, separators, etc. in this category\n * @property {Array.} menus - the menus provided by this category\n */\n\n/**\n * @typedef {object} PendingExtensionWorker - Information about an extension worker still initializing\n * @property {string} extensionURL - the URL of the extension to be loaded by this worker\n * @property {Function} resolve - function to call on successful worker startup\n * @property {Function} reject - function to call on failed worker startup\n */\n\nclass ExtensionManager {\n constructor (runtime) {\n /**\n * The ID number to provide to the next extension worker.\n * @type {int}\n */\n this.nextExtensionWorker = 0;\n\n /**\n * FIFO queue of extensions which have been requested but not yet loaded in a worker,\n * along with promise resolution functions to call once the worker is ready or failed.\n *\n * @type {Array.}\n */\n this.pendingExtensions = [];\n\n /**\n * Map of worker ID to workers which have been allocated but have not yet finished initialization.\n * @type {Array.}\n */\n this.pendingWorkers = [];\n\n /**\n * Set of loaded extension URLs/IDs (equivalent for built-in extensions).\n * @type {Set.}\n * @private\n */\n this._loadedExtensions = new Map();\n\n /**\n * Keep a reference to the runtime so we can construct internal extension objects.\n * TODO: remove this in favor of extensions accessing the runtime as a service.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n dispatch.setService('extensions', this).catch(e => {\n log.error(`ExtensionManager was unable to register extension service: ${JSON.stringify(e)}`);\n });\n }\n\n /**\n * Check whether an extension is registered or is in the process of loading. This is intended to control loading or\n * adding extensions so it may return `true` before the extension is ready to be used. Use the promise returned by\n * `loadExtensionURL` if you need to wait until the extension is truly ready.\n * @param {string} extensionID - the ID of the extension.\n * @returns {boolean} - true if loaded, false otherwise.\n */\n isExtensionLoaded (extensionID) {\n return this._loadedExtensions.has(extensionID);\n }\n\n /**\n * Synchronously load an internal extension (core or non-core) by ID. This call will\n * fail if the provided id is not does not match an internal extension.\n * @param {string} extensionId - the ID of an internal extension\n */\n loadExtensionIdSync (extensionId) {\n if (!builtinExtensions.hasOwnProperty(extensionId)) {\n log.warn(`Could not find extension ${extensionId} in the built in extensions.`);\n return;\n }\n\n /** @TODO dupe handling for non-builtin extensions. See commit 670e51d33580e8a2e852b3b038bb3afc282f81b9 */\n if (this.isExtensionLoaded(extensionId)) {\n const message = `Rejecting attempt to load a second extension with ID ${extensionId}`;\n log.warn(message);\n return;\n }\n\n const extension = builtinExtensions[extensionId]();\n const extensionInstance = new extension(this.runtime);\n const serviceName = this._registerInternalExtension(extensionInstance);\n this._loadedExtensions.set(extensionId, serviceName);\n }\n\n /**\n * Load an extension by URL or internal extension ID\n * @param {string} extensionURL - the URL for the extension to load OR the ID of an internal extension\n * @returns {Promise} resolved once the extension is loaded and initialized or rejected on failure\n */\n loadExtensionURL (extensionURL) {\n if (builtinExtensions.hasOwnProperty(extensionURL)) {\n /** @TODO dupe handling for non-builtin extensions. See commit 670e51d33580e8a2e852b3b038bb3afc282f81b9 */\n if (this.isExtensionLoaded(extensionURL)) {\n const message = `Rejecting attempt to load a second extension with ID ${extensionURL}`;\n log.warn(message);\n return Promise.resolve();\n }\n\n const extension = builtinExtensions[extensionURL]();\n const extensionInstance = new extension(this.runtime);\n const serviceName = this._registerInternalExtension(extensionInstance);\n this._loadedExtensions.set(extensionURL, serviceName);\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n // If we `require` this at the global level it breaks non-webpack targets, including tests\n const ExtensionWorker = require('worker-loader?name=extension-worker.js!./extension-worker');\n\n this.pendingExtensions.push({extensionURL, resolve, reject});\n dispatch.addWorker(new ExtensionWorker());\n });\n }\n\n /**\n * Regenerate blockinfo for any loaded extensions\n * @returns {Promise} resolved once all the extensions have been reinitialized\n */\n refreshBlocks () {\n const allPromises = Array.from(this._loadedExtensions.values()).map(serviceName =>\n dispatch.call(serviceName, 'getInfo')\n .then(info => {\n info = this._prepareExtensionInfo(serviceName, info);\n dispatch.call('runtime', '_refreshExtensionPrimitives', info);\n })\n .catch(e => {\n log.error(`Failed to refresh built-in extension primitives: ${JSON.stringify(e)}`);\n })\n );\n return Promise.all(allPromises);\n }\n\n allocateWorker () {\n const id = this.nextExtensionWorker++;\n const workerInfo = this.pendingExtensions.shift();\n this.pendingWorkers[id] = workerInfo;\n return [id, workerInfo.extensionURL];\n }\n\n /**\n * Synchronously collect extension metadata from the specified service and begin the extension registration process.\n * @param {string} serviceName - the name of the service hosting the extension.\n */\n registerExtensionServiceSync (serviceName) {\n const info = dispatch.callSync(serviceName, 'getInfo');\n this._registerExtensionInfo(serviceName, info);\n }\n\n /**\n * Collect extension metadata from the specified service and begin the extension registration process.\n * @param {string} serviceName - the name of the service hosting the extension.\n */\n registerExtensionService (serviceName) {\n dispatch.call(serviceName, 'getInfo').then(info => {\n this._registerExtensionInfo(serviceName, info);\n });\n }\n\n /**\n * Called by an extension worker to indicate that the worker has finished initialization.\n * @param {int} id - the worker ID.\n * @param {*?} e - the error encountered during initialization, if any.\n */\n onWorkerInit (id, e) {\n const workerInfo = this.pendingWorkers[id];\n delete this.pendingWorkers[id];\n if (e) {\n workerInfo.reject(e);\n } else {\n workerInfo.resolve(id);\n }\n }\n\n /**\n * Register an internal (non-Worker) extension object\n * @param {object} extensionObject - the extension object to register\n * @returns {string} The name of the registered extension service\n */\n _registerInternalExtension (extensionObject) {\n const extensionInfo = extensionObject.getInfo();\n const fakeWorkerId = this.nextExtensionWorker++;\n const serviceName = `extension_${fakeWorkerId}_${extensionInfo.id}`;\n dispatch.setServiceSync(serviceName, extensionObject);\n dispatch.callSync('extensions', 'registerExtensionServiceSync', serviceName);\n return serviceName;\n }\n\n /**\n * Sanitize extension info then register its primitives with the VM.\n * @param {string} serviceName - the name of the service hosting the extension\n * @param {ExtensionInfo} extensionInfo - the extension's metadata\n * @private\n */\n _registerExtensionInfo (serviceName, extensionInfo) {\n extensionInfo = this._prepareExtensionInfo(serviceName, extensionInfo);\n dispatch.call('runtime', '_registerExtensionPrimitives', extensionInfo).catch(e => {\n log.error(`Failed to register primitives for extension on service ${serviceName}:`, e);\n });\n }\n\n /**\n * Modify the provided text as necessary to ensure that it may be used as an attribute value in valid XML.\n * @param {string} text - the text to be sanitized\n * @returns {string} - the sanitized text\n * @private\n */\n _sanitizeID (text) {\n return text.toString().replace(/[<\"&]/, '_');\n }\n\n /**\n * Apply minor cleanup and defaults for optional extension fields.\n * TODO: make the ID unique in cases where two copies of the same extension are loaded.\n * @param {string} serviceName - the name of the service hosting this extension block\n * @param {ExtensionInfo} extensionInfo - the extension info to be sanitized\n * @returns {ExtensionInfo} - a new extension info object with cleaned-up values\n * @private\n */\n _prepareExtensionInfo (serviceName, extensionInfo) {\n extensionInfo = Object.assign({}, extensionInfo);\n if (!/^[a-z0-9]+$/i.test(extensionInfo.id)) {\n throw new Error('Invalid extension id');\n }\n extensionInfo.name = extensionInfo.name || extensionInfo.id;\n extensionInfo.blocks = extensionInfo.blocks || [];\n extensionInfo.targetTypes = extensionInfo.targetTypes || [];\n extensionInfo.blocks = extensionInfo.blocks.reduce((results, blockInfo) => {\n try {\n let result;\n switch (blockInfo) {\n case '---': // separator\n result = '---';\n break;\n default: // an ExtensionBlockMetadata object\n result = this._prepareBlockInfo(serviceName, blockInfo);\n break;\n }\n results.push(result);\n } catch (e) {\n // TODO: more meaningful error reporting\n log.error(`Error processing block: ${e.message}, Block:\\n${JSON.stringify(blockInfo)}`);\n }\n return results;\n }, []);\n extensionInfo.menus = extensionInfo.menus || {};\n extensionInfo.menus = this._prepareMenuInfo(serviceName, extensionInfo.menus);\n return extensionInfo;\n }\n\n /**\n * Prepare extension menus. e.g. setup binding for dynamic menu functions.\n * @param {string} serviceName - the name of the service hosting this extension block\n * @param {Array.} menus - the menu defined by the extension.\n * @returns {Array.} - a menuInfo object with all preprocessing done.\n * @private\n */\n _prepareMenuInfo (serviceName, menus) {\n const menuNames = Object.getOwnPropertyNames(menus);\n for (let i = 0; i < menuNames.length; i++) {\n const menuName = menuNames[i];\n let menuInfo = menus[menuName];\n\n // If the menu description is in short form (items only) then normalize it to general form: an object with\n // its items listed in an `items` property.\n if (!menuInfo.items) {\n menuInfo = {\n items: menuInfo\n };\n menus[menuName] = menuInfo;\n }\n // If `items` is a string, it should be the name of a function in the extension object. Calling the\n // function should return an array of items to populate the menu when it is opened.\n if (typeof menuInfo.items === 'string') {\n const menuItemFunctionName = menuInfo.items;\n const serviceObject = dispatch.services[serviceName];\n // Bind the function here so we can pass a simple item generation function to Scratch Blocks later.\n menuInfo.items = this._getExtensionMenuItems.bind(this, serviceObject, menuItemFunctionName);\n }\n }\n return menus;\n }\n\n /**\n * Fetch the items for a particular extension menu, providing the target ID for context.\n * @param {object} extensionObject - the extension object providing the menu.\n * @param {string} menuItemFunctionName - the name of the menu function to call.\n * @returns {Array} menu items ready for scratch-blocks.\n * @private\n */\n _getExtensionMenuItems (extensionObject, menuItemFunctionName) {\n // Fetch the items appropriate for the target currently being edited. This assumes that menus only\n // collect items when opened by the user while editing a particular target.\n const editingTarget = this.runtime.getEditingTarget() || this.runtime.getTargetForStage();\n const editingTargetID = editingTarget ? editingTarget.id : null;\n const extensionMessageContext = this.runtime.makeMessageContextForTarget(editingTarget);\n\n // TODO: Fix this to use dispatch.call when extensions are running in workers.\n const menuFunc = extensionObject[menuItemFunctionName];\n const menuItems = menuFunc.call(extensionObject, editingTargetID).map(\n item => {\n item = maybeFormatMessage(item, extensionMessageContext);\n switch (typeof item) {\n case 'object':\n return [\n maybeFormatMessage(item.text, extensionMessageContext),\n item.value\n ];\n case 'string':\n return [item, item];\n default:\n return item;\n }\n });\n\n if (!menuItems || menuItems.length < 1) {\n throw new Error(`Extension menu returned no items: ${menuItemFunctionName}`);\n }\n return menuItems;\n }\n\n /**\n * Apply defaults for optional block fields.\n * @param {string} serviceName - the name of the service hosting this extension block\n * @param {ExtensionBlockMetadata} blockInfo - the block info from the extension\n * @returns {ExtensionBlockMetadata} - a new block info object which has values for all relevant optional fields.\n * @private\n */\n _prepareBlockInfo (serviceName, blockInfo) {\n blockInfo = Object.assign({}, {\n blockType: BlockType.COMMAND,\n terminal: false,\n blockAllThreads: false,\n arguments: {}\n }, blockInfo);\n blockInfo.opcode = blockInfo.opcode && this._sanitizeID(blockInfo.opcode);\n blockInfo.text = blockInfo.text || blockInfo.opcode;\n\n switch (blockInfo.blockType) {\n case BlockType.EVENT:\n if (blockInfo.func) {\n log.warn(`Ignoring function \"${blockInfo.func}\" for event block ${blockInfo.opcode}`);\n }\n break;\n case BlockType.BUTTON:\n if (blockInfo.opcode) {\n log.warn(`Ignoring opcode \"${blockInfo.opcode}\" for button with text: ${blockInfo.text}`);\n }\n break;\n default: {\n if (!blockInfo.opcode) {\n throw new Error('Missing opcode for block');\n }\n\n const funcName = blockInfo.func ? this._sanitizeID(blockInfo.func) : blockInfo.opcode;\n\n const getBlockInfo = blockInfo.isDynamic ?\n args => args && args.mutation && args.mutation.blockInfo :\n () => blockInfo;\n const callBlockFunc = (() => {\n if (dispatch._isRemoteService(serviceName)) {\n return (args, util, realBlockInfo) =>\n dispatch.call(serviceName, funcName, args, util, realBlockInfo);\n }\n\n // avoid promise latency if we can call direct\n const serviceObject = dispatch.services[serviceName];\n if (!serviceObject[funcName]) {\n // The function might show up later as a dynamic property of the service object\n log.warn(`Could not find extension block function called ${funcName}`);\n }\n return (args, util, realBlockInfo) =>\n serviceObject[funcName](args, util, realBlockInfo);\n })();\n\n blockInfo.func = (args, util) => {\n const realBlockInfo = getBlockInfo(args);\n // TODO: filter args using the keys of realBlockInfo.arguments? maybe only if sandboxed?\n return callBlockFunc(args, util, realBlockInfo);\n };\n break;\n }\n }\n\n return blockInfo;\n }\n}\n\nmodule.exports = ExtensionManager;\n","/**\n * Default types of Target supported by the VM\n * @enum {string}\n */\nconst TargetType = {\n /**\n * Rendered target which can move, change costumes, etc.\n */\n SPRITE: 'sprite',\n\n /**\n * Rendered target which cannot move but can change backdrops\n */\n STAGE: 'stage'\n};\n\nmodule.exports = TargetType;\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\n// const log = require('../../util/log');\nconst cast = require('../../util/cast');\nconst BLE = require('../../io/ble');\nconst Base64Util = require('../../util/base64-util');\n\n\n/**\n * Formatter which is used for translating.\n * When it was loaded as a module, 'formatMessage' will be replaced which is used in the runtime.\n * @type {Function}\n */\nlet formatMessage = require('format-message');\n\nconst EXTENSION_ID = 'microbitMore';\n\n/**\n * URL to get this extension as a module.\n * When it was loaded as a module, 'extensionURL' will be replaced a URL which is retrieved from.\n * @type {string}\n */\nlet extensionURL = 'https://yokobond.github.io/mbit-more-v2/dev/dist/microbitMore.mjs';\n\n/**\n * Icon png to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAErmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjQwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iNDAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSI0MCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iNDAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjcyLjAiCiAgIHRpZmY6WVJlc29sdXRpb249IjcyLjAiCiAgIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiCiAgIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjEtMDMtMTBUMTE6NTE6MzgrMDk6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjEtMDMtMTBUMTE6NTE6MzgrMDk6MDAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJwcm9kdWNlZCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iRGVzaWduZXIgaVBhZCAxLjkuMSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wMy0xMFQxMTo1MTozOCswOTowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+CHKf4QAAAYJpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHLS0JBFIc/tehlFBTRooWEtdKwAqtNkBIWSIgZ9NrozUfg43KvEdE2aCsURG16LeovqG3QOgiKIoh2QeuiNiW3czUwIs9w5nzzmzmHmTNgjaSVjF7jgUw2r4UDPsfs3Lyj7hkbDXQwhD2q6OpYKBSkqn3cYTHjjdusVf3cv9a0FNcVsNQLjyqqlheeEA6u5lWTt4XblVR0SfhU2KXJBYVvTT1W5heTk2X+MlmLhP1gbRV2JH9x7BcrKS0jLC/HmUmvKD/3MV9ij2dnpiV2i3ehEyaADweTjOPHSz8jMntxM0CfrKiS7ynlT5GTXEVmlTU0lkmSIo9L1BWpHpeYED0uI82a2f+/fdUTgwPl6nYf1D4ZxlsP1G1BsWAYn4eGUTwC2yNcZCv5uQMYfhe9UNGc+9CyAWeXFS22A+eb0PmgRrVoSbKJWxMJeD2B5jlou4bGhXLPfvY5vofIunzVFezuQa+cb1n8BlPUZ91ko37dAAAACXBIWXMAAAsTAAALEwEAmpwYAAAJXElEQVRYhe2XW2wc5RXHf+eb2dmb144dx0kcQi4QTJprMRcnESni2halacMtUIEUib6lfejtAREh8UBRLyqiVRHqQ9UbEpSqVUGiShuo2iQ4QI0Fde4JTpM4dmLHu+u9zc7Md/qwaycbh5KUPuYvjXZGc76Z35zbdxau6Iqu6L9KPu0DXnjhZ9e3NGd+MnL6dKei2Mgax3HsjBktQ8Vi6Ttbt36j//8C+OKT6bZ0Un/X1mxvsoo532h8wkQnRpxoxbWBB2AtumfAq/QsryZf/GOmsP94S8pai4gYEXGstYExouVyJaeq0WXwRMAfgG1DQ0MVAHfyztVzor2f76nMvnDFyFmHzdtm8q1HJrh3bYUwErb+aAbzO6KmXR/E6TsQS0PpwmWJ+m/zZcBN6tvArUBPA+Ctq/xpcP8ecdi8rZ1jww4AlarwtWdbefO9BOmEUqx86gz5ON08eTIFGHMbLQ4dd3lo20xGztbgJkrCV5+aSe+ABzANrq3Z0t4S1W0Np8acTwMo0078t0W9mALw4ZEYjzw1k7P5c6k46bGG5AQs8OX1RZYujDiTdQGhJQN+YPjpK/GL2l+KhoaGBM7z4KTe2evx2NMzmSg1emgSbtoCYN2qFK+8meDDQ1X8QFmzOs6X1hky8TK+32gbXgbkNMC/9cV5/HttlP3puTUJ5wKuKg4QihABh443cfstLtct9ECE1maXIIoR+WXiQIsnxJLNjOZylw05RfL7Hyb1m8/PIAg/Hi4GpK2lCfDqLyoABWO4an6cjo44YCgUQg4fKhCPlIwqLSlDKXAYDwJ8YyhfAuRkiKdorprXqVYvbmzqQGlVZqmy1Fp6wpB/Og4DjsNEPE4+CAhEUBFca8m4Li1BwKIoYl0YMmAMfa7LKFA2htIlAk7l8MfBnQ8ZVyUJ3FGpsKGpiUcLBbpF+PrDD5NS5a61a3n0/vvJqPLYhg3csWABW4pFNqbTfKFSoZlag7ywcABWXBOw+c4SC+aE0977iZoMsRGhOYpIWkshlyMdRWwaHye3YwfzVBk5cIAj775Lhyr53l5u27ePtjCkmM+TsJZ0GGLqz7vwxTd0VZk/O+L4SGMZNlyJCKqKSC3yU+eqU8aiiqhi6oXSYi13Hj3KcWCoWiVZLtMaRdw7OEgqCBDA1Ne4QEKViQvgRIRcweHXf27CamMo3fONMpkM3d3dlEq1rcvzPHp7e4mCoGasiuu6uCKkk0nOBAFJEdo9jwezWd5pbyfd0cGi/fvpzGTwYzHOiDArmSQWBDiOg0pjL+3u7iaVSnHKr7J2bYzh4WEOHz7M6tWrpb+/Xw3A6tWrBSCVStHe3s7o6CjZbJa2tjZc18WpezQSwbGWCBjP5cg5Dm/G45zM5XCt5Ya+PhZt305zFDE2McFbrsvpWIxsPk9oTM3znCsOEWHWrFkYYxgcHCQejzNr1ixEBKmHcepjVDVS1aBQKLBz50527dpFEARTYTd1D1YBBZoyGUJVhuJxPurpoWQMbZ7HvHicCBhYtowzHR1UrSWVToMqoT1Xt5NpFIYhfX199Pf3s2/fPqIoAihfLMSOMcZpbm6mu7sbz/NIJBLnHqbK+fWlIgTGkHUcxufOZWdTE1uApDG80dRES2cnhSNH8EUQU/eDMUzOXqoKIniex8qVKzHG0NXVhV/bepINgP39/Tpv3jyKxSLZbJYlS5YAkM/nCcNzWJEITo2YXC6H9TwqQcCrO3bgOg7ftxYHOBuLEb79NjOA6+u26tSGBysy5RVVZWxsjHQ6zapVqzDGcPbs2Rp8LVDnPKiqTExMsH37du68qcJf301MgTmq2PpvVA9Ne1sbB4tFiMfZ9MAD/Pyll+js6WFORwcHX3+dTRs3cnpgALt3L21tbYzm82g9TUI5t1vt2bNnKtxTngXef/99hQvakarylc+VuOeWytT15ILJ3qWqoErJ91FVSkHA4WPHKBvD4ZER9p48Sd4Yjp48yej4OJEqlbqt1j/0Qk2+Q3X6btHQBzMpJZ20bN8Tn2YcAn59OIhU8YMAay2+tRwaHMQHTmez5HyfCnDo2DGaSqUp26juffsJkBeqwYMTJeHXb6TZ/k6iwcjWD1QJRAiNobW1laoxOOk0Gx96iArw2TVruGfDBqrA3Rs3sqCri6rj0NraSmQMvgiBXN4UfrHx7qKyQEmELLDL80iXy+z2PIbLZX7z8ssA7Nq1i2QyiQX+9NprSD7PhONwdaXCHs+jIEKV/3Hc6uzs1GRcWblEw3IFBj4yblTvCQZIuLB5fUxTEka9uyO3WoQqkLWWguvieLDhrphai339rdAJK0qTtbRQ7xkerF/naDXm2ld2hk6+ci7Urgv33R7TdEp49S9VyRcvMs0ArFmBn/SCxc2p4MYbl+rUR1pg010xzefKmxYNZpfdvN4Nh4BTIhRdlyrwwBc9e2K4vD4MKtdtvN2JQqBkDKdFGALW3WZscahyT2okf8N9dzn2fA8+fE9MrYaPp7zKZ7Zs9Br+pjYAdl3j6BOPjZ08NR59tHBe47zRvcKo1fLbm/9RHLyuy9WSMQQi+PWQXX+N4YMDlXef+9XI0SWLHA2pebgsQskYFi9JaNdAfvfWQ+P7Fs93Gyri+kWO/mv/6G+feG54//w5puGeAGTf6/H2HhzyR7MGvxKFXkyNl8yYdDxid38VEVizyiOTVg0DV11XzI53fMIIUDBGWLtKiMcCDdXFWld291eJIgUEY6BneYx4LFA1ilVPdvZViVQQDDcvC0gmRIPQs81p65SrkDGH3eUPErkAJ472hk2JTob9kKTnu2ot1q+QSFqaUx6OY6hWAqwXyeFjoSyc59GaCqhGBlWIGYuNBEskY1klk0nQkrJ1wBDH8VAtcWAwkLGs4aYVHjNSFUIbQ4gYzzp8MBxIoWyd665OsPQaA8F5O8nyB7F//0UUQDU2t7WKqnI6KxgsYkPEuKBK3KmycLZFbC1NRAVsFYwLeKhWaE66qPq1GdLWxjQxAaoei+f6LJ6r9fE9RNQHNWQSworFIUFk8GIFxrItwW1bajU01WaMY67t3x/8svcD5zlrZfa184MfGCOJU6NaEomw1jPvH0w9i+rQzJbw+eOnqtbWIogQIIJMlJPfRUysKVF95sRI1aoFkdphcSVfSD4ZWbS9JXjmxHBgVQWRiLgXc89OpH4Munf2TPv07JlyN1d0RVd0afoP68aPxQiA3SAAAAAASUVORK5CYII=';\n\n/**\n * Enum for version of the hardware.\n * @readonly\n * @enum {number}\n */\nconst MbitMoreHardwareVersion =\n{\n MICROBIT_V1: 1,\n MICROBIT_V2: 2\n};\n\n/**\n * Enum for micro:bit BLE command protocol.\n * https://github.com/LLK/scratch-microbit-firmware/blob/master/protocol.md\n * @readonly\n * @enum {number}\n */\nconst BLECommand = {\n CMD_CONFIG: 0x00,\n CMD_PIN: 0x01,\n CMD_DISPLAY: 0x02,\n CMD_AUDIO: 0x03,\n CMD_DATA: 0x04\n};\n\n/**\n * Enum for command about gpio pins.\n * @readonly\n * @enum {number}\n */\nconst MbitMorePinCommand =\n{\n SET_OUTPUT: 0x01,\n SET_PWM: 0x02,\n SET_SERVO: 0x03,\n SET_PULL: 0x04,\n SET_EVENT: 0x05\n};\n\n/**\n * Enum for command about gpio pins.\n * @readonly\n * @enum {number}\n */\nconst MbitMoreDisplayCommand =\n{\n CLEAR: 0x00,\n TEXT: 0x01,\n PIXELS_0: 0x02,\n PIXELS_1: 0x03\n};\n\n/**\n * Enum for name of pull mode.\n * @readonly\n * @enum {number}\n */\nconst MbitMorePullModeName = {\n NONE: 'NONE',\n DOWN: 'DOWN',\n UP: 'UP'\n};\n\n/**\n * Enum for ID of pull mode.\n * @readonly\n * @enum {number}\n */\nconst MbitMorePullModeID = {\n NONE: 0,\n DOWN: 1,\n UP: 2\n};\n\n/**\n * Enum for data format.\n * @readonly\n * @enum {number}\n */\nconst MbitMoreDataFormat = {\n CONFIG: 0x10, // not used at this version\n PIN_EVENT: 0x11,\n ACTION_EVENT: 0x12,\n DATA_NUMBER: 0x13,\n DATA_TEXT: 0x14\n};\n\n/**\n * Enum for action event type.\n * @readonly\n * @enum {number}\n */\nconst MbitMoreActionEvent = {\n BUTTON: 0x01,\n GESTURE: 0x02\n};\n\n/**\n * Enum for ID of pin-mode\n * @readonly\n * @enum {string}\n */\nconst MbitMorePinMode = {\n INPUT: 'INPUT',\n OUTPUT: 'OUTPUT',\n PWM: 'PWM',\n SERVO: 'SERVO',\n TOUCH: 'TOUCH'\n};\n\n/**\n * Enum for ID of buttons\n * @readonly\n * @enum {string}\n */\nconst MbitMoreButtonName = {\n P0: 'P0',\n P1: 'P1',\n P2: 'P2',\n A: 'A',\n B: 'B',\n LOGO: 'LOGO'\n};\n\n/**\n * Enum for componentID of buttons\n * @readonly\n * @enum {string}\n */\nconst MbitMoreButtonID = {\n 1: 'A',\n 2: 'B',\n 100: 'P0',\n 101: 'P1',\n 102: 'P2',\n 121: 'LOGO'\n};\n\n/**\n * Enum for index of pin for buttons\n * @readonly\n * @enum {number}\n */\nconst MbitMoreButtonPinIndex = {\n P0: 0,\n P1: 1,\n P2: 2\n};\n\n/**\n * Enum for index in data of button state\n * @readonly\n * @enum {number}\n */\nconst MbitMoreButtonStateIndex = {\n P0: 0,\n P1: 1,\n P2: 2,\n A: 3,\n B: 4,\n LOGO: 5\n};\n\n/**\n * Enum for name of event from button\n * @readonly\n * @enum {string}\n */\nconst MbitMoreButtonEventName = {\n DOWN: 'DOWN',\n UP: 'UP',\n CLICK: 'CLICK',\n LONG_CLICK: 'LONG_CLICK',\n HOLD: 'HOLD',\n DOUBLE_CLICK: 'DOUBLE_CLICK'\n};\n\n/**\n * Enum for ID of event from button\n * @readonly\n * @enum {string}\n */\nconst MbitMoreButtonEventID = {\n 1: 'DOWN',\n 2: 'UP',\n 3: 'CLICK',\n 4: 'LONG_CLICK',\n 5: 'HOLD',\n 6: 'DOUBLE_CLICK'\n};\n\n/**\n * Enum for name of gesture.\n * @readonly\n * @enum {string}\n */\nconst MbitMoreGestureName =\n{\n TILT_UP: 'TILT_UP',\n TILT_DOWN: 'TILT_DOWN',\n TILT_LEFT: 'TILT_LEFT',\n TILT_RIGHT: 'TILT_RIGHT',\n FACE_UP: 'FACE_UP',\n FACE_DOWN: 'FACE_DOWN',\n FREEFALL: 'FREEFALL',\n G3: 'G3',\n G6: 'G6',\n G8: 'G8',\n SHAKE: 'SHAKE'\n};\n\n/**\n * Enum for ID of gesture.\n * @readonly\n * @enum {string}\n */\nconst MbitMoreGestureID =\n{\n 1: 'TILT_UP',\n 2: 'TILT_DOWN',\n 3: 'TILT_LEFT',\n 4: 'TILT_RIGHT',\n 5: 'FACE_UP',\n 6: 'FACE_DOWN',\n 7: 'FREEFALL',\n 8: 'G3',\n 9: 'G6',\n 10: 'G8',\n 11: 'SHAKE'\n};\n\n/**\n * Enum for event type in the micro:bit runtime.\n * @readonly\n * @enum {number}\n */\nconst MbitMorePinEventType = {\n NONE: 0,\n ON_EDGE: 1,\n ON_PULSE: 2,\n ON_TOUCH: 3\n};\n\n/**\n * Enum for event value in the micro:bit runtime.\n * @readonly\n * @enum {number}\n */\nconst MbitMorePinEvent = {\n RISE: 2,\n FALL: 3,\n PULSE_HIGH: 4,\n PULSE_LOW: 5\n};\n\n/**\n * Enum for data type of data-sending.\n * @readonly\n * @enum {number}\n */\nconst MbitMoreSendingDataType = {\n NUMBER: 1,\n TEXT: 2\n};\n\n/**\n * Enum for sub-command about configurations.\n * @readonly\n * @enum {number}\n */\nconst MbitMoreConfig =\n{\n MIC: 0x01,\n TOUCH: 0x02\n};\n\n/**\n * Enum for sub-command about audio.\n * @readonly\n * @enum {number}\n */\nconst MbitMoreAudioCommand =\n{\n STOP_TONE: 0x00,\n PLAY_TONE: 0x01\n};\n\n/**\n * A time interval to wait (in milliseconds) before reporting to the BLE socket\n * that data has stopped coming from the peripheral.\n */\nconst BLETimeout = 4500;\n\n/**\n * A time interval to wait (in milliseconds) while a block that sends a BLE message is running.\n * @type {number}\n */\nconst BLESendInterval = 30;\n\n/**\n * A string to report to the BLE socket when the micro:bit has stopped receiving data.\n * @type {string}\n */\nconst BLEDataStoppedError = 'micro:bit extension stopped receiving data';\n\nconst MM_SERVICE = {\n ID: '0b50f3e4-607f-4151-9091-7d008d6ffc5c',\n COMMAND_CH: '0b500100-607f-4151-9091-7d008d6ffc5c',\n STATE_CH: '0b500101-607f-4151-9091-7d008d6ffc5c',\n MOTION_CH: '0b500102-607f-4151-9091-7d008d6ffc5c',\n PIN_EVENT_CH: '0b500110-607f-4151-9091-7d008d6ffc5c',\n ACTION_EVENT_CH: '0b500111-607f-4151-9091-7d008d6ffc5c',\n ANALOG_IN_CH: [\n '0b500120-607f-4151-9091-7d008d6ffc5c',\n '0b500121-607f-4151-9091-7d008d6ffc5c',\n '0b500122-607f-4151-9091-7d008d6ffc5c'\n ],\n MESSAGE_CH: '0b500130-607f-4151-9091-7d008d6ffc5c'\n};\n\n/**\n * Enum for axis menu options.\n * @readonly\n * @enum {string}\n */\nconst AxisSymbol = {\n X: 'x',\n Y: 'y',\n Z: 'z',\n Absolute: 'absolute'\n};\n\n/**\n * The unit-value of the gravitational acceleration from Micro:bit.\n * @type {number}\n */\nconst G = 1024;\n\n/**\n * Manage communication with a MicroBit peripheral over a Scrath Link client socket.\n */\nclass MbitMore {\n\n /**\n * Construct a MicroBit communication object.\n * @param {Runtime} runtime - the Scratch 3.0 runtime\n * @param {string} extensionId - the id of the extension\n */\n constructor (runtime, extensionId) {\n\n /**\n * The Scratch 3.0 runtime used to trigger the green flag button.\n * @type {Runtime}\n * @private\n */\n this.runtime = runtime;\n\n /**\n * The BluetoothLowEnergy connection socket for reading/writing peripheral data.\n * @type {BLE}\n * @private\n */\n this._ble = null;\n this.runtime.registerPeripheralExtension(extensionId, this);\n\n /**\n * The id of the extension this peripheral belongs to.\n */\n this._extensionId = extensionId;\n\n this.digitalLevel = {};\n this.lightLevel = 0;\n this.temperature = 0;\n this.soundLevel = 0;\n this.pitch = 0;\n this.roll = 0;\n this.acceleration = {\n x: 0,\n y: 0,\n z: 0\n };\n this.compassHeading = 0;\n this.magneticForce = {\n x: 0,\n y: 0,\n z: 0\n };\n\n this.buttonState = {};\n\n /**\n * The most recently received button events for each buttons.\n * @type {Object} - Store of buttons which has events.\n * @private\n */\n this.buttonEvents = {};\n Object.keys(MbitMoreButtonStateIndex).forEach(name => {\n this.buttonEvents[name] = {};\n });\n\n /**\n * The most recently received gesture events.\n * @type {Object } - Store of gesture ID and timestamp.\n * @private\n */\n this.gestureEvents = {};\n\n\n /**\n * The most recently received events for each pin.\n * @type {Object} - Store of pins which has events.\n * @private\n */\n this._pinEvents = {};\n\n /**\n * The most recently received data from micro:bit.\n * @type {Object} - Store of received data\n * @private\n */\n this.receivedData = {};\n\n this.analogIn = [0, 1, 2];\n this.analogValue = [];\n this.analogIn.forEach(pinIndex => {\n this.analogValue[pinIndex] = 0;\n });\n\n this.gpio = [\n 0, 1, 2,\n 8,\n 12, 13, 14, 15, 16\n ];\n this.gpio.forEach(pinIndex => {\n this.digitalLevel[pinIndex] = 0;\n });\n\n /**\n * Interval ID for data reading timeout.\n * @type {number}\n * @private\n */\n this._timeoutID = null;\n\n /**\n * A flag that is true while we are busy sending data to the BLE socket.\n * @type {boolean}\n * @private\n */\n this.bleBusy = true;\n\n /**\n * ID for a timeout which is used to clear the busy flag if it has been\n * true for a long time.\n */\n this.bleBusyTimeoutID = null;\n\n this.reset = this.reset.bind(this);\n this._onConnect = this._onConnect.bind(this);\n this.onNotify = this.onNotify.bind(this);\n\n this.stopTone = this.stopTone.bind(this);\n if (this.runtime) {\n this.runtime.on('PROJECT_STOP_ALL', this.stopTone);\n }\n\n this.analogInUpdateInterval = 100; // milli-seconds\n this.analogInLastUpdated = [Date.now(), Date.now(), Date.now()];\n\n this.initConfig();\n }\n\n /**\n * Initialize configuration of the micro:bit.\n */\n initConfig () {\n this.config = {};\n this.config.mic = false;\n this.config.pinMode = {};\n }\n\n /**\n * Start updating process for micro:bit state and motion.\n */\n startUpdater () {\n if (this.updater) {\n clearTimeout(this.updater);\n }\n if (this.bleAccessWaiting) {\n this.updater = setTimeout(() => this.startUpdater(), 0);\n return;\n }\n this.updateState()\n .then(() => this.updateMotion())\n .finally(() => {\n this.updater = setTimeout(\n () => this.startUpdater(),\n this.microbitUpdateInterval\n );\n });\n }\n\n /**\n * Stop updating process for micro:bit state and motion.\n */\n stopUpdater () {\n clearTimeout(this.updater);\n }\n\n /**\n * @param {string} text - the text to display.\n * @param {number} delay - The time to delay between characters, in milliseconds.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n displayText (text, delay, util) {\n const textLength = Math.min(18, text.length);\n const textData = new Uint8Array(textLength + 1);\n for (let i = 0; i < textLength; i++) {\n textData[i] = text.charCodeAt(i);\n }\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_DISPLAY << 5) | MbitMoreDisplayCommand.TEXT,\n message: new Uint8Array([\n Math.min(255, (Math.max(0, delay) / 10)),\n ...textData\n ])\n }],\n util\n );\n }\n\n /**\n * Send display pixcels command to micro:bit.\n * @param {Array.>} matrix - pattern to display.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n displayPixels (matrix, util) {\n const cmdSet = [\n {\n id: (BLECommand.CMD_DISPLAY << 5) | MbitMoreDisplayCommand.PIXELS_0,\n message: new Uint8Array([\n ...matrix[0],\n ...matrix[1],\n ...matrix[2]\n ])\n },\n {\n id: (BLECommand.CMD_DISPLAY << 5) | MbitMoreDisplayCommand.PIXELS_1,\n message: new Uint8Array([\n ...matrix[3],\n ...matrix[4]\n ])\n }\n ];\n return this.sendCommandSet(cmdSet, util);\n }\n\n /**\n * Set pull mode to the pin.\n * @param {number} pinIndex - index of the pin\n * @param {MbitMorePullModeID} pullMode - pull mode to set\n * @param {BlockUtility} util - utility object provided from the runtime\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n setPullMode (pinIndex, pullMode, util) {\n this.config.pinMode[pinIndex] = MbitMorePinMode.INPUT;\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_PIN << 5) | MbitMorePinCommand.SET_PULL,\n message: new Uint8Array([\n pinIndex,\n pullMode\n ])\n }],\n util\n );\n }\n\n /**\n * Set pin to digital output mode and the level.\n * @param {number} pinIndex - Index of pin.\n * @param {boolean} level - Value in digital (true = High)\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n setPinOutput (pinIndex, level, util) {\n this.config.pinMode[pinIndex] = MbitMorePinMode.OUTPUT;\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_PIN << 5) | MbitMorePinCommand.SET_OUTPUT,\n message: new Uint8Array(\n [\n pinIndex,\n (level ? 1 : 0)\n ]\n )\n }],\n util\n );\n }\n\n /**\n *\n * @param {number} pinIndex - index of the pin\n * @param {number} level - value of analog output [0..1024].\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n setPinPWM (pinIndex, level, util) {\n this.config.pinMode[pinIndex] = MbitMorePinMode.PWM;\n const dataView = new DataView(new ArrayBuffer(2));\n dataView.setUint16(0, level, true);\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_PIN << 5) | MbitMorePinCommand.SET_PWM,\n message: new Uint8Array(\n [\n pinIndex,\n dataView.getUint8(0),\n dataView.getUint8(1)\n ]\n )\n }],\n util\n );\n }\n\n setPinServo (pinIndex, angle, range, center, util) {\n this.config.pinMode[pinIndex] = MbitMorePinMode.SERVO;\n if (!range || range < 0) range = 0;\n if (!center || center < 0) center = 0;\n const dataView = new DataView(new ArrayBuffer(6));\n dataView.setUint16(0, angle, true);\n dataView.setUint16(2, range, true);\n dataView.setUint16(4, center, true);\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_PIN << 5) | MbitMorePinCommand.SET_SERVO,\n message: new Uint8Array(\n [\n pinIndex,\n dataView.getUint8(0),\n dataView.getUint8(1),\n dataView.getUint8(2),\n dataView.getUint8(3),\n dataView.getUint8(4),\n dataView.getUint8(5)\n ]\n )\n }],\n util);\n }\n\n /**\n * Read light level from the light sensor.\n * @param {object} util - utility object provided by the runtime.\n * @return {Promise} - a Promise that resolves light level.\n */\n readLightLevel () {\n if (!this.isConnected()) {\n return 0;\n }\n return this.lightLevel;\n }\n\n /**\n * Update data of the analog input.\n * @param {number} pinIndex - index of the pin to get value.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves value of analog input or undefined if this process was yield.\n */\n readAnalogIn (pinIndex, util) {\n if (!this.isConnected()) {\n return Promise.resolve(0);\n }\n if ((Date.now() - this.analogInLastUpdated[pinIndex]) < this.analogInUpdateInterval) {\n return Promise.resolve(this.analogValue[pinIndex]);\n }\n if (this.bleBusy) {\n this.bleAccessWaiting = true;\n if (util) util.yield(); // re-try this call after a while.\n return; // Do not return Promise.resolve() to re-try.\n }\n this.bleBusy = true;\n this.bleBusyTimeoutID = window.setTimeout(() => {\n this.bleBusy = false;\n this.bleAccessWaiting = false;\n }, 1000);\n return new Promise(resolve => this._ble.read(\n MM_SERVICE.ID,\n MM_SERVICE.ANALOG_IN_CH[pinIndex],\n false)\n .then(result => {\n window.clearTimeout(this.bleBusyTimeoutID);\n this.bleBusy = false;\n this.bleAccessWaiting = false;\n if (!result) {\n return resolve(this.analogValue[pinIndex]);\n }\n const data = Base64Util.base64ToUint8Array(result.message);\n const dataView = new DataView(data.buffer, 0);\n this.analogValue[pinIndex] = dataView.getUint16(0, true);\n this.analogInLastUpdated = Date.now();\n resolve(this.analogValue[pinIndex]);\n })\n );\n }\n\n /**\n * Update data of digital level, light level, temperature, sound level.\n * @return {Promise} - a Promise that resolves updated data holder.\n */\n updateState () {\n if (!this.isConnected()) return Promise.resolve(this);\n if (this.bleBusy) {\n return Promise.resolve(this);\n }\n this.bleBusy = true;\n this.bleBusyTimeoutID = window.setTimeout(() => {\n this.bleBusy = false;\n }, 1000);\n return new Promise(resolve => {\n this._ble.read(\n MM_SERVICE.ID,\n MM_SERVICE.STATE_CH,\n false)\n .then(result => {\n window.clearTimeout(this.bleBusyTimeoutID);\n this.bleBusy = false;\n if (!result) return resolve(this);\n const data = Base64Util.base64ToUint8Array(result.message);\n const dataView = new DataView(data.buffer, 0);\n // Digital Input\n const gpioData = dataView.getUint32(0, true);\n for (let i = 0; i < this.gpio.length; i++) {\n this.digitalLevel[this.gpio[i]] = (gpioData >> this.gpio[i]) & 1;\n }\n Object.keys(MbitMoreButtonStateIndex).forEach(\n name => {\n this.buttonState[name] = (gpioData >> (24 + MbitMoreButtonStateIndex[name])) & 1;\n });\n this.lightLevel = dataView.getUint8(4);\n this.temperature = dataView.getUint8(5) - 128;\n this.soundLevel = dataView.getUint8(6);\n this.resetConnectionTimeout();\n resolve(this);\n });\n });\n }\n\n /**\n * Read temperature (integer in celsius) from the micro:bit cpu.\n * @return {number} - degrees of temperature [centigrade].\n */\n readTemperature () {\n if (!this.isConnected()) {\n return 0;\n }\n return this.temperature;\n }\n\n /**\n * Configurate microphone.\n * @param {boolean} use - true to use microphone.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} - a Promise that resolves state of the microphone or undefined if the process was yield.\n */\n configMic (use, util) {\n use = (use === true);\n if (!this.isConnected()) {\n return Promise.resolve(false);\n }\n if (this.config.mic === use) {\n return Promise.resolve(this.config.mic);\n }\n const sendPromise = this.sendCommandSet(\n [{\n id: (BLECommand.CMD_CONFIG << 5) | MbitMoreConfig.MIC,\n message: new Uint8Array([(use ? 1 : 0)]) // use microphone\n }],\n util\n );\n if (sendPromise) {\n return sendPromise\n .then(() => {\n this.config.mic = use;\n return this.config.mic;\n });\n }\n return;\n }\n\n /**\n * Play tone on the speaker.\n * @param {number} frequency - wave frequency to play [Hz]\n * @param {number} volume laudness of tone [%]\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} - a Promise that resolves to send command or undefined if this process was yield.\n */\n playTone (frequency, volume, util) {\n if (!this.isConnected()) {\n return Promise.resolve();\n }\n const frequencyData = new DataView(new ArrayBuffer(4));\n frequencyData.setUint32(0, Math.round(1000000 / frequency), true);\n volume = Math.round(volume * 0xff / 100);\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_AUDIO << 5) | MbitMoreAudioCommand.PLAY_TONE,\n message: new Uint8Array([\n frequencyData.getUint8(0),\n frequencyData.getUint8(1),\n frequencyData.getUint8(2),\n frequencyData.getUint8(3),\n volume\n ])\n }],\n util\n );\n }\n\n /**\n * Stop playing tone on the speaker.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} - a Promise that resolves to send command or undefined if this process was yield.\n */\n stopTone (util) {\n if (!this.isConnected()) {\n return Promise.resolve();\n }\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_AUDIO << 5) | MbitMoreAudioCommand.STOP_TONE,\n message: new Uint8Array([])\n }],\n util\n );\n }\n\n /**\n * Read sound level.\n * @return {number} - level of loudness (0 .. 255).\n */\n readSoundLevel () {\n if (!this.isConnected()) {\n return 0;\n }\n return this.soundLevel;\n }\n\n /**\n * Update data of acceleration, magnetic force.\n * @return {Promise} - a Promise that resolves updated data holder.\n */\n updateMotion () {\n if (!this.isConnected()) return Promise.resolve(this);\n if (this.bleBusy) {\n return Promise.resolve(this);\n }\n this.bleBusy = true;\n this.bleBusyTimeoutID = window.setTimeout(() => {\n this.bleBusy = false;\n }, 1000);\n return new Promise(resolve => {\n this._ble.read(\n MM_SERVICE.ID,\n MM_SERVICE.MOTION_CH,\n false)\n .then(result => {\n window.clearTimeout(this.bleBusyTimeoutID);\n this.bleBusy = false;\n if (!result) return resolve(this);\n const data = Base64Util.base64ToUint8Array(result.message);\n const dataView = new DataView(data.buffer, 0);\n // Accelerometer\n this.pitch = Math.round(dataView.getInt16(0, true) * 180 / Math.PI / 1000);\n this.roll = Math.round(dataView.getInt16(2, true) * 180 / Math.PI / 1000);\n this.acceleration.x = 1000 * dataView.getInt16(4, true) / G;\n this.acceleration.y = 1000 * dataView.getInt16(6, true) / G;\n this.acceleration.z = 1000 * dataView.getInt16(8, true) / G;\n // Magnetometer\n this.compassHeading = dataView.getUint16(10, true);\n this.magneticForce.x = dataView.getInt16(12, true);\n this.magneticForce.y = dataView.getInt16(14, true);\n this.magneticForce.z = dataView.getInt16(16, true);\n this.resetConnectionTimeout();\n resolve(this);\n });\n });\n }\n\n /**\n * Read pitch [degrees] of the micro:bit heading direction.\n * @return {number} - degree of pitch.\n */\n readPitch () {\n if (!this.isConnected()) {\n return 0;\n }\n return this.pitch;\n }\n\n /**\n * Read roll [degrees] of the micro:bit heading direction.\n * @return {number} - degree of roll.\n */\n readRoll () {\n if (!this.isConnected()) {\n return 0;\n }\n return this.roll;\n }\n\n /**\n * Read the value of gravitational acceleration [milli-g] for the axis.\n * @param {AxisSymbol} axis - direction of acceleration.\n * @return {number} - value of acceleration.\n */\n readAcceleration (axis) {\n if (!this.isConnected()) {\n return 0;\n }\n if (axis === AxisSymbol.Absolute) {\n return Math.round(\n Math.sqrt(\n (this.acceleration.x ** 2) +\n (this.acceleration.y ** 2) +\n (this.acceleration.z ** 2)\n )\n );\n }\n return this.acceleration[axis];\n }\n\n /**\n * Read the angle (degrees) of heading direction from the north.\n * @return {number} - degree of compass heading.\n */\n readCompassHeading () {\n if (!this.isConnected()) {\n return 0;\n }\n return this.compassHeading;\n }\n\n\n /**\n * Read value of magnetic force [micro teslas] for the axis.\n * @param {AxisSymbol} axis - direction of magnetic force.\n * @return {number} - value of magnetic force.\n */\n readMagneticForce (axis) {\n if (!this.isConnected()) {\n return 0;\n }\n if (axis === AxisSymbol.Absolute) {\n return Math.round(\n Math.sqrt(\n (this.magneticForce.x ** 2) +\n (this.magneticForce.y ** 2) +\n (this.magneticForce.z ** 2)\n )\n );\n }\n return this.magneticForce[axis];\n }\n\n /**\n * Called by the runtime when user wants to scan for a peripheral.\n */\n scan () {\n if (this._ble) {\n this._ble.disconnect();\n }\n this.bleBusy = true;\n this._ble = new BLE(this.runtime, this._extensionId, {\n filters: [\n {namePrefix: 'BBC micro:bit'},\n {services: [MM_SERVICE.ID]}\n ]\n }, this._onConnect, this.reset);\n }\n\n /**\n * Called by the runtime when user wants to connect to a certain peripheral.\n * @param {number} id - the id of the peripheral to connect to.\n */\n connect (id) {\n if (this._ble) {\n this._ble.connectPeripheral(id);\n }\n }\n\n /**\n * Disconnect from the micro:bit.\n */\n disconnect () {\n if (this._ble) {\n this._ble.disconnect();\n }\n this.stopUpdater();\n this.reset();\n }\n\n /**\n * Reset all the state and timeout/interval ids.\n */\n reset () {\n if (this._timeoutID) {\n window.clearTimeout(this._timeoutID);\n this._timeoutID = null;\n }\n }\n\n /**\n * Return true if connected to the micro:bit.\n * @return {boolean} - whether the micro:bit is connected.\n */\n isConnected () {\n let connected = false;\n if (this._ble) {\n connected = this._ble.isConnected();\n }\n return connected;\n }\n\n /**\n * Send a command to micro:bit.\n * @param {object} command command to send.\n * @param {number} command.id ID of the command.\n * @param {Uint8Array} command.message Contents of the command.\n * @return {Promise} a Promise that resolves when the data was sent.\n */\n sendCommand (command) {\n const data = Base64Util.uint8ArrayToBase64(\n new Uint8Array([\n command.id,\n ...command.message\n ])\n );\n return new Promise(resolve => {\n this._ble.write(\n MM_SERVICE.ID,\n MM_SERVICE.COMMAND_CH,\n data,\n 'base64',\n false\n );\n setTimeout(() => resolve(), BLESendInterval);\n });\n }\n\n /**\n * Send multiple commands sequentially.\n * @param {Array.<{id: number, message: Uint8Array}>} commands array of command.\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {Promise} a Promise that resolves when the all commands was sent.\n */\n sendCommandSet (commands, util) {\n if (!this.isConnected()) return Promise.resolve();\n if (this.bleBusy) {\n this.bleAccessWaiting = true;\n if (util) {\n util.yield(); // re-try this call after a while.\n } else {\n setTimeout(() => this.sendCommandSet(commands, util), 1);\n }\n return; // Do not return Promise.resolve() to re-try.\n }\n this.bleBusy = true;\n // Clear busy and BLE access waiting flag when the scratch-link does not respond.\n this.bleBusyTimeoutID = window.setTimeout(() => {\n this.bleBusy = false;\n this.bleAccessWaiting = false;\n }, 1000);\n return new Promise(resolve => {\n commands.reduce(\n (acc, cur, i) => {\n const sendPromise = acc.then(() => this.sendCommand(cur));\n if (i === commands.length - 1) {\n // the last command\n sendPromise.then(() => {\n this.bleBusy = false;\n this.bleAccessWaiting = false;\n window.clearTimeout(this.bleBusyTimeoutID);\n resolve();\n });\n }\n return sendPromise;\n },\n Promise.resolve()\n );\n });\n }\n\n /**\n * Starts reading data from peripheral after BLE has connected to it.\n * @private\n */\n _onConnect () {\n this._ble.read(\n MM_SERVICE.ID,\n MM_SERVICE.COMMAND_CH,\n false)\n .then(result => {\n const data = Base64Util.base64ToUint8Array(result.message);\n const dataView = new DataView(data.buffer, 0);\n this.hardware = dataView.getUint8(0);\n this.protocol = dataView.getUint8(1);\n this._ble.startNotifications(\n MM_SERVICE.ID,\n MM_SERVICE.ACTION_EVENT_CH,\n this.onNotify);\n this._ble.startNotifications(\n MM_SERVICE.ID,\n MM_SERVICE.PIN_EVENT_CH,\n this.onNotify);\n if (this.hardware === MbitMoreHardwareVersion.MICROBIT_V1) {\n this.microbitUpdateInterval = 100; // milli-seconds\n } else {\n this._ble.startNotifications(\n MM_SERVICE.ID,\n MM_SERVICE.MESSAGE_CH,\n this.onNotify);\n this.microbitUpdateInterval = 50; // milli-seconds\n }\n this.initConfig();\n this.bleBusy = false;\n this.startUpdater();\n this.resetConnectionTimeout();\n });\n }\n\n /**\n * Process the data from the incoming BLE characteristic.\n * @param {string} msg - the incoming BLE data.\n * @private\n */\n onNotify (msg) {\n const data = Base64Util.base64ToUint8Array(msg);\n const dataView = new DataView(data.buffer, 0);\n const dataFormat = dataView.getUint8(19);\n if (dataFormat === MbitMoreDataFormat.ACTION_EVENT) {\n const actionEventType = dataView.getUint8(0);\n if (actionEventType === MbitMoreActionEvent.BUTTON) {\n const buttonName = MbitMoreButtonID[dataView.getUint16(1, true)];\n const eventName = MbitMoreButtonEventID[dataView.getUint8(3)];\n this.buttonEvents[buttonName][eventName] = dataView.getUint32(4, true); // Timestamp\n } else if (actionEventType === MbitMoreActionEvent.GESTURE) {\n const gestureName = MbitMoreGestureID[dataView.getUint8(1)];\n this.gestureEvents[gestureName] = dataView.getUint32(2, true); // Timestamp\n }\n } else if (dataFormat === MbitMoreDataFormat.PIN_EVENT) {\n const pinIndex = dataView.getUint8(0);\n if (!this._pinEvents[pinIndex]) {\n this._pinEvents[pinIndex] = {};\n }\n const event = dataView.getUint8(1);\n this._pinEvents[pinIndex][event] =\n {\n value: dataView.getUint32(2, true), // timesamp of the edge or duration of the pulse\n timestamp: Date.now() // received time\n };\n } else if (dataFormat === MbitMoreDataFormat.DATA_NUMBER) {\n const label = new TextDecoder().decode(data.slice(0, 8).filter(char => (char !== 0)));\n this.receivedData[label] =\n {\n content: dataView.getFloat32(8, true),\n timestamp: Date.now()\n };\n } else if (dataFormat === MbitMoreDataFormat.DATA_TEXT) {\n const label = new TextDecoder().decode(data.slice(0, 8).filter(char => (char !== 0)));\n this.receivedData[label] =\n {\n content: new TextDecoder().decode(data.slice(8, 20).filter(char => (char !== 0))),\n timestamp: Date.now()\n };\n }\n this.resetConnectionTimeout();\n }\n\n /**\n * Cancel disconnect timeout and start counting again.\n */\n resetConnectionTimeout () {\n if (this._timeoutID) window.clearTimeout(this._timeoutID);\n this._timeoutID = window.setTimeout(() => this._ble.handleDisconnectError(BLEDataStoppedError), BLETimeout);\n }\n\n /**\n * Return whether the pin value is high.\n * @param {number} pin - the pin to check.\n * @return {boolean} - whether the pin is high or not.\n */\n isPinHigh (pin) {\n const level = this.readDigitalLevel(pin);\n return level === 1;\n }\n\n /**\n * Read digital input from the pin.\n * @param {number} pin - the pin to read.\n * @return {number} - digital input value of the pin [0|1].\n */\n readDigitalLevel (pin) {\n if (!this.isConnected()) {\n return 0;\n }\n return this.digitalLevel[pin];\n }\n\n /**\n * Return whether the button is pressed.\n * @param {string} buttonName - name of the button\n * @return {boolean} - true when it is pressed\n */\n isButtonPressed (buttonName) {\n if (!this.isConnected()) {\n return false;\n }\n return this.buttonState[buttonName] === 1;\n }\n\n /**\n * Return whether the pin is touch-mode.\n * @param {number} pinIndex - indesx of the pin\n * @return {boolean} - true when it is touch-mode\n */\n isPinTouchMode (pinIndex) {\n return this.config.pinMode[pinIndex] === MbitMorePinMode.TOUCH;\n }\n\n /**\n * Configurate touch mode of the pin.\n * @param {number} pinIndex - index of the pin as a button.\n * @param {object} util - utility object provided by the runtime.\n * @return {Promise} - a Promise that resolves when configured or the process was yield.\n */\n configTouchPin (pinIndex, util) {\n if (!this.isConnected()) {\n return Promise.resolve();\n }\n if (this.isPinTouchMode(pinIndex)) {\n return Promise.resolve();\n }\n const sendPromise = this.sendCommandSet(\n [{\n id: (BLECommand.CMD_CONFIG << 5) | MbitMoreConfig.TOUCH,\n message: new Uint8Array([\n pinIndex,\n 1\n ])\n }],\n util\n );\n if (sendPromise) {\n return sendPromise\n .then(() => {\n this.config.pinMode[pinIndex] = MbitMorePinMode.TOUCH;\n });\n }\n return Promise.resolve();\n }\n\n /**\n * Return whether the touche-pin is touched.\n * @param {string} buttonName - ID to check.\n * @return {boolean} - whether the id is high or not.\n */\n isTouched (buttonName) {\n if (!this.isConnected()) {\n return false;\n }\n return this.buttonState[buttonName] === 1;\n }\n\n /**\n * Return the last timestamp of the button event or undefined if the event is not received.\n * @param {MbitMoreButtonName} buttonName - name of the button to get the event.\n * @param {MbitMoreButtonEventName} eventName - name of event to get.\n * @return {?number} Timestamp of the last event or null.\n */\n getButtonEventTimestamp (buttonName, eventName) {\n if (this.buttonEvents[buttonName] && this.buttonEvents[buttonName][eventName]) {\n return this.buttonEvents[buttonName][eventName];\n }\n return null;\n }\n\n /**\n * Return the last timestamp of the gesture event or undefined if the event is not received.\n * @param {MbitMoreGestureName} gestureName - name of the event.\n * @return {?number} Timestamp of the last event or null.\n */\n getGestureEventTimestamp (gestureName) {\n if (this.gestureEvents[gestureName]) {\n return this.gestureEvents[gestureName];\n }\n return null;\n }\n\n /**\n * Return the last value of the pin event or undefined if the event was not received.\n * @param {number} pinIndex - index of the pin to get the event.\n * @param {MbitMorePinEvent} event - event to get.\n * @return {?number} Timestamp of the last event or null.\n */\n getPinEventValue (pinIndex, event) {\n if (this._pinEvents[pinIndex] && this._pinEvents[pinIndex][event]) {\n return this._pinEvents[pinIndex][event].value;\n }\n return null;\n }\n\n /**\n * Return the last timestamp of the pin event or undefined if the event was not received.\n * @param {number} pinIndex - index of the pin to get the event.\n * @param {MbitMorePinEvent} event - event to get.\n * @return {?number} Timestamp of the last event or null.\n */\n getPinEventTimestamp (pinIndex, event) {\n if (this._pinEvents[pinIndex] && this._pinEvents[pinIndex][event]) {\n return this._pinEvents[pinIndex][event].timestamp;\n }\n return null;\n }\n\n /**\n * Set event type to be get from the pin.\n * @param {number} pinIndex - Index of the pin to set.\n * @param {MbitMorePinEventType} eventType - Event type to set.\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n listenPinEventType (pinIndex, eventType, util) {\n return this.sendCommandSet(\n [{\n id: (BLECommand.CMD_PIN << 5) | MbitMorePinCommand.SET_EVENT,\n message: new Uint8Array([\n pinIndex,\n eventType\n ])\n }],\n util\n );\n }\n\n /**\n * Send data to micro:bit.\n * @param {string} label - label of the data [ascii]\n * @param {string} content - content of the data [ascii | number]\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when sending done or undefined if this process was yield.\n */\n sendData (label, content, util) {\n const labelData = new Array(8)\n .fill()\n .map((_value, index) => label.charCodeAt(index));\n const contentNumber = Number(content);\n let contentData;\n let type;\n if (Number.isNaN(contentNumber)) {\n type = MbitMoreSendingDataType.TEXT;\n contentData = content\n .split('')\n .map(ascii => ascii.charCodeAt(0))\n .slice(0, 11);\n } else {\n type = MbitMoreSendingDataType.NUMBER;\n const dataView = new DataView(new ArrayBuffer(4));\n dataView.setFloat32(0, contentNumber, true);\n contentData = [\n dataView.getUint8(0),\n dataView.getUint8(1),\n dataView.getUint8(2),\n dataView.getUint8(3)\n ];\n }\n return this.sendCommandSet(\n [{\n id: ((BLECommand.CMD_DATA << 5) | type),\n message: new Uint8Array([\n ...labelData,\n ...contentData])\n }],\n util);\n }\n\n /**\n * Return the last data with the label or undefined if no data received with the label.\n * @param {string} label - label to get.\n * @return {?(number | string)} data of the label or null.\n */\n getDataLabeled (label) {\n if (this.receivedData[label]) {\n return this.receivedData[label].content;\n }\n return null;\n }\n\n /**\n * Return the last timestamp of the data or undefined if the data is not received.\n * @param {string} label - label of the data.\n * @return {?number} Timestamp of the last data or null.\n */\n getDataTimestamp (label) {\n if (this.receivedData[label]) {\n return this.receivedData[label].timestamp;\n }\n return null;\n }\n}\n\n/**\n * Scratch 3.0 blocks to interact with a MicroBit peripheral.\n */\nclass MbitMoreBlocks {\n\n /**\n * @return {string} - the name of this extension.\n */\n static get EXTENSION_NAME () {\n return 'Microbit More';\n }\n\n /**\n * @return {string} - the ID of this extension.\n */\n static get EXTENSION_ID () {\n return EXTENSION_ID;\n }\n\n /**\n * URL to get this extension.\n * @type {string}\n */\n static get extensionURL () {\n return extensionURL;\n }\n\n /**\n * Set URL to get this extension.\n * @param {string} url - URL\n */\n static set extensionURL (url) {\n extensionURL = url;\n }\n\n /**\n * @return {array} - text and values for each gestures menu element\n */\n get GESTURES_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.tiltUp',\n default: 'titl up',\n description: 'label for tilt up gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.TILT_UP\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.tiltDown',\n default: 'titl down',\n description: 'label for tilt down gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.TILT_DOWN\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.tiltLeft',\n default: 'titl left',\n description: 'label for tilt left gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.TILT_LEFT\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.tiltRight',\n default: 'titl right',\n description: 'label for tilt right gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.TILT_RIGHT\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.faceUp',\n default: 'face up',\n description: 'label for face up gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.FACE_UP\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.faceDown',\n default: 'face down',\n description: 'label for face down gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.FACE_DOWN\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.freefall',\n default: 'freefall',\n description: 'label for freefall gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.FREEFALL\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.g3',\n default: '3G',\n description: 'label for 3G gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.G3\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.g6',\n default: '6G',\n description: 'label for 6G gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.G6\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.g8',\n default: '8G',\n description: 'label for 3G gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.G8\n },\n {\n text: formatMessage({\n id: 'mbitMore.gesturesMenu.shake',\n default: 'shake',\n description: 'label for shaken gesture in gesture picker for microbit more extension'\n }),\n value: MbitMoreGestureName.SHAKE\n }\n\n ];\n }\n\n\n /**\n * @return {array} - text and values for each buttons menu element\n */\n get BUTTON_ID_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.buttonIDMenu.a',\n default: 'A',\n description: 'label for \"A\" element in button picker for Microbit More extension'\n }),\n value: MbitMoreButtonName.A\n },\n {\n text: formatMessage({\n id: 'mbitMore.buttonIDMenu.b',\n default: 'B',\n description: 'label for \"B\" element in button picker for Microbit More extension'\n }),\n value: MbitMoreButtonName.B\n }\n ];\n }\n\n /**\n * @return {array} - Menu items for button event selector.\n */\n get BUTTON_EVENT_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.buttonEventMenu.down',\n default: 'down',\n description: 'label for button down event'\n }),\n value: MbitMoreButtonEventName.DOWN\n },\n {\n text: formatMessage({\n id: 'mbitMore.buttonEventMenu.up',\n default: 'up',\n description: 'label for button up event'\n }),\n value: MbitMoreButtonEventName.UP\n },\n {\n text: formatMessage({\n id: 'mbitMore.buttonEventMenu.click',\n default: 'click',\n description: 'label for button click event'\n }),\n value: MbitMoreButtonEventName.CLICK\n // },\n // // These events are not in use because they are unstable in coal-microbit-v2.\n // {\n // text: formatMessage({\n // id: 'mbitMore.buttonEventMenu.pressed',\n // default: 'pressed',\n // description: 'label for button hold event'\n // }),\n // value: MbitMoreButtonEventName.HOLD\n // },\n // {\n // text: formatMessage({\n // id: 'mbitMore.buttonEventMenu.longClick',\n // default: 'long click',\n // description: 'label for button long click event'\n // }),\n // value: MbitMoreButtonEventName.LONG_CLICK\n // },\n // {\n // text: formatMessage({\n // id: 'mbitMore.buttonEventMenu.doubleClick',\n // default: 'double click',\n // description: 'label for button double click event'\n // }),\n // value: MbitMoreButtonEventName.DOUBLE_CLICK\n }\n ];\n }\n\n /**\n * @return {array} - text and values for each buttons menu element\n */\n get TOUCH_ID_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.touchIDMenu.logo',\n default: 'LOGO',\n description: 'label for \"LOGO\" element in touch button picker for Microbit More extension'\n }),\n value: MbitMoreButtonName.LOGO\n },\n {\n text: 'P0',\n value: MbitMoreButtonName.P0\n },\n {\n text: 'P1',\n value: MbitMoreButtonName.P1\n },\n {\n text: 'P2',\n value: MbitMoreButtonName.P2\n }\n ];\n }\n\n /**\n * @return {array} - Menu items for touch event selector.\n */\n get TOUCH_EVENT_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.touchEventMenu.touched',\n default: 'touched',\n description: 'label for touched event'\n }),\n value: MbitMoreButtonEventName.DOWN\n },\n {\n text: formatMessage({\n id: 'mbitMore.touchEventMenu.released',\n default: 'released',\n description: 'label for released event'\n }),\n value: MbitMoreButtonEventName.UP\n },\n {\n text: formatMessage({\n id: 'mbitMore.touchEventMenu.tapped',\n default: 'tapped',\n description: 'label for tapped event'\n }),\n value: MbitMoreButtonEventName.CLICK\n // },\n // // These events are not in use because they are unstable in coal-microbit-v2.\n // {\n // text: formatMessage({\n // id: 'mbitMore.touchEventMenu.pressed',\n // default: 'pressed',\n // description: 'label for hold event in touch'\n // }),\n // value: MbitMoreButtonEventName.HOLD\n // },\n // {\n // text: formatMessage({\n // id: 'mbitMore.touchEventMenu.longClick',\n // default: 'long click',\n // description: 'label for long click event in touch'\n // }),\n // value: MbitMoreButtonEventName.LONG_CLICK\n // },\n // {\n // text: formatMessage({\n // id: 'mbitMore.touchEventMenu.doubleClick',\n // default: 'double click',\n // description: 'label for double click event in touch'\n // }),\n // value: MbitMoreButtonEventName.DOUBLE_CLICK\n }\n ];\n }\n\n get ANALOG_IN_PINS_MENU () {\n return this._peripheral.analogIn.map(\n pinIndex =>\n Object.create({\n text: `P${pinIndex.toString()}`,\n value: pinIndex.toString()\n })\n );\n }\n\n\n get GPIO_MENU () {\n return this._peripheral.gpio.map(\n pinIndex =>\n Object.create({\n text: `P${pinIndex.toString()}`,\n value: pinIndex.toString()\n })\n );\n }\n\n get DIGITAL_VALUE_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.digitalValueMenu.Low',\n default: 'Low',\n description: 'label for low value in digital output menu for microbit more extension'\n }),\n value: 'false'\n },\n {\n text: formatMessage({\n id: 'mbitMore.digitalValueMenu.High',\n default: 'High',\n description: 'label for high value in digital output menu for microbit more extension'\n }),\n value: 'true'\n }\n ];\n }\n\n get AXIS_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.axisMenu.x',\n default: 'x',\n description: 'label of X axis.'\n }),\n value: AxisSymbol.X\n },\n {\n text: formatMessage({\n id: 'mbitMore.axisMenu.y',\n default: 'y',\n description: 'label of Y axis.'\n }),\n value: AxisSymbol.Y\n },\n {\n text: formatMessage({\n id: 'mbitMore.axisMenu.z',\n default: 'z',\n description: 'label of Z axis.'\n }),\n value: AxisSymbol.Z\n },\n {\n text: formatMessage({\n id: 'mbitMore.axisMenu.absolute',\n default: 'absolute',\n description: 'label of absolute value.'\n }),\n value: AxisSymbol.Absolute\n }\n ];\n }\n\n /**\n * @return {array} - text and values for each pin mode menu element\n */\n get PIN_MODE_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.pinModeMenu.pullNone',\n default: 'pull none',\n description: 'label for pullNone mode'\n }),\n value: MbitMorePullModeName.NONE\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinModeMenu.pullUp',\n default: 'pull up',\n description: 'label for pullUp mode'\n }),\n value: MbitMorePullModeName.UP\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinModeMenu.pullDown',\n default: 'pull down',\n description: 'label for pullDown mode'\n }),\n value: MbitMorePullModeName.DOWN\n }\n ];\n }\n\n /**\n * @return {array} - Menu items for event selector.\n */\n get PIN_EVENT_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.pinEventMenu.pulseLow',\n default: 'low pulse',\n description: 'label for low pulse event'\n }),\n value: 'PULSE_LOW'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventMenu.pulseHigh',\n default: 'high pulse',\n description: 'label for high pulse event'\n }),\n value: 'PULSE_HIGH'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventMenu.fall',\n default: 'fall',\n description: 'label for fall event'\n }),\n value: 'FALL'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventMenu.rise',\n default: 'rise',\n description: 'label for rise event'\n }),\n value: 'RISE'\n }\n ];\n }\n\n /**\n * @return {array} - Menu items for event selector.\n */\n get PIN_EVENT_TIMESTAMP_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.pinEventTimestampMenu.pulseLow',\n default: 'low pulse',\n description: 'label for low pulse event'\n }),\n value: 'PULSE_LOW'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventTimestampMenu.pulseHigh',\n default: 'high pulse',\n description: 'label for high pulse event'\n }),\n value: 'PULSE_HIGH'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventTimestampMenu.fall',\n default: 'fall',\n description: 'label for fall event'\n }),\n value: 'FALL'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventTimestampMenu.rise',\n default: 'rise',\n description: 'label for rise event'\n }),\n value: 'RISE'\n }\n ];\n }\n\n /**\n * @return {array} - Menu items for event listening.\n */\n get PIN_EVENT_TYPE_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.pinEventTypeMenu.none',\n default: 'none',\n description: 'label for remove event listener'\n }),\n value: 'NONE'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventTypeMenu.pulse',\n default: 'pulse',\n description: 'label for pulse event type'\n }),\n value: 'ON_PULSE'\n },\n {\n text: formatMessage({\n id: 'mbitMore.pinEventTypeMenu.edge',\n default: 'edge',\n description: 'label for edge event type'\n }),\n value: 'ON_EDGE'\n }\n ];\n }\n\n /**\n * @return {array} - Menu items for connection state.\n */\n get CONNECTION_STATE_MENU () {\n return [\n {\n text: formatMessage({\n id: 'mbitMore.connectionStateMenu.connected',\n default: 'connected',\n description: 'label for connected'\n }),\n value: 'connected'\n },\n {\n text: formatMessage({\n id: 'mbitMore.connectionStateMenu.disconnected',\n default: 'disconnected',\n description: 'label for disconnected'\n }),\n value: 'disconnected'\n }\n ];\n }\n\n /**\n * Construct a set of MicroBit blocks.\n * @param {Runtime} runtime - the Scratch 3.0 runtime.\n */\n constructor (runtime) {\n /**\n * The Scratch 3.0 runtime.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n if (runtime.formatMessage) {\n // Replace 'formatMessage' to a formatter which is used in the runtime.\n formatMessage = runtime.formatMessage;\n }\n // Create a new MicroBit peripheral instance\n this._peripheral = new MbitMore(this.runtime, MbitMoreBlocks.EXTENSION_ID);\n\n /**\n * The previous timestamps of button events.\n * @type {Object.>} button ID to object with event and timestamp.\n */\n this.prevButtonEvents = {};\n\n /**\n * The previous timestamps of gesture events.\n * @type {Object.} key: event ID, value: timestamp.\n */\n this.prevGestureEvents = {};\n\n /**\n * The previous timestamps of pin events.\n * @type {Object.>} pin index to object with event and timestamp.\n */\n this.prevPinEvents = {};\n\n /**\n * The previous timestamps of messages.\n * @type {Object.} pin index to object with event and timestamp.\n */\n this.prevReceivedData = {};\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n this.setupTranslations();\n return {\n id: MbitMoreBlocks.EXTENSION_ID,\n name: MbitMoreBlocks.EXTENSION_NAME,\n extensionURL: MbitMoreBlocks.extensionURL,\n blockIconURI: blockIconURI,\n showStatusButton: true,\n blocks: [\n {\n opcode: 'whenConnectionChanged',\n text: formatMessage({\n id: 'mbitMore.whenConnectionChanged',\n default: 'when micro:bit [STATE]',\n description: 'when a micro:bit connection state changed'\n }),\n blockType: BlockType.HAT,\n arguments: {\n STATE: {\n type: ArgumentType.STRING,\n menu: 'connectionStateMenu',\n defaultValue: 'connected'\n }\n }\n },\n '---',\n {\n opcode: 'whenButtonEvent',\n text: formatMessage({\n id: 'mbitMore.whenButtonEvent',\n default: 'when button [NAME] is [EVENT]',\n description: 'when the selected button on the micro:bit get the selected event'\n }),\n blockType: BlockType.HAT,\n arguments: {\n NAME: {\n type: ArgumentType.STRING,\n menu: 'buttonIDMenu',\n defaultValue: MbitMoreButtonName.A\n },\n EVENT: {\n type: ArgumentType.STRING,\n menu: 'buttonEventMenu',\n defaultValue: MbitMoreButtonEventName.DOWN\n }\n }\n },\n {\n opcode: 'isButtonPressed',\n text: formatMessage({\n id: 'mbitMore.isButtonPressed',\n default: 'button [NAME] pressed?',\n description: 'is the selected button on the micro:bit pressed?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n NAME: {\n type: ArgumentType.STRING,\n menu: 'buttonIDMenu',\n defaultValue: MbitMoreButtonName.A\n }\n }\n },\n {\n opcode: 'whenTouchEvent',\n text: formatMessage({\n id: 'mbitMore.whenTouchEvent',\n default: 'when pin [NAME] is [EVENT]',\n description: 'when the selected touch pin on the micro:bit is touched'\n }),\n blockType: BlockType.HAT,\n arguments: {\n NAME: {\n type: ArgumentType.STRING,\n menu: 'touchIDMenu',\n defaultValue: MbitMoreButtonName.LOGO\n },\n EVENT: {\n type: ArgumentType.STRING,\n menu: 'touchEventMenu',\n defaultValue: MbitMoreButtonEventName.DOWN\n }\n }\n },\n {\n opcode: 'isPinTouched',\n text: formatMessage({\n id: 'mbitMore.isPinTouched',\n default: 'pin [NAME] is touched?',\n description: 'is the selected pin is touched?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n NAME: {\n type: ArgumentType.STRING,\n menu: 'touchIDMenu',\n defaultValue: MbitMoreButtonName.LOGO\n }\n }\n },\n '---',\n {\n opcode: 'whenGesture',\n text: formatMessage({\n id: 'mbitMore.whenGesture',\n default: 'when [GESTURE]',\n description: 'when the selected gesture is detected by the micro:bit'\n }),\n blockType: BlockType.HAT,\n arguments: {\n GESTURE: {\n type: ArgumentType.STRING,\n menu: 'gestures',\n defaultValue: MbitMoreGestureName.SHAKE\n }\n }\n },\n '---',\n {\n opcode: 'displayMatrix',\n text: formatMessage({\n id: 'mbitMore.displayMatrix',\n default: 'display pattern [MATRIX] ',\n description: 'display a pattern on the micro:bit display'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MATRIX: {\n type: ArgumentType.MATRIX,\n defaultValue: '0101010101100010101000100'\n }\n }\n },\n {\n opcode: 'displayText',\n text: formatMessage({\n id: 'mbitMore.displayText',\n default: 'display text [TEXT] delay [DELAY] ms',\n description: 'display text on the micro:bit display'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n TEXT: {\n type: ArgumentType.STRING,\n defaultValue: 'Hello!'\n },\n DELAY: {\n type: ArgumentType.NUMBER,\n defaultValue: 120\n }\n }\n },\n {\n opcode: 'displayClear',\n text: formatMessage({\n id: 'mbitMore.clearDisplay',\n default: 'clear display',\n description: 'display nothing on the micro:bit display'\n }),\n blockType: BlockType.COMMAND\n },\n '---',\n {\n opcode: 'getLightLevel',\n text: formatMessage({\n id: 'mbitMore.lightLevel',\n default: 'light intensity',\n description: 'how much the amount of light falling on the LEDs on micro:bit'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'getTemperature',\n text: formatMessage({\n id: 'mbitMore.temperature',\n default: 'temperature',\n description: 'temperature (celsius) on the surface of CPU of micro:bit'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'getCompassHeading',\n text: formatMessage({\n id: 'mbitMore.compassHeading',\n default: 'angle with the North',\n description: 'angle from the North to the micro:bit heading direction'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'getPitch',\n text: formatMessage({\n id: 'mbitMore.pitch',\n default: 'pitch',\n description: 'nose up movement of the micro:bit from level'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'getRoll',\n text: formatMessage({\n id: 'mbitMore.roll',\n default: 'roll',\n description: 'clockwise circular movement of the micro:bit from level'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'getSoundLevel',\n text: formatMessage({\n id: 'mbitMore.soundLevel',\n default: 'sound level',\n description: 'level of the sound from microphone on micro:bit'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'getMagneticForce',\n text: formatMessage({\n id: 'mbitMore.magneticForce',\n default: 'magnetic force',\n description: 'value of magnetic force (micro tesla)'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n AXIS: {\n type: ArgumentType.STRING,\n menu: 'axis',\n defaultValue: AxisSymbol.Absolute\n }\n }\n },\n {\n opcode: 'getAcceleration',\n text: formatMessage({\n id: 'mbitMore.acceleration',\n default: 'acceleration [AXIS]',\n description: 'value of acceleration on the axis (milli-g)'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n AXIS: {\n type: ArgumentType.STRING,\n menu: 'axis',\n defaultValue: AxisSymbol.X\n }\n }\n },\n '---',\n {\n opcode: 'getAnalogValue',\n text: formatMessage({\n id: 'mbitMore.analogValue',\n default: 'analog value of pin [PIN]',\n description: 'analog input value of the pin'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n PIN: {\n type: ArgumentType.STRING,\n menu: 'analogInPins',\n defaultValue: '0'\n }\n }\n },\n {\n opcode: 'setPullMode',\n text: formatMessage({\n id: 'mbitMore.setPullMode',\n default: 'set pin [PIN] to input [MODE]',\n description: 'set a pin into the mode'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n },\n MODE: {\n type: ArgumentType.STRING,\n menu: 'pinMode',\n defaultValue: MbitMorePullModeName.UP\n }\n }\n },\n {\n opcode: 'isPinHigh',\n text: formatMessage({\n id: 'mbitMore.isPinHigh',\n default: '[PIN] pin is high?',\n description: 'is the selected pin high as digital?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n }\n }\n },\n '---',\n {\n opcode: 'setDigitalOut',\n text: formatMessage({\n id: 'mbitMore.setDigitalOut',\n default: 'set [PIN] Digital [LEVEL]',\n description: 'set pin to Digtal Output mode and the level(true = High)'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n },\n LEVEL: {\n type: ArgumentType.STRING,\n menu: 'digitalValueMenu',\n defaultValue: 'false'\n }\n }\n },\n {\n opcode: 'setAnalogOut',\n text: formatMessage({\n id: 'mbitMore.setAnalogOut',\n default: 'set [PIN] analog [LEVEL] %',\n description: 'set pin to PWM mode and the level(0 to 1023)'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n },\n LEVEL: {\n type: ArgumentType.NUMBER,\n defaultValue: 0\n }\n }\n },\n {\n opcode: 'playTone',\n text: formatMessage({\n id: 'mbitMore.playTone',\n default: 'play tone [FREQ] Hz volume [VOL] %',\n description: 'play tone on the speaker'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n FREQ: {\n type: ArgumentType.NUMBER,\n defaultValue: 440\n },\n VOL: {\n type: ArgumentType.NUMBER,\n defaultValue: 100\n }\n }\n },\n {\n opcode: 'stopTone',\n text: formatMessage({\n id: 'mbitMore.stopTone',\n default: 'stop tone',\n description: 'stop tone on the speaker'\n }),\n blockType: BlockType.COMMAND\n },\n {\n opcode: 'setServo',\n text: formatMessage({\n id: 'mbitMore.setServo',\n default: 'set [PIN] Servo [ANGLE]',\n description: 'set pin to Servo mode and the angle(0 to 180)'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n },\n ANGLE: {\n type: ArgumentType.NUMBER,\n defaultValue: 0\n },\n RANGE: {\n type: ArgumentType.NUMBER,\n defaultValue: 2000\n },\n CENTER: {\n type: ArgumentType.NUMBER,\n defaultValue: 1500\n }\n }\n },\n '---',\n {\n opcode: 'listenPinEventType',\n text: formatMessage({\n id: 'mbitMore.listenPinEventType',\n default: 'catch event [EVENT_TYPE] on [PIN]',\n description: 'listen the event on the pin'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n EVENT_TYPE: {\n type: ArgumentType.STRING,\n menu: 'pinEventTypeMenu',\n defaultValue: 'NONE'\n },\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n }\n }\n },\n {\n opcode: 'whenPinEvent',\n text: formatMessage({\n id: 'mbitMore.whenPinEvent',\n default: 'when catch [EVENT] at pin [PIN]',\n description: 'when catch the event at the pin'\n\n }),\n blockType: BlockType.HAT,\n arguments: {\n EVENT: {\n type: ArgumentType.STRING,\n menu: 'pinEventMenu',\n defaultValue: 'PULSE_LOW'\n },\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n }\n }\n },\n {\n opcode: 'getPinEventValue',\n text: formatMessage({\n id: 'mbitMore.getPinEventValue',\n default: 'value of [EVENT] at [PIN]',\n description: 'value of the value of the event (timestamp of the edge or duration of the pulse)'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n EVENT: {\n type: ArgumentType.STRING,\n menu: 'pinEventTimestampMenu',\n defaultValue: 'PULSE_LOW'\n },\n PIN: {\n type: ArgumentType.STRING,\n menu: 'gpio',\n defaultValue: '0'\n }\n }\n },\n '---',\n {\n opcode: 'whenDataReceived',\n text: formatMessage({\n id: 'mbitMore.whenDataReceived',\n default: 'when data with loabel [LABEL] received from micro:bit',\n description: 'when the data which has the label received'\n\n }),\n blockType: BlockType.HAT,\n arguments: {\n LABEL: {\n type: ArgumentType.STRING,\n defaultValue: 'label-01'\n }\n }\n },\n {\n opcode: 'getDataLabeled',\n text: formatMessage({\n id: 'mbitMore.getDataLabeled',\n default: 'data of label [LABEL]',\n description: 'the last data which has the label'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n LABEL: {\n type: ArgumentType.STRING,\n defaultValue: 'label-01'\n }\n }\n },\n {\n opcode: 'sendData',\n text: formatMessage({\n id: 'mbitMore.sendData',\n default: 'send data [DATA] with label [LABEL] to micro:bit',\n description: 'send data content with label to micro:bit'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n LABEL: {\n type: ArgumentType.STRING,\n defaultValue: 'label-01'\n },\n DATA: {\n type: ArgumentType.STRING,\n defaultValue: 'data'\n }\n }\n }\n ],\n menus: {\n buttonIDMenu: {\n acceptReporters: false,\n items: this.BUTTON_ID_MENU\n },\n buttonEventMenu: {\n acceptReporters: false,\n items: this.BUTTON_EVENT_MENU\n },\n touchIDMenu: {\n acceptReporters: false,\n items: this.TOUCH_ID_MENU\n },\n touchEventMenu: {\n acceptReporters: false,\n items: this.TOUCH_EVENT_MENU\n },\n gestures: {\n acceptReporters: false,\n items: this.GESTURES_MENU\n },\n analogInPins: {\n acceptReporters: false,\n items: this.ANALOG_IN_PINS_MENU\n },\n digitalValueMenu: {\n acceptReporters: true,\n items: this.DIGITAL_VALUE_MENU\n },\n gpio: {\n acceptReporters: false,\n items: this.GPIO_MENU\n },\n axis: {\n acceptReporters: false,\n items: this.AXIS_MENU\n },\n pinMode: {\n acceptReporters: false,\n items: this.PIN_MODE_MENU\n },\n pinEventTypeMenu: {\n acceptReporters: false,\n items: this.PIN_EVENT_TYPE_MENU\n },\n pinEventMenu: {\n acceptReporters: false,\n items: this.PIN_EVENT_MENU\n },\n pinEventTimestampMenu: {\n acceptReporters: false,\n items: this.PIN_EVENT_TIMESTAMP_MENU\n },\n connectionStateMenu: {\n acceptReporters: false,\n items: this.CONNECTION_STATE_MENU\n }\n },\n // eslint-disable-next-line no-use-before-define\n translationMap: extensionTranslations\n };\n }\n\n /**\n * Update the previous occured time of all button events.\n */\n updatePrevButtonEvents () {\n this.prevButtonEvents = {};\n Object.entries(this._peripheral.buttonEvents).forEach(([componentID, events]) => {\n this.prevButtonEvents[componentID] = {};\n Object.entries(events).forEach(([eventName, timestamp]) => {\n this.prevButtonEvents[componentID][eventName] = timestamp;\n });\n });\n }\n\n /**\n * Test whether the event raised at the button.\n * @param {object} args - the block's arguments.\n * @param {string} args.NAME - name of the button.\n * @param {string} args.EVENT - name of event to catch.\n * @return {boolean} - true if the event raised.\n */\n whenButtonEvent (args) {\n if (!this.updateLastButtonEventTimer) {\n this.updateLastButtonEventTimer = setTimeout(() => {\n this.updatePrevButtonEvents();\n this.updateLastButtonEventTimer = null;\n }, this.runtime.currentStepTime);\n }\n const buttonName = args.NAME;\n const eventName = args.EVENT;\n const lastTimestamp =\n this._peripheral.getButtonEventTimestamp(buttonName, eventName);\n if (lastTimestamp === null) return false;\n if (!this.prevButtonEvents[buttonName]) return true;\n return lastTimestamp !== this.prevButtonEvents[buttonName][eventName];\n }\n\n /**\n * Test whether the A or B button is pressed\n * @param {object} args - the block's arguments.\n * @param {string} args.NAME - name of the button.\n * @param {object} util - utility object provided by the runtime.\n * @return {boolean} - whether the button is pressed or not.\n */\n isButtonPressed (args) {\n const buttonName = args.NAME;\n return this._peripheral.isButtonPressed(buttonName);\n }\n\n\n /**\n * Test whether the touch event raised at the pin.\n * @param {object} args - the block's arguments.\n * @param {string} args.NAME - name of the pin to catch.\n * @param {string} args.EVENT - event to catch.\n * @param {object} util - utility object provided by the runtime.\n * @return {Promise} - a Promise that resolves that resolves the touch state.\n */\n whenTouchEvent (args, util) {\n const buttonName = args.NAME;\n if (buttonName === MbitMoreButtonName.LOGO) {\n return this.whenButtonEvent(args);\n }\n if (this._peripheral.isPinTouchMode(MbitMoreButtonPinIndex[buttonName])) {\n return this.whenButtonEvent(args);\n }\n this._peripheral.configTouchPin(MbitMoreButtonPinIndex[buttonName], util);\n return false;\n }\n\n /**\n * Test whether the touch-pin is touched.\n * @param {object} args - the block's arguments.\n * @param {string} args.NAME - name of the pin.\n * @param {object} util - utility object provided by the runtime.\n * @return {boolean} - whether the button is pressed or not.\n */\n isPinTouched (args, util) {\n const buttonName = args.NAME;\n if (buttonName === MbitMoreButtonName.LOGO) {\n return this._peripheral.isTouched(buttonName);\n }\n if (this._peripheral.isPinTouchMode(MbitMoreButtonPinIndex[buttonName])) {\n return this._peripheral.isTouched(buttonName);\n }\n this._peripheral.configTouchPin(MbitMoreButtonPinIndex[buttonName], util);\n return false;\n }\n\n /**\n * Update the last occured time of all gesture events.\n */\n updatePrevGestureEvents () {\n this.prevGestureEvents = {};\n Object.entries(this._peripheral.gestureEvents).forEach(([gestureName, timestamp]) => {\n this.prevGestureEvents[gestureName] = timestamp;\n });\n }\n\n /**\n * Test whether the gesture event raised.\n * @param {object} args - the block's arguments.\n * @param {string} args.GESTURE - name of the gesture.\n * @return {boolean} - true if the event raised.\n */\n whenGesture (args) {\n if (!this.updateLastGestureEventTimer) {\n this.updateLastGestureEventTimer = setTimeout(() => {\n this.updatePrevGestureEvents();\n this.updateLastGestureEventTimer = null;\n }, this.runtime.currentStepTime);\n }\n const gestureName = args.GESTURE;\n const lastTimestamp =\n this._peripheral.getGestureEventTimestamp(gestureName);\n if (lastTimestamp === null) return false;\n if (!this.prevGestureEvents[gestureName]) return true;\n return lastTimestamp !== this.prevGestureEvents[gestureName];\n }\n\n /**\n * Display pixcel pattern on the 5x5 LED matrix with brightness and write mode.\n * @param {object} args - the block's arguments.\n * @param {string} args.MATRIX - the pattern of the pixels.\n * @param {object} util - utility object provided by the runtime.\n * @return {Promise} - a Promise that resolves after a tick.\n */\n displayMatrix (args, util) {\n const matrixString = cast.toString(args.MATRIX)\n .replace(/!-~/g, ws => String.fromCharCode(ws.charCodeAt(0) - 0xFEE0)); // zenkaku to hankaku\n let matrixData;\n if (matrixString.includes(',')) {\n // comma separated values\n matrixData = matrixString.split(/[,\\n]/);\n } else if (/[ \\t]\\d*[ \\t]/g.test(matrixString)) {\n // space|tab separated values\n matrixData = matrixString.split(/\\s/g);\n } else {\n // 0|1 pattern.\n matrixData = matrixString.replace(/\\s/g, '')\n .split('');\n matrixData = matrixData.map(level => ((level === '0') ? 0 : 100));\n }\n matrixData = matrixData.map(brightness =>\n (Math.max(0,\n Math.min(100,\n Number(brightness)) * 255 / 100))); // percent to 8bits value\n const matrix = [];\n for (let line = 0; line < 5; line++) {\n matrix[line] = [];\n for (let col = 0; col < 5; col++) {\n matrix[line][col] = matrixData[(line * 5) + col];\n }\n }\n return this._peripheral.displayPixels(matrix, util);\n }\n\n /**\n * Display text on the 5x5 LED matrix.\n * Displayable character is ascii and non-ascii is replaced to '?'.\n * @param {object} args - the block's arguments.\n * @param {string} args.TEXT - The contents to display.\n * @param {number} args.DELAY - The time to delay between characters, in milliseconds.\n * @param {object} util - utility object provided by the runtime.\n * @return {Promise} - a Promise that resolves after the text is done printing.\n * Note the limit is 18 characters\n * The print time is calculated by multiplying the number of horizontal pixels\n * by the default scroll delay of 120ms.\n * The number of horizontal pixels = 6px for each character in the string,\n * 1px before the string, and 5px after the string.\n */\n displayText (args, util) {\n const text = String(args.TEXT)\n .replace(/!-~/g, zenkaku =>\n String.fromCharCode(zenkaku.charCodeAt(0) - 0xFEE0)) // zenkaku to hankaku\n .replace(/[^ -~]/g, '?');\n let delay = parseInt(args.DELAY, 10);\n delay = isNaN(delay) ? 120 : delay; // Use default delay if NaN.\n if (text.length > 0) this._peripheral.displayText(text, delay, util);\n const yieldDelay = delay * ((6 * text.length) + 6);\n\n return new Promise(resolve => {\n setTimeout(() => {\n resolve();\n }, yieldDelay);\n });\n }\n\n /**\n * Turn all 5x5 matrix LEDs off.\n * @param {object} args - the block's arguments.\n * @param {object} util - utility object provided by the runtime.\n * @return {Promise} - a Promise that resolves after a tick.\n */\n displayClear (args, util) {\n const matrix = [\n [0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0]\n ];\n return this._peripheral.displayPixels(matrix, util);\n }\n\n /**\n * Test the selected pin is high as digital.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @return {boolean} - true if the pin is high.\n */\n isPinHigh (args) {\n return this._peripheral.isPinHigh(parseInt(args.PIN, 10));\n }\n\n /**\n * Get amount of light (0 - 255) on the LEDs.\n * @param {object} args - the block's arguments.\n * @return {number} - light level.\n */\n getLightLevel () {\n const level = this._peripheral.readLightLevel();\n return Math.round(level * 1000 / 255) / 10;\n }\n\n /**\n * Get temperature (integer in celsius) of micro:bit.\n * @param {object} args - the block's arguments.\n * @return {number} - value of temperature [centigrade].\n */\n getTemperature () {\n return this._peripheral.readTemperature();\n }\n\n /**\n * Get loudness of the sound from microphone on micro:bit.\n * @param {object} args - the block's arguments.\n * @param {object} util - utility object provided by the runtime.\n * @return {Promise} - a Promise that resolves digital input value of the pin.\n */\n getSoundLevel (args, util) {\n return this._peripheral.configMic(true, util)\n .then(micState => {\n if (micState) {\n return this._peripheral.readSoundLevel();\n }\n return 0;\n });\n }\n\n /**\n * Return angle from the north to the micro:bit heading direction.\n * @return {number} - degree of compass heading angle from the north (0 - 359 degrees).\n */\n getCompassHeading () {\n return this._peripheral.readCompassHeading();\n }\n\n /**\n * Return analog value of the pin.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves analog input value of the pin or undefined if this process was yield.\n */\n getAnalogValue (args, util) {\n const pinIndex = parseInt(args.PIN, 10);\n const resultPromise = this._peripheral.readAnalogIn(pinIndex, util);\n if (!resultPromise) return;\n return resultPromise.then(level => Math.round(level * 100 * 10 / 1024) / 10);\n }\n\n /**\n * Return digital value of the pin.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @return {Promise} - a Promise that resolves digital input value of the pin.\n */\n getDigitalValue (args) {\n return this._peripheral.readDigitalLevel(parseInt(args.PIN, 10));\n }\n\n /**\n * Send data with label.\n * @param {object} args - the block's arguments.\n * @property {string} args.LABEL - label of the data.\n * @property {string} args.DATA - content of the data.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} - a Promise that resolves when the process was done or undefined if labels was empty.\n */\n sendData (args, util) {\n if (args.LABEL.length <= 0) {\n return;\n }\n return this._peripheral.sendData(args.LABEL, args.DATA, util);\n }\n\n /**\n * Set pull mode of the pin.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {MbitMorePullModeName} args.MODE - mode to set.\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {undefined}\n */\n setPullMode (args, util) {\n this._peripheral.setPullMode(parseInt(args.PIN, 10), MbitMorePullModeID[args.MODE], util);\n }\n\n /**\n * Set the pin to Output mode and level.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {boolean | string | number} args.LEVEL - value to be set.\n * @param {object} util - utility object provided by the runtime.\n * @return {undefined}\n */\n setDigitalOut (args, util) {\n let level = (args.LEVEL === true);\n level = level || (args.LEVEL === 'true');\n if (!level) {\n const num = Number(args.LEVEL);\n if (!isNaN(num)) {\n level = (num > 0);\n }\n }\n this._peripheral.setPinOutput(parseInt(args.PIN, 10), level, util);\n }\n\n /**\n * Set the pin to PWM mode and level.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {number} args.LEVEL - value[%] for PWM.\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n setAnalogOut (args, util) {\n let percent = parseInt(args.LEVEL, 10);\n if (isNaN(percent)) {\n return;\n }\n percent = Math.max(0, Math.min(percent, 100));\n const level = Math.round(percent * 1024 / 100);\n return this._peripheral.setPinPWM(\n parseInt(args.PIN, 10),\n level,\n util\n );\n }\n\n /**\n * Set the pin to Servo mode and angle.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n setServo (args, util) {\n let angle = parseInt(args.ANGLE, 10);\n if (isNaN(angle)) return;\n angle = Math.max(0, angle);\n angle = Math.min(angle, 180);\n // let range = parseInt(args.RANGE, 10);\n // if (isNaN(range)) range = 0;\n // range = Math.max(0, range);\n // let center = parseInt(args.CENTER, 10);\n // if (isNaN(center)) range = 0;\n // center = Math.max(0, center);\n return this._peripheral.setPinServo(parseInt(args.PIN, 10), angle, null, null, util);\n }\n\n /**\n * Return the value of magnetic force [micro tesla] on axis.\n * @param {object} args - the block's arguments.\n * @property {AxisSymbol} AXIS - the axis (X, Y, Z, Absolute).\n * @return {number} - value of magnetic force.\n */\n getMagneticForce (args) {\n return this._peripheral.readMagneticForce(args.AXIS);\n }\n\n /**\n * Return the value of acceleration on the specified axis.\n * @param {object} args - the block's arguments.\n * @param {AxisSymbol} args.AXIS - direction to get.\n * @return {number} - value of acceleration.\n */\n getAcceleration (args) {\n return this._peripheral.readAcceleration(args.AXIS);\n }\n\n /**\n * Return pitch [degrees] of the micro:bit heading direction.\n * @return {number} - degree of pitch.\n */\n getPitch () {\n return this._peripheral.readPitch();\n }\n\n /**\n * Read roll [degrees] of the micro:bit heading direction.\n * @return {number} - degree of roll.\n */\n getRoll () {\n return this._peripheral.readRoll();\n }\n\n\n /**\n * Play tone on the speaker.\n * @param {object} args - the block's arguments.\n * @param {string} args.FREQ - wave frequency to play\n * @param {string} args.VOL laudness of tone\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} - a Promise that resolves to send command or undefined if this process was yield.\n */\n playTone (args, util) {\n const frequency = parseFloat(args.FREQ);\n let volume = parseInt(args.VOL, 10);\n volume = Math.min(100, (Math.max(0, volume)));\n return this._peripheral.playTone(frequency, volume, util);\n }\n\n /**\n * Stop playing tone on the speaker.\n * @param {object} args - the block's arguments.\n * @param {object} util - utility object provided by the runtime.\n * @return {?Promise} - a Promise that resolves to send command or undefined if this process was yield.\n */\n stopTone (args, util) {\n return this._peripheral.stopTone(util);\n }\n\n /**\n * Set listening event type at the pin.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {string} args.EVENT_TYPE - event to listen.\n * @param {BlockUtility} util - utility object provided by the runtime.\n * @return {?Promise} a Promise that resolves when command sending done or undefined if this process was yield.\n */\n listenPinEventType (args, util) {\n return this._peripheral.listenPinEventType(parseInt(args.PIN, 10), MbitMorePinEventType[args.EVENT_TYPE], util);\n }\n\n /**\n * Rerutn value (timestamp of the edge or duration of the pulse) of the event or 0 when the event is not received.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {string} args.EVENT - event value to get.\n * @param {object} util - utility object provided by the runtime.\n * @return {number} - timestamp of the event or 0.\n */\n getPinEventValue (args) {\n const value = this._peripheral.getPinEventValue(parseInt(args.PIN, 10), MbitMorePinEvent[args.EVENT]);\n return value ? value : 0;\n }\n\n /**\n * Update the previous occured time of all pin events.\n */\n updatePrevPinEvents () {\n this.prevPinEvents = {};\n Object.entries(this._peripheral._pinEvents).forEach(([pinIndex, events]) => {\n this.prevPinEvents[pinIndex] = {};\n Object.entries(events).forEach(([eventID, eventData]) => {\n this.prevPinEvents[pinIndex][eventID] = {};\n Object.entries(eventData).forEach(([key, value]) => {\n this.prevPinEvents[pinIndex][eventID][key] = value;\n });\n });\n });\n }\n\n /**\n * Return the previous timestamp of the pin event or undefined if the event was not received.\n * @param {number} pinIndex - index of the pin to get the event.\n * @param {MbitMorePinEvent} eventID - ID of the event to get.\n * @return {?number} Timestamp of the previous event or null.\n */\n getPrevPinEventTimestamp (pinIndex, eventID) {\n if (this.prevPinEvents[pinIndex] && this.prevPinEvents[pinIndex][eventID]) {\n return this.prevPinEvents[pinIndex][eventID].timestamp;\n }\n return null;\n }\n\n /**\n * Test whether the event raised at the pin.\n * @param {object} args - the block's arguments.\n * @param {number} args.PIN - pin ID.\n * @param {string} args.EVENT - event to catch.\n * @return {boolean} - true if the event raised.\n */\n whenPinEvent (args) {\n if (!this.updateLastPinEventTimer) {\n this.updateLastPinEventTimer = setTimeout(() => {\n this.updatePrevPinEvents();\n this.updateLastPinEventTimer = null;\n }, this.runtime.currentStepTime);\n }\n const pinIndex = parseInt(args.PIN, 10);\n const eventID = MbitMorePinEvent[args.EVENT];\n const lastTimestamp =\n this._peripheral.getPinEventTimestamp(pinIndex, eventID);\n if (lastTimestamp === null) return false;\n const prevTimestamp = this.getPrevPinEventTimestamp(pinIndex, eventID);\n if (prevTimestamp === null) return true;\n return lastTimestamp !== prevTimestamp;\n }\n\n /**\n * Rerutn the last content of the messge or undefined if the data which has the label is not received.\n * @param {object} args - the block's arguments.\n * @param {number} args.LABEL - label of the data.\n * @return {?(string | number)} - content of the data.\n */\n getDataLabeled (args) {\n return this._peripheral.getDataLabeled(args.LABEL);\n }\n\n /**\n * Update the previous occured time of all received data.\n */\n updatePrevReceivedData () {\n this.prevReceivedData = {};\n Object.entries(this._peripheral.receivedData).forEach(([label, contentObject]) => {\n this.prevReceivedData[label] = {};\n Object.entries(contentObject).forEach(([key, value]) => {\n this.prevReceivedData[label][key] = value;\n });\n });\n }\n\n /**\n * Return the previous timestamp of the data or undefined if the data was not received.\n * @param {string} label - label of the data.\n * @return {?number} Timestamp of the previous data or null.\n */\n getPrevReceivedDataTimestamp (label) {\n if (this.prevReceivedData[label]) {\n return this.prevReceivedData[label].timestamp;\n }\n return null;\n }\n\n /**\n * Test whether the data received which had the label.\n * @param {object} args - the block's arguments.\n * @param {number} args.LABEL - label of the data.\n * @return {boolean} - true if the data received.\n */\n whenDataReceived (args) {\n if (!this.updateLastDataTimer) {\n this.updateLastDataTimer = setTimeout(() => {\n this.updatePrevReceivedData();\n this.updateLastDataTimer = null;\n }, this.runtime.currentStepTime);\n }\n const label = args.LABEL;\n const lastTimestamp =\n this._peripheral.getDataTimestamp(label);\n if (lastTimestamp === null) return false;\n const prevTimestamp = this.getPrevReceivedDataTimestamp(label);\n if (prevTimestamp === null) return true;\n return lastTimestamp !== prevTimestamp;\n }\n\n /**\n * Test whether a micro:bit connected.\n * @param {object} args - the block's arguments.\n * @property {string} args.STATE - the state of connection to check.\n * @return {boolean} - true if the state is matched.\n */\n whenConnectionChanged (args) {\n const state = (args.STATE === 'connected');\n return (state === this._peripheral.isConnected());\n }\n\n /**\n * Setup format-message for this extension.\n */\n setupTranslations () {\n const localeSetup = formatMessage.setup();\n if (localeSetup && localeSetup.translations[localeSetup.locale]) {\n Object.assign(\n localeSetup.translations[localeSetup.locale],\n // eslint-disable-next-line no-use-before-define\n extensionTranslations[localeSetup.locale]\n );\n }\n }\n}\n\nconst extensionTranslations = {\n 'ja': {\n 'mbitMore.whenButtonEvent': 'ボタン [NAME] が [EVENT] とき',\n 'mbitMore.buttonIDMenu.a': 'A',\n 'mbitMore.buttonIDMenu.b': 'B',\n 'mbitMore.buttonEventMenu.down': '下がった',\n 'mbitMore.buttonEventMenu.pressed': '押された',\n 'mbitMore.buttonEventMenu.up': '上がった',\n 'mbitMore.buttonEventMenu.click': 'クリックされた',\n 'mbitMore.buttonEventMenu.longClick': '長くクリックされた',\n 'mbitMore.buttonEventMenu.doubleClick': 'ダブルクリックされた',\n 'mbitMore.isButtonPressed': 'ボタン [NAME] が押された',\n 'mbitMore.whenTouchEvent': 'ピン [NAME] が [EVENT] とき',\n 'mbitMore.isPinTouched': 'ピン [NAME] が触れられた',\n 'mbitMore.touchIDMenu.logo': 'ロゴ',\n 'mbitMore.touchEventMenu.touched': '触れられた',\n 'mbitMore.touchEventMenu.pressed': '押された',\n 'mbitMore.touchEventMenu.released': '放された',\n 'mbitMore.touchEventMenu.tapped': 'タップされた',\n 'mbitMore.whenGesture': '[GESTURE] とき',\n 'mbitMore.gesturesMenu.tiltUp': '上へ傾いた',\n 'mbitMore.gesturesMenu.tiltDown': '下へ傾いた',\n 'mbitMore.gesturesMenu.tiltLeft': '左へ傾いた',\n 'mbitMore.gesturesMenu.tiltRight': '右へ傾いた',\n 'mbitMore.gesturesMenu.faceUp': '表になった',\n 'mbitMore.gesturesMenu.faceDown': '裏になった',\n 'mbitMore.gesturesMenu.freefall': '落ちた',\n 'mbitMore.gesturesMenu.g3': '3Gかかった',\n 'mbitMore.gesturesMenu.g6': '6Gかかった',\n 'mbitMore.gesturesMenu.g8': '8Gかかった',\n 'mbitMore.gesturesMenu.shake': 'ゆさぶられた',\n 'mbitMore.displayMatrix': 'パターン [MATRIX] を表示する',\n 'mbitMore.displayText': '文字 [TEXT] を [DELAY] ミリ秒間隔で流す',\n 'mbitMore.clearDisplay': '画面を消す',\n 'mbitMore.isPinHigh': 'ピン [PIN] がハイである',\n 'mbitMore.lightLevel': '明るさ',\n 'mbitMore.temperature': '温度',\n 'mbitMore.compassHeading': '北からの角度',\n 'mbitMore.magneticForce': '磁力 [AXIS]',\n 'mbitMore.acceleration': '加速度 [AXIS]',\n 'mbitMore.pitch': 'ピッチ',\n 'mbitMore.roll': 'ロール',\n 'mbitMore.soundLevel': '音の大きさ',\n 'mbitMore.analogValue': 'ピン [PIN] のアナログレベル',\n 'mbitMore.setPullMode': 'ピン [PIN] を [MODE] 入力にする',\n 'mbitMore.setDigitalOut': 'ピン [PIN] をデジタル出力 [LEVEL] にする',\n 'mbitMore.setAnalogOut': 'ピン [PIN] をアナログ出力 [LEVEL] %にする',\n 'mbitMore.playTone': '[FREQ] Hzの音を [VOL] %の大きさで鳴らす',\n 'mbitMore.stopTone': '音を止める',\n 'mbitMore.setServo': 'ピン [PIN] をサーボ [ANGLE] 度にする',\n 'mbitMore.digitalValueMenu.Low': 'ロー',\n 'mbitMore.digitalValueMenu.High': 'ハイ',\n 'mbitMore.axisMenu.x': 'x',\n 'mbitMore.axisMenu.y': 'y',\n 'mbitMore.axisMenu.z': 'z',\n 'mbitMore.axisMenu.absolute': '大きさ',\n 'mbitMore.pinModeMenu.pullNone': '開放',\n 'mbitMore.pinModeMenu.pullUp': 'プルアップ',\n 'mbitMore.pinModeMenu.pullDown': 'プルダウン',\n 'mbitMore.listenPinEventType': 'ピン [PIN] で [EVENT_TYPE] ',\n 'mbitMore.pinEventTypeMenu.none': 'イベントを受けない',\n 'mbitMore.pinEventTypeMenu.edge': 'エッジイベントを受ける',\n 'mbitMore.pinEventTypeMenu.pulse': 'パルスイベントを受ける',\n 'mbitMore.pinEventTypeMenu.touch': 'タッチイベントを受ける',\n 'mbitMore.whenPinEvent': 'ピン [PIN] で [EVENT] イベントが上がった',\n 'mbitMore.pinEventMenu.rise': 'ライズ',\n 'mbitMore.pinEventMenu.fall': 'フォール',\n 'mbitMore.pinEventMenu.pulseHigh': 'ハイパルス',\n 'mbitMore.pinEventMenu.pulseLow': 'ローパルス',\n 'mbitMore.getPinEventValue': 'ピン [PIN] の [EVENT]',\n 'mbitMore.pinEventTimestampMenu.rise': 'ライズの時刻',\n 'mbitMore.pinEventTimestampMenu.fall': 'フォールの時刻',\n 'mbitMore.pinEventTimestampMenu.pulseHigh': 'ハイパルスの期間',\n 'mbitMore.pinEventTimestampMenu.pulseLow': 'ローパルスの期間',\n 'mbitMore.whenDataReceived': 'micro:bit からラベル [LABEL] のデータを受け取ったとき',\n 'mbitMore.getDataLabeled': 'ラベル [LABEL] のデータ',\n 'mbitMore.sendData': 'micro:bit へデータ [DATA] にラベル [LABEL] を付けて送る',\n 'mbitMore.connectionStateMenu.connected': 'つながった',\n 'mbitMore.connectionStateMenu.disconnected': '切れた',\n 'mbitMore.whenConnectionChanged': 'micro:bit と[STATE]とき'\n },\n 'ja-Hira': {\n 'mbitMore.whenButtonEvent': '[NAME] ボタンが [EVENT] とき',\n 'mbitMore.buttonIDMenu.a': 'A',\n 'mbitMore.buttonIDMenu.b': 'B',\n 'mbitMore.buttonEventMenu.down': 'さがった',\n 'mbitMore.buttonEventMenu.hold': 'おされた',\n 'mbitMore.buttonEventMenu.up': 'あがった',\n 'mbitMore.buttonEventMenu.click': 'クリックされた',\n 'mbitMore.buttonEventMenu.longClick': 'ながくクリックされた',\n 'mbitMore.buttonEventMenu.doubleClick': 'ダブルクリックされた',\n 'mbitMore.isButtonPressed': '[NAME] ボタンがおされた',\n 'mbitMore.whenTouchEvent': 'ピン [NAME] が [EVENT] とき',\n 'mbitMore.isPinTouched': 'ピン [NAME] がふれられた',\n 'mbitMore.touchIDMenu.logo': 'ロゴ',\n 'mbitMore.touchEventMenu.touched': 'ふれられた',\n 'mbitMore.touchEventMenu.pressed': 'おされた',\n 'mbitMore.touchEventMenu.released': 'はなされた',\n 'mbitMore.touchEventMenu.tapped': 'タップされた',\n 'mbitMore.whenGesture': '[GESTURE] とき',\n 'mbitMore.gesturesMenu.tiltUp': 'うえへかたむいた',\n 'mbitMore.gesturesMenu.tiltDown': 'したへかたむいた',\n 'mbitMore.gesturesMenu.tiltLeft': 'ひだりへかたむいた',\n 'mbitMore.gesturesMenu.tiltRight': 'みぎへかたむいた',\n 'mbitMore.gesturesMenu.faceUp': 'おもてになった',\n 'mbitMore.gesturesMenu.faceDown': 'うらになった',\n 'mbitMore.gesturesMenu.freefall': 'おちた',\n 'mbitMore.gesturesMenu.g3': '3Gかかった',\n 'mbitMore.gesturesMenu.g6': '6Gかかった',\n 'mbitMore.gesturesMenu.g8': '8Gかかった',\n 'mbitMore.gesturesMenu.shake': 'ゆさぶられた',\n 'mbitMore.displayMatrix': 'パターン [MATRIX] をひょうじする',\n 'mbitMore.displayText': 'もじ [TEXT] を [DELAY] ミリびょうかんかくでながす',\n 'mbitMore.clearDisplay': 'がめんをけす',\n 'mbitMore.isPinHigh': 'ピン [PIN] がハイである',\n 'mbitMore.lightLevel': 'あかるさ',\n 'mbitMore.temperature': 'おんど',\n 'mbitMore.compassHeading': 'きたからのかくど',\n 'mbitMore.magneticForce': 'じりょく [AXIS]',\n 'mbitMore.acceleration': 'かそくど [AXIS]',\n 'mbitMore.pitch': 'ピッチ',\n 'mbitMore.roll': 'ロール',\n 'mbitMore.soundLevel': 'おとのおおきさ',\n 'mbitMore.analogValue': 'ピン [PIN] のアナログレベル',\n 'mbitMore.setPullMode': 'ピン [PIN] を [MODE] にゅうりょくにする',\n 'mbitMore.setDigitalOut': 'ピン [PIN] をデジタルしゅつりょく [LEVEL] にする',\n 'mbitMore.setAnalogOut': 'ピン [PIN] をアナログしゅつりょく [LEVEL] パーセントにする',\n 'mbitMore.playTone': '[FREQ] ヘルツのおとを [VOL] パーセントの大きさで鳴らす',\n 'mbitMore.stopTone': 'おとをとめる',\n 'mbitMore.setServo': 'ピン [PIN] をサーボ [ANGLE] どにする',\n 'mbitMore.digitalValueMenu.Low': 'ロー',\n 'mbitMore.digitalValueMenu.High': 'ハイ',\n 'mbitMore.axisMenu.x': 'x',\n 'mbitMore.axisMenu.y': 'y',\n 'mbitMore.axisMenu.z': 'z',\n 'mbitMore.axisMenu.absolute': 'おおきさ',\n 'mbitMore.pinModeMenu.pullNone': 'かいほう',\n 'mbitMore.pinModeMenu.pullUp': 'プルアップ',\n 'mbitMore.pinModeMenu.pullDown': 'プルダウン',\n 'mbitMore.listenPinEventType': 'ピン [PIN] で [EVENT_TYPE]',\n 'mbitMore.pinEventTypeMenu.none': 'イベントをうけない',\n 'mbitMore.pinEventTypeMenu.edge': 'エッジイベントをうける',\n 'mbitMore.pinEventTypeMenu.pulse': 'パルスイベントをうける',\n 'mbitMore.pinEventTypeMenu.touch': 'タッチイベントをうける',\n 'mbitMore.whenPinEvent': 'ピン [PIN] で [EVENT] イベントがあがった',\n 'mbitMore.pinEventMenu.rise': 'ライズ',\n 'mbitMore.pinEventMenu.fall': 'フォール',\n 'mbitMore.pinEventMenu.pulseHigh': 'ハイパルス',\n 'mbitMore.pinEventMenu.pulseLow': 'ローパルス',\n 'mbitMore.getPinEventValue': 'ピン [PIN] の [EVENT]',\n 'mbitMore.pinEventTimestampMenu.rise': 'ライズのじかん',\n 'mbitMore.pinEventTimestampMenu.fall': 'フォールのじかん',\n 'mbitMore.pinEventTimestampMenu.pulseHigh': 'ハイパルスのきかん',\n 'mbitMore.pinEventTimestampMenu.pulseLow': 'ローパルスのきかん',\n 'mbitMore.whenDataReceived': 'micro:bit からラベル [LABEL] のデータをうけとったとき',\n 'mbitMore.getDataLabeled': 'ラベル [LABEL] のデータ',\n 'mbitMore.sendData': 'micro:bit へデータ [DATA] にラベル [LABEL] をつけておくる',\n 'mbitMore.connectionStateMenu.connected': 'つながった',\n 'mbitMore.connectionStateMenu.disconnected': 'きれた',\n 'mbitMore.whenConnectionChanged': 'micro:bit と[STATE]とき'\n },\n 'pt-br': {\n 'mbitMore.lightLevel': 'Intensidade da Luz',\n 'mbitMore.compassHeading': 'Está em direção ao Norte',\n 'mbitMore.magneticForce': 'Força Magnética [AXIS]',\n 'mbitMore.acceleration': 'Aceleração no Eixo[AXIS]',\n 'mbitMore.analogValue': 'Ler Pino Analógico [PIN]',\n 'mbitMore.setInput': 'Definir Pino[PIN] como entrada',\n 'mbitMore.setAnalogOut': 'Definir pino PWM[PIN]com[LEVEL]',\n 'mbitMore.setServo': 'Definir Servo no pino [PIN]com ângulo de [ANGLE]॰',\n 'mbitMore.digitalValueMenu.Low': 'desligado',\n 'mbitMore.digitalValueMenu.High': 'ligado'\n },\n 'pt': {\n 'mbitMore.lightLevel': 'Intensidade da Luz',\n 'mbitMore.compassHeading': 'Está em direção ao Norte',\n 'mbitMore.magneticForce': 'Força Magnética [AXIS]',\n 'mbitMore.acceleration': 'Aceleração no Eixo[AXIS]',\n 'mbitMore.analogValue': 'Ler Pino Analógico [PIN]',\n 'mbitMore.setInput': 'Definir Pino[PIN] como entrada',\n 'mbitMore.setAnalogOut': 'Definir pino PWM[PIN]com[LEVEL]',\n 'mbitMore.setServo': 'Definir Servo no pino [PIN]com ângulo de [ANGLE]॰',\n 'mbitMore.digitalValueMenu.Low': 'desligado',\n 'mbitMore.digitalValueMenu.High': 'ligado'\n }\n};\n\nexports.blockClass = MbitMoreBlocks; // loadable-extension needs this line.\nmodule.exports = MbitMoreBlocks;\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Cast = require('../../util/cast');\nconst formatMessage = require('format-message');\nconst color = require('../../util/color');\nconst BLE = require('../../io/ble');\nconst Base64Util = require('../../util/base64-util');\nconst MathUtil = require('../../util/math-util');\nconst RateLimiter = require('../../util/rateLimiter.js');\nconst log = require('../../util/log');\n\n/**\n * The LEGO Wireless Protocol documentation used to create this extension can be found at:\n * https://lego.github.io/lego-ble-wireless-protocol-docs/index.html\n */\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst iconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACpQTFRF////fIel5ufolZ62/2YavsPS+YZOkJmy9/j53+Hk6+zs6N/b6dfO////tDhMHAAAAA50Uk5T/////////////////wBFwNzIAAAA6ElEQVR42uzX2w6DIBAEUGDVtlr//3dLaLwgiwUd2z7MJPJg5EQWiGhGcAxBggQJEiT436CIfqXJPTn3MKNYYMSDFpoAmp24OaYgvwKnFgL2zvVTCwHrMoMi+nUQLFthaNCCa0iwclLkDgYVsQp0mzxuqXgK1MRzoCLWgkPXNN2wI/q6Kvt7u/cX0HtejN8x2sXpnpb8J8D3b0Keuhh3X975M+i0xNVbg3s1TIasgK21bQyGO+s2PykaGMYbge8KrNrssvkOWDXkErB8UuBHETjoYLkKBA8ZfuDkbwVBggQJEiR4MC8BBgDTtMZLx2nFCQAAAABJRU5ErkJggg==';\n\n/**\n * Boost BLE UUIDs.\n * @enum {string}\n */\nconst BoostBLE = {\n service: '00001623-1212-efde-1623-785feabcd123',\n characteristic: '00001624-1212-efde-1623-785feabcd123',\n sendInterval: 100,\n sendRateMax: 20\n};\n\n/**\n * Boost Motor Max Power Add. Defines how much more power than the target speed\n * the motors may supply to reach the target speed faster.\n * Lower number == softer, slower reached target speed.\n * Higher number == harder, faster reached target speed.\n * @constant {number}\n */\nconst BoostMotorMaxPowerAdd = 10;\n\n/**\n * A time interval to wait (in milliseconds) in between battery check calls.\n * @type {number}\n */\nconst BoostPingInterval = 5000;\n\n/**\n * The number of continuous samples the color-sensor will evaluate color from.\n * @type {number}\n */\nconst BoostColorSampleSize = 5;\n\n/**\n * Enum for Boost sensor and actuator types.\n * @readonly\n * @enum {number}\n */\nconst BoostIO = {\n MOTOR_WEDO: 0x01,\n MOTOR_SYSTEM: 0x02,\n BUTTON: 0x05,\n LIGHT: 0x08,\n VOLTAGE: 0x14,\n CURRENT: 0x15,\n PIEZO: 0x16,\n LED: 0x17,\n TILT_EXTERNAL: 0x22,\n MOTION_SENSOR: 0x23,\n COLOR: 0x25,\n MOTOREXT: 0x26,\n MOTORINT: 0x27,\n TILT: 0x28\n};\n\n/**\n * Enum for ids for various output command feedback types on the Boost.\n * @readonly\n * @enum {number}\n */\nconst BoostPortFeedback = {\n IN_PROGRESS: 0x01,\n COMPLETED: 0x02,\n DISCARDED: 0x04,\n IDLE: 0x08,\n BUSY_OR_FULL: 0x10\n};\n\n/**\n * Enum for physical Boost Ports\n * @readonly\n * @enum {number}\n */\n\nconst BoostPort10000223OrOlder = {\n A: 55,\n B: 56,\n C: 1,\n D: 2\n};\n\nconst BoostPort10000224OrNewer = {\n A: 0,\n B: 1,\n C: 2,\n D: 3\n};\n\n// Set default port mapping to support the newer firmware\nlet BoostPort = BoostPort10000224OrNewer;\n\n/**\n * Ids for each color sensor value used by the extension.\n * @readonly\n * @enum {string}\n */\nconst BoostColor = {\n ANY: 'any',\n NONE: 'none',\n RED: 'red',\n BLUE: 'blue',\n GREEN: 'green',\n YELLOW: 'yellow',\n WHITE: 'white',\n BLACK: 'black'\n};\n\n/**\n * Enum for indices for each color sensed by the Boost vision sensor.\n * @readonly\n * @enum {number}\n */\nconst BoostColorIndex = {\n [BoostColor.NONE]: 255,\n [BoostColor.RED]: 9,\n [BoostColor.BLUE]: 3,\n [BoostColor.GREEN]: 5,\n [BoostColor.YELLOW]: 7,\n [BoostColor.WHITE]: 10,\n [BoostColor.BLACK]: 0\n};\n\n/**\n * Enum for Message Types\n * @readonly\n * @enum {number}\n */\nconst BoostMessage = {\n HUB_PROPERTIES: 0x01,\n HUB_ACTIONS: 0x02,\n HUB_ALERTS: 0x03,\n HUB_ATTACHED_IO: 0x04,\n ERROR: 0x05,\n PORT_INPUT_FORMAT_SETUP_SINGLE: 0x41,\n PORT_INPUT_FORMAT_SETUP_COMBINED: 0x42,\n PORT_INFORMATION: 0x43,\n PORT_MODEINFORMATION: 0x44,\n PORT_VALUE: 0x45,\n PORT_VALUE_COMBINED: 0x46,\n PORT_INPUT_FORMAT: 0x47,\n PORT_INPUT_FORMAT_COMBINED: 0x48,\n OUTPUT: 0x81,\n PORT_FEEDBACK: 0x82\n};\n\n/**\n * Enum for Hub Property Types\n * @readonly\n * @enum {number}\n */\n\nconst BoostHubProperty = {\n ADVERTISEMENT_NAME: 0x01,\n BUTTON: 0x02,\n FW_VERSION: 0x03,\n HW_VERSION: 0x04,\n RSSI: 0x05,\n BATTERY_VOLTAGE: 0x06,\n BATTERY_TYPE: 0x07,\n MANUFACTURER_NAME: 0x08,\n RADIO_FW_VERSION: 0x09,\n LEGO_WP_VERSION: 0x0A,\n SYSTEM_TYPE_ID: 0x0B,\n HW_NETWORK_ID: 0x0C,\n PRIMARY_MAC: 0x0D,\n SECONDARY_MAC: 0x0E,\n HW_NETWORK_FAMILY: 0x0F\n};\n\n/**\n * Enum for Hub Property Operations\n * @readonly\n * @enum {number}\n */\n\nconst BoostHubPropertyOperation = {\n SET: 0x01,\n ENABLE_UPDATES: 0x02,\n DISABLE_UPDATES: 0x03,\n RESET: 0x04,\n REQUEST_UPDATE: 0x05,\n UPDATE: 0x06\n};\n\n/**\n * Enum for Motor Subcommands (for 0x81)\n * @readonly\n * @enum {number}\n */\nconst BoostOutputSubCommand = {\n START_POWER: 0x01,\n START_POWER_PAIR: 0x02,\n SET_ACC_TIME: 0x05,\n SET_DEC_TIME: 0x06,\n START_SPEED: 0x07,\n START_SPEED_PAIR: 0x08,\n START_SPEED_FOR_TIME: 0x09,\n START_SPEED_FOR_TIME_PAIR: 0x0A,\n START_SPEED_FOR_DEGREES: 0x0B,\n START_SPEED_FOR_DEGREES_PAIR: 0x0C,\n GO_TO_ABS_POSITION: 0x0D,\n GO_TO_ABS_POSITION_PAIR: 0x0E,\n PRESET_ENCODER: 0x14,\n WRITE_DIRECT_MODE_DATA: 0x51\n};\n\n/**\n * Enum for Startup/Completion information for an output command.\n * Startup and completion bytes must be OR'ed to be combined to a single byte.\n * @readonly\n * @enum {number}\n */\nconst BoostOutputExecution = {\n // Startup information\n BUFFER_IF_NECESSARY: 0x00,\n EXECUTE_IMMEDIATELY: 0x10,\n // Completion information\n NO_ACTION: 0x00,\n COMMAND_FEEDBACK: 0x01\n};\n\n/**\n * Enum for Boost Motor end states\n * @readonly\n * @enum {number}\n */\nconst BoostMotorEndState = {\n FLOAT: 0,\n HOLD: 126,\n BRAKE: 127\n};\n\n/**\n * Enum for Boost Motor acceleration/deceleration profiles\n * @readyonly\n * @enum {number}\n */\nconst BoostMotorProfile = {\n DO_NOT_USE: 0x00,\n ACCELERATION: 0x01,\n DECELERATION: 0x02\n};\n\n/**\n * Enum for when Boost IO's are attached/detached\n * @readonly\n * @enum {number}\n */\nconst BoostIOEvent = {\n ATTACHED: 0x01,\n DETACHED: 0x00,\n ATTACHED_VIRTUAL: 0x02\n};\n\n/**\n * Enum for selected sensor modes.\n * @enum {number}\n */\nconst BoostMode = {\n TILT: 0, // angle (pitch/yaw)\n LED: 1, // Set LED to accept RGB values\n COLOR: 0, // Read indexed colors from Vision Sensor\n MOTOR_SENSOR: 2, // Set motors to report their position\n UNKNOWN: 0 // Anything else will use the default mode (mode 0)\n};\n\n/**\n * Enum for Boost motor states.\n * @param {number}\n */\nconst BoostMotorState = {\n OFF: 0,\n ON_FOREVER: 1,\n ON_FOR_TIME: 2,\n ON_FOR_ROTATION: 3\n};\n\n/**\n * Helper function for converting a JavaScript number to an INT32-number\n * @param {number} number - a number\n * @return {array} - a 4-byte array of Int8-values representing an INT32-number\n */\nconst numberToInt32Array = function (number) {\n const buffer = new ArrayBuffer(4);\n const dataview = new DataView(buffer);\n dataview.setInt32(0, number);\n return [\n dataview.getInt8(3),\n dataview.getInt8(2),\n dataview.getInt8(1),\n dataview.getInt8(0)\n ];\n};\n\n/**\n * Helper function for converting a regular array to a Little Endian INT32-value\n * @param {Array} array - an array containing UInt8-values\n * @return {number} - a number\n */\nconst int32ArrayToNumber = function (array) {\n const i = Uint8Array.from(array);\n const d = new DataView(i.buffer);\n return d.getInt32(0, true);\n};\n\n/**\n * Manage power, direction, position, and timers for one Boost motor.\n */\nclass BoostMotor {\n /**\n * Construct a Boost Motor instance.\n * @param {Boost} parent - the Boost peripheral which owns this motor.\n * @param {int} index - the zero-based index of this motor on its parent peripheral.\n */\n constructor (parent, index) {\n /**\n * The Boost peripheral which owns this motor.\n * @type {Boost}\n * @private\n */\n this._parent = parent;\n\n /**\n * The zero-based index of this motor on its parent peripheral.\n * @type {int}\n * @private\n */\n this._index = index;\n\n /**\n * This motor's current direction: 1 for \"this way\" or -1 for \"that way\"\n * @type {number}\n * @private\n */\n this._direction = 1;\n\n /**\n * This motor's current power level, in the range [0,100].\n * @type {number}\n * @private\n */\n this._power = 50;\n\n /**\n * This motor's current relative position\n * @type {number}\n * @private\n */\n this._position = 0;\n\n /**\n * Is this motor currently moving?\n * @type {boolean}\n * @private\n */\n this._status = BoostMotorState.OFF;\n\n /**\n * If the motor has been turned on or is actively braking for a specific duration, this is the timeout ID for\n * the end-of-action handler. Cancel this when changing plans.\n * @type {Object}\n * @private\n */\n this._pendingDurationTimeoutId = null;\n\n /**\n * The starting time for the pending duration timeout.\n * @type {number}\n * @private\n */\n this._pendingDurationTimeoutStartTime = null;\n\n /**\n * The delay/duration of the pending duration timeout.\n * @type {number}\n * @private\n */\n this._pendingDurationTimeoutDelay = null;\n\n /**\n * The target position of a turn-based command.\n * @type {number}\n * @private\n */\n this._pendingRotationDestination = null;\n\n /**\n * If the motor has been turned on run for a specific rotation, this is the function\n * that will be called once Scratch VM gets a notification from the Move Hub.\n * @type {Object}\n * @private\n */\n this._pendingRotationPromise = null;\n\n this.turnOff = this.turnOff.bind(this);\n }\n\n /**\n * @return {int} - this motor's current direction: 1 for \"this way\" or -1 for \"that way\"\n */\n get direction () {\n return this._direction;\n }\n\n /**\n * @param {int} value - this motor's new direction: 1 for \"this way\" or -1 for \"that way\"\n */\n set direction (value) {\n if (value < 0) {\n this._direction = -1;\n } else {\n this._direction = 1;\n }\n }\n\n /**\n * @return {int} - this motor's current power level, in the range [0,100].\n */\n get power () {\n return this._power;\n }\n\n /**\n * @param {int} value - this motor's new power level, in the range [10,100].\n */\n set power (value) {\n /**\n * Scale the motor power to a range between 10 and 100,\n * to make sure the motors will run with something built onto them.\n */\n if (value === 0) {\n this._power = 0;\n } else {\n this._power = MathUtil.scale(value, 1, 100, 10, 100);\n }\n }\n\n /**\n * @return {int} - this motor's current position, in the range of [-MIN_INT32,MAX_INT32]\n */\n get position () {\n return this._position;\n }\n\n /**\n * @param {int} value - set this motor's current position.\n */\n set position (value) {\n this._position = value;\n }\n\n /**\n * @return {BoostMotorState} - the motor's current state.\n */\n get status () {\n return this._status;\n }\n\n /**\n * @param {BoostMotorState} value - set this motor's state.\n */\n set status (value) {\n this._clearRotationState();\n this._clearDurationTimeout();\n this._status = value;\n }\n\n /**\n * @return {number} - time, in milliseconds, of when the pending duration timeout began.\n */\n get pendingDurationTimeoutStartTime () {\n return this._pendingDurationTimeoutStartTime;\n }\n\n /**\n * @return {number} - delay, in milliseconds, of the pending duration timeout.\n */\n get pendingDurationTimeoutDelay () {\n return this._pendingDurationTimeoutDelay;\n }\n\n /**\n * @return {number} - target position, in degrees, of the pending rotation.\n */\n get pendingRotationDestination () {\n return this._pendingRotationDestination;\n }\n\n /**\n * @return {Promise} - the Promise function for the pending rotation.\n */\n get pendingRotationPromise () {\n return this._pendingRotationPromise;\n }\n\n /**\n * @param {function} func - function to resolve pending rotation Promise\n */\n set pendingRotationPromise (func) {\n this._pendingRotationPromise = func;\n }\n\n /**\n * Turn this motor on indefinitely\n * @private\n */\n _turnOn () {\n const cmd = this._parent.generateOutputCommand(\n this._index,\n BoostOutputExecution.EXECUTE_IMMEDIATELY,\n BoostOutputSubCommand.START_SPEED,\n [\n this.power * this.direction,\n MathUtil.clamp(this.power + BoostMotorMaxPowerAdd, 0, 100),\n BoostMotorProfile.DO_NOT_USE\n ]);\n\n this._parent.send(BoostBLE.characteristic, cmd);\n }\n\n /**\n * Turn this motor on indefinitely\n */\n turnOnForever () {\n this.status = BoostMotorState.ON_FOREVER;\n this._turnOn();\n }\n\n /**\n * Turn this motor on for a specific duration.\n * @param {number} milliseconds - run the motor for this long.\n */\n turnOnFor (milliseconds) {\n milliseconds = Math.max(0, milliseconds);\n this.status = BoostMotorState.ON_FOR_TIME;\n this._turnOn();\n this._setNewDurationTimeout(this.turnOff, milliseconds);\n }\n\n /**\n * Turn this motor on for a specific rotation in degrees.\n * @param {number} degrees - run the motor for this amount of degrees.\n * @param {number} direction - rotate in this direction\n */\n turnOnForDegrees (degrees, direction) {\n degrees = Math.max(0, degrees);\n\n const cmd = this._parent.generateOutputCommand(\n this._index,\n (BoostOutputExecution.EXECUTE_IMMEDIATELY ^ BoostOutputExecution.COMMAND_FEEDBACK),\n BoostOutputSubCommand.START_SPEED_FOR_DEGREES,\n [\n ...numberToInt32Array(degrees),\n this.power * this.direction * direction,\n MathUtil.clamp(this.power + BoostMotorMaxPowerAdd, 0, 100),\n BoostMotorEndState.BRAKE,\n BoostMotorProfile.DO_NOT_USE\n ]\n );\n\n this.status = BoostMotorState.ON_FOR_ROTATION;\n this._pendingRotationDestination = this.position + (degrees * this.direction * direction);\n this._parent.send(BoostBLE.characteristic, cmd);\n }\n\n /**\n * Turn this motor off.\n * @param {boolean} [useLimiter=true] - if true, use the rate limiter\n */\n turnOff (useLimiter = true) {\n const cmd = this._parent.generateOutputCommand(\n this._index,\n BoostOutputExecution.EXECUTE_IMMEDIATELY,\n BoostOutputSubCommand.START_POWER,\n [\n BoostMotorEndState.FLOAT\n ]\n );\n\n this.status = BoostMotorState.OFF;\n this._parent.send(BoostBLE.characteristic, cmd, useLimiter);\n }\n\n /**\n * Clear the motor action timeout, if any. Safe to call even when there is no pending timeout.\n * @private\n */\n _clearDurationTimeout () {\n if (this._pendingDurationTimeoutId !== null) {\n clearTimeout(this._pendingDurationTimeoutId);\n this._pendingDurationTimeoutId = null;\n this._pendingDurationTimeoutStartTime = null;\n this._pendingDurationTimeoutDelay = null;\n }\n }\n\n /**\n * Set a new motor action timeout, after clearing an existing one if necessary.\n * @param {Function} callback - to be called at the end of the timeout.\n * @param {int} delay - wait this many milliseconds before calling the callback.\n * @private\n */\n _setNewDurationTimeout (callback, delay) {\n this._clearDurationTimeout();\n const timeoutID = setTimeout(() => {\n if (this._pendingDurationTimeoutId === timeoutID) {\n this._pendingDurationTimeoutId = null;\n this._pendingDurationTimeoutStartTime = null;\n this._pendingDurationTimeoutDelay = null;\n }\n callback();\n }, delay);\n this._pendingDurationTimeoutId = timeoutID;\n this._pendingDurationTimeoutStartTime = Date.now();\n this._pendingDurationTimeoutDelay = delay;\n }\n\n /**\n * Clear the motor states related to rotation-based commands, if any.\n * Safe to call even when there is no pending promise function.\n * @private\n */\n _clearRotationState () {\n if (this._pendingRotationPromise !== null) {\n this._pendingRotationPromise();\n this._pendingRotationPromise = null;\n }\n this._pendingRotationDestination = null;\n }\n}\n\n/**\n * Manage communication with a Boost peripheral over a Bluetooth Low Energy client socket.\n */\nclass Boost {\n\n constructor (runtime, extensionId) {\n\n /**\n * The Scratch 3.0 runtime used to trigger the green flag button.\n * @type {Runtime}\n * @private\n */\n this._runtime = runtime;\n this._runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this));\n\n /**\n * The id of the extension this peripheral belongs to.\n */\n this._extensionId = extensionId;\n\n /**\n * A list of the ids of the physical or virtual sensors.\n * @type {string[]}\n * @private\n */\n this._ports = [];\n\n /**\n * A list of motors registered by the Boost hardware.\n * @type {BoostMotor[]}\n * @private\n */\n this._motors = [];\n\n /**\n * The most recently received value for each sensor.\n * @type {Object.}\n * @private\n */\n this._sensors = {\n tiltX: 0,\n tiltY: 0,\n color: BoostColor.NONE,\n previousColor: BoostColor.NONE\n };\n\n /**\n * An array of values from the Boost Vision Sensor.\n * @type {Array}\n * @private\n */\n this._colorSamples = [];\n\n /**\n * The Bluetooth connection socket for reading/writing peripheral data.\n * @type {BLE}\n * @private\n */\n this._ble = null;\n this._runtime.registerPeripheralExtension(extensionId, this);\n\n /**\n * A rate limiter utility, to help limit the rate at which we send BLE messages\n * over the socket to Scratch Link to a maximum number of sends per second.\n * @type {RateLimiter}\n * @private\n */\n this._rateLimiter = new RateLimiter(BoostBLE.sendRateMax);\n\n /**\n * An interval id for the battery check interval.\n * @type {number}\n * @private\n */\n this._pingDeviceId = null;\n\n this.reset = this.reset.bind(this);\n this._onConnect = this._onConnect.bind(this);\n this._onMessage = this._onMessage.bind(this);\n this._pingDevice = this._pingDevice.bind(this);\n }\n\n /**\n * @return {number} - the latest value received for the tilt sensor's tilt about the X axis.\n */\n get tiltX () {\n return this._sensors.tiltX;\n }\n\n /**\n * @return {number} - the latest value received for the tilt sensor's tilt about the Y axis.\n */\n get tiltY () {\n return this._sensors.tiltY;\n }\n\n /**\n * @return {number} - the latest color value received from the vision sensor.\n */\n get color () {\n return this._sensors.color;\n }\n\n /**\n * @return {number} - the previous color value received from the vision sensor.\n */\n get previousColor () {\n return this._sensors.previousColor;\n }\n\n /**\n * Look up the color id for an index received from the vision sensor.\n * @param {number} index - the color index to look up.\n * @return {BoostColor} the color id for this index.\n */\n boostColorForIndex (index) {\n const colorForIndex = Object.keys(BoostColorIndex).find(key => BoostColorIndex[key] === index);\n return colorForIndex || BoostColor.NONE;\n }\n\n /**\n * Access a particular motor on this peripheral.\n * @param {int} index - the index of the desired motor.\n * @return {BoostMotor} - the BoostMotor instance, if any, at that index.\n */\n motor (index) {\n return this._motors[index];\n }\n\n /**\n * Stop all the motors that are currently running.\n */\n stopAllMotors () {\n this._motors.forEach(motor => {\n if (motor) {\n // Send the motor off command without using the rate limiter.\n // This allows the stop button to stop motors even if we are\n // otherwise flooded with commands.\n motor.turnOff(false);\n }\n });\n }\n\n /**\n * Set the Boost peripheral's LED to a specific color.\n * @param {int} inputRGB - a 24-bit RGB color in 0xRRGGBB format.\n * @return {Promise} - a promise of the completion of the set led send operation.\n */\n setLED (inputRGB) {\n const rgb = [\n (inputRGB >> 16) & 0x000000FF,\n (inputRGB >> 8) & 0x000000FF,\n (inputRGB) & 0x000000FF\n ];\n\n const cmd = this.generateOutputCommand(\n this._ports.indexOf(BoostIO.LED),\n BoostOutputExecution.EXECUTE_IMMEDIATELY ^ BoostOutputExecution.COMMAND_FEEDBACK,\n BoostOutputSubCommand.WRITE_DIRECT_MODE_DATA,\n [BoostMode.LED,\n ...rgb]\n );\n\n return this.send(BoostBLE.characteristic, cmd);\n }\n\n /**\n * Sets the input mode of the LED to RGB.\n * @return {Promise} - a promise returned by the send operation.\n */\n setLEDMode () {\n const cmd = this.generateInputCommand(\n this._ports.indexOf(BoostIO.LED),\n BoostMode.LED,\n 0,\n false\n );\n\n return this.send(BoostBLE.characteristic, cmd);\n }\n\n /**\n * Stop the motors on the Boost peripheral.\n */\n stopAll () {\n if (!this.isConnected()) return;\n this.stopAllMotors();\n }\n\n /**\n * Called by the runtime when user wants to scan for a Boost peripheral.\n */\n scan () {\n if (this._ble) {\n this._ble.disconnect();\n }\n this._ble = new BLE(this._runtime, this._extensionId, {\n filters: [{\n services: [BoostBLE.service],\n manufacturerData: {\n 0x0397: {\n dataPrefix: [0x00, 0x40],\n mask: [0x00, 0xFF]\n }\n }\n }],\n optionalServices: []\n }, this._onConnect, this.reset);\n }\n\n /**\n * Called by the runtime when user wants to connect to a certain Boost peripheral.\n * @param {number} id - the id of the peripheral to connect to.\n */\n connect (id) {\n if (this._ble) {\n this._ble.connectPeripheral(id);\n }\n }\n\n /**\n * Disconnects from the current BLE socket and resets state.\n */\n disconnect () {\n if (this._ble) {\n this._ble.disconnect();\n }\n\n this.reset();\n }\n\n /**\n * Reset all the state and timeout/interval ids.\n */\n reset () {\n this._ports = [];\n this._motors = [];\n this._sensors = {\n tiltX: 0,\n tiltY: 0,\n color: BoostColor.NONE,\n previousColor: BoostColor.NONE\n };\n\n if (this._pingDeviceId) {\n window.clearInterval(this._pingDeviceId);\n this._pingDeviceId = null;\n }\n }\n\n /**\n * Called by the runtime to detect whether the Boost peripheral is connected.\n * @return {boolean} - the connected state.\n */\n isConnected () {\n let connected = false;\n if (this._ble) {\n connected = this._ble.isConnected();\n }\n return connected;\n }\n\n /**\n * Write a message to the Boost peripheral BLE socket.\n * @param {number} uuid - the UUID of the characteristic to write to\n * @param {Array} message - the message to write.\n * @param {boolean} [useLimiter=true] - if true, use the rate limiter\n * @return {Promise} - a promise result of the write operation\n */\n send (uuid, message, useLimiter = true) {\n if (!this.isConnected()) return Promise.resolve();\n\n if (useLimiter) {\n if (!this._rateLimiter.okayToSend()) return Promise.resolve();\n }\n\n return this._ble.write(\n BoostBLE.service,\n uuid,\n Base64Util.uint8ArrayToBase64(message),\n 'base64'\n );\n }\n\n /**\n * Generate a Boost 'Output Command' in the byte array format\n * (COMMON HEADER, PORT ID, EXECUTION BYTE, SUBCOMMAND ID, PAYLOAD).\n *\n * Payload is accepted as an array since these vary across different subcommands.\n *\n * @param {number} portID - the port (Connect ID) to send a command to.\n * @param {number} execution - Byte containing startup/completion information\n * @param {number} subCommand - the id of the subcommand byte.\n * @param {array} payload - the list of bytes to send as subcommand payload\n * @return {array} - a generated output command.\n */\n generateOutputCommand (portID, execution, subCommand, payload) {\n const hubID = 0x00;\n const command = [hubID, BoostMessage.OUTPUT, portID, execution, subCommand, ...payload];\n command.unshift(command.length + 1); // Prepend payload with length byte;\n\n return command;\n }\n\n /**\n * Generate a Boost 'Input Command' in the byte array format\n * (COMMAND ID, COMMAND TYPE, CONNECT ID, TYPE ID, MODE, DELTA INTERVAL (4 BYTES),\n * UNIT, NOTIFICATIONS ENABLED).\n *\n * This sends a command to the Boost that sets that input format\n * of the specified inputs and sets value change notifications.\n *\n * @param {number} portID - the port (Connect ID) to send a command to.\n * @param {number} mode - the mode of the input sensor.\n * @param {number} delta - the delta change needed to trigger notification.\n * @param {boolean} enableNotifications - whether to enable notifications.\n * @return {array} - a generated input command.\n */\n generateInputCommand (portID, mode, delta, enableNotifications) {\n const command = [\n 0x00, // Hub ID\n BoostMessage.PORT_INPUT_FORMAT_SETUP_SINGLE,\n portID,\n mode\n ].concat(numberToInt32Array(delta)).concat([\n enableNotifications\n ]);\n command.unshift(command.length + 1); // Prepend payload with length byte;\n\n return command;\n }\n\n /**\n * Starts reading data from peripheral after BLE has connected.\n * @private\n */\n _onConnect () {\n this._ble.startNotifications(\n BoostBLE.service,\n BoostBLE.characteristic,\n this._onMessage\n );\n this._pingDeviceId = window.setInterval(this._pingDevice, BoostPingInterval);\n\n // Send a request for firmware version.\n setTimeout(() => {\n const command = [\n 0x00, // Hub ID\n BoostMessage.HUB_PROPERTIES,\n BoostHubProperty.FW_VERSION,\n BoostHubPropertyOperation.REQUEST_UPDATE\n ];\n command.unshift(command.length + 1);\n this.send(BoostBLE.characteristic, command, false);\n }, 500);\n\n }\n\n /**\n * Process the sensor data from the incoming BLE characteristic.\n * @param {object} base64 - the incoming BLE data.\n * @private\n */\n _onMessage (base64) {\n const data = Base64Util.base64ToUint8Array(base64);\n\n /**\n * First three bytes are the common header:\n * 0: Length of message\n * 1: Hub ID (always 0x00 at the moment, unused)\n * 2: Message Type\n * 3: Port ID\n * We base our switch-case on Message Type\n */\n\n const messageType = data[2];\n const portID = data[3];\n\n switch (messageType) {\n \n case BoostMessage.HUB_PROPERTIES: {\n const property = data[3];\n switch (property) {\n case BoostHubProperty.FW_VERSION: {\n // Establish firmware version 1.0.00.0224 as a 32-bit signed integer (little endian)\n const fwVersion10000224 = int32ArrayToNumber([0x24, 0x02, 0x00, 0x10]);\n const fwHub = int32ArrayToNumber(data.slice(5, data.length));\n if (fwHub < fwVersion10000224) {\n BoostPort = BoostPort10000223OrOlder;\n log.info('Move Hub firmware older than version 1.0.00.0224 detected. Using old port mapping.');\n } else {\n BoostPort = BoostPort10000224OrNewer;\n }\n break;\n }\n }\n break;\n }\n case BoostMessage.HUB_ATTACHED_IO: { // IO Attach/Detach events\n const event = data[4];\n const typeId = data[5];\n\n switch (event) {\n case BoostIOEvent.ATTACHED:\n this._registerSensorOrMotor(portID, typeId);\n break;\n case BoostIOEvent.DETACHED:\n this._clearPort(portID);\n break;\n case BoostIOEvent.ATTACHED_VIRTUAL:\n default:\n }\n break;\n }\n case BoostMessage.PORT_VALUE: {\n const type = this._ports[portID];\n\n switch (type) {\n case BoostIO.TILT:\n this._sensors.tiltX = data[4];\n this._sensors.tiltY = data[5];\n break;\n case BoostIO.COLOR:\n this._colorSamples.unshift(data[4]);\n if (this._colorSamples.length > BoostColorSampleSize) {\n this._colorSamples.pop();\n if (this._colorSamples.every((v, i, arr) => v === arr[0])) {\n this._sensors.previousColor = this._sensors.color;\n this._sensors.color = this.boostColorForIndex(this._colorSamples[0]);\n } else {\n this._sensors.color = BoostColor.NONE;\n }\n } else {\n this._sensors.color = BoostColor.NONE;\n }\n break;\n case BoostIO.MOTOREXT:\n case BoostIO.MOTORINT:\n this.motor(portID).position = int32ArrayToNumber(data.slice(4, 8));\n break;\n case BoostIO.CURRENT:\n case BoostIO.VOLTAGE:\n case BoostIO.LED:\n break;\n default:\n log.warn(`Unknown sensor value! Type: ${type}`);\n }\n break;\n }\n case BoostMessage.PORT_FEEDBACK: {\n const feedback = data[4];\n const motor = this.motor(portID);\n if (motor) {\n // Makes sure that commands resolve both when they actually complete and when they fail\n const isBusy = feedback & BoostPortFeedback.IN_PROGRESS;\n const commandCompleted = feedback & (BoostPortFeedback.COMPLETED ^ BoostPortFeedback.DISCARDED);\n if (!isBusy && commandCompleted) {\n if (motor.status === BoostMotorState.ON_FOR_ROTATION) {\n motor.status = BoostMotorState.OFF;\n }\n }\n }\n break;\n }\n case BoostMessage.ERROR:\n log.warn(`Error reported by hub: ${data}`);\n break;\n }\n }\n\n /**\n * Ping the Boost hub. If the Boost hub has disconnected\n * for some reason, the BLE socket will get an error back and automatically\n * close the socket.\n * @private\n */\n _pingDevice () {\n this._ble.read(\n BoostBLE.service,\n BoostBLE.characteristic,\n false\n );\n }\n\n /**\n * Register a new sensor or motor connected at a port. Store the type of\n * sensor or motor internally, and then register for notifications on input\n * values if it is a sensor.\n * @param {number} portID - the port to register a sensor or motor on.\n * @param {number} type - the type ID of the sensor or motor\n * @private\n */\n _registerSensorOrMotor (portID, type) {\n // Record which port is connected to what type of device\n this._ports[portID] = type;\n\n // Record motor port\n if (type === BoostIO.MOTORINT || type === BoostIO.MOTOREXT) {\n this._motors[portID] = new BoostMotor(this, portID);\n }\n\n // Set input format for tilt or distance sensor\n let mode = null;\n let delta = 1;\n\n switch (type) {\n case BoostIO.MOTORINT:\n case BoostIO.MOTOREXT:\n mode = BoostMode.MOTOR_SENSOR;\n break;\n case BoostIO.COLOR:\n mode = BoostMode.COLOR;\n delta = 0;\n break;\n case BoostIO.LED:\n mode = BoostMode.LED;\n /**\n * Sets the LED to blue to give an indication on the hub\n * that it has connected successfully.\n */\n this.setLEDMode();\n this.setLED(0x0000FF);\n break;\n case BoostIO.TILT:\n mode = BoostMode.TILT;\n break;\n default:\n mode = BoostMode.UNKNOWN;\n }\n\n const cmd = this.generateInputCommand(\n portID,\n mode,\n delta,\n true // Receive feedback\n );\n\n this.send(BoostBLE.characteristic, cmd);\n }\n\n /**\n * Clear the sensors or motors present on the ports.\n * @param {number} portID - the port to clear.\n * @private\n */\n _clearPort (portID) {\n const type = this._ports[portID];\n if (type === BoostIO.TILT) {\n this._sensors.tiltX = this._sensors.tiltY = 0;\n }\n if (type === BoostIO.COLOR) {\n this._sensors.color = BoostColor.NONE;\n }\n this._ports[portID] = 'none';\n this._motors[portID] = null;\n }\n}\n\n/**\n * Enum for motor specification.\n * @readonly\n * @enum {string}\n */\nconst BoostMotorLabel = {\n A: 'A',\n B: 'B',\n C: 'C',\n D: 'D',\n AB: 'AB',\n ALL: 'ABCD'\n};\n\n/**\n * Enum for motor direction specification.\n * @readonly\n * @enum {string}\n */\nconst BoostMotorDirection = {\n FORWARD: 'this way',\n BACKWARD: 'that way',\n REVERSE: 'reverse'\n};\n\n/**\n * Enum for tilt sensor direction.\n * @readonly\n * @enum {string}\n */\nconst BoostTiltDirection = {\n UP: 'up',\n DOWN: 'down',\n LEFT: 'left',\n RIGHT: 'right',\n ANY: 'any'\n};\n\n/**\n * Scratch 3.0 blocks to interact with a LEGO Boost peripheral.\n */\nclass Scratch3BoostBlocks {\n\n /**\n * @return {string} - the ID of this extension.\n */\n static get EXTENSION_ID () {\n return 'boost';\n }\n\n /**\n * @return {number} - the tilt sensor counts as \"tilted\" if its tilt angle meets or exceeds this threshold.\n */\n static get TILT_THRESHOLD () {\n return 15;\n }\n\n /**\n * Construct a set of Boost blocks.\n * @param {Runtime} runtime - the Scratch 3.0 runtime.\n */\n constructor (runtime) {\n /**\n * The Scratch 3.0 runtime.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n // Create a new Boost peripheral instance\n this._peripheral = new Boost(this.runtime, Scratch3BoostBlocks.EXTENSION_ID);\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: Scratch3BoostBlocks.EXTENSION_ID,\n name: 'BOOST',\n blockIconURI: iconURI,\n showStatusButton: true,\n blocks: [\n {\n opcode: 'motorOnFor',\n text: formatMessage({\n id: 'boost.motorOnFor',\n default: 'turn motor [MOTOR_ID] for [DURATION] seconds',\n description: 'turn a motor on for some time'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: BoostMotorLabel.A\n },\n DURATION: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n }\n },\n {\n opcode: 'motorOnForRotation',\n text: formatMessage({\n id: 'boost.motorOnForRotation',\n default: 'turn motor [MOTOR_ID] for [ROTATION] rotations',\n description: 'turn a motor on for rotation'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: BoostMotorLabel.A\n },\n ROTATION: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n }\n },\n {\n opcode: 'motorOn',\n text: formatMessage({\n id: 'boost.motorOn',\n default: 'turn motor [MOTOR_ID] on',\n description: 'turn a motor on indefinitely'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: BoostMotorLabel.A\n }\n }\n },\n {\n opcode: 'motorOff',\n text: formatMessage({\n id: 'boost.motorOff',\n default: 'turn motor [MOTOR_ID] off',\n description: 'turn a motor off'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: BoostMotorLabel.A\n }\n }\n },\n {\n opcode: 'setMotorPower',\n text: formatMessage({\n id: 'boost.setMotorPower',\n default: 'set motor [MOTOR_ID] speed to [POWER] %',\n description: 'set the motor\\'s speed without turning it on'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: BoostMotorLabel.ALL\n },\n POWER: {\n type: ArgumentType.NUMBER,\n defaultValue: 100\n }\n }\n },\n {\n opcode: 'setMotorDirection',\n text: formatMessage({\n id: 'boost.setMotorDirection',\n default: 'set motor [MOTOR_ID] direction [MOTOR_DIRECTION]',\n description: 'set the motor\\'s turn direction without turning it on'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: BoostMotorLabel.A\n },\n MOTOR_DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_DIRECTION',\n defaultValue: BoostMotorDirection.FORWARD\n }\n }\n },\n {\n opcode: 'getMotorPosition',\n text: formatMessage({\n id: 'boost.getMotorPosition',\n default: 'motor [MOTOR_REPORTER_ID] position',\n description: 'the position returned by the motor'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n MOTOR_REPORTER_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_REPORTER_ID',\n defaultValue: BoostMotorLabel.A\n }\n }\n },\n {\n opcode: 'whenColor',\n text: formatMessage({\n id: 'boost.whenColor',\n default: 'when [COLOR] brick seen',\n description: 'check for when color'\n }),\n blockType: BlockType.HAT,\n arguments: {\n COLOR: {\n type: ArgumentType.STRING,\n menu: 'COLOR',\n defaultValue: BoostColor.ANY\n }\n }\n },\n {\n opcode: 'seeingColor',\n text: formatMessage({\n id: 'boost.seeingColor',\n default: 'seeing [COLOR] brick?',\n description: 'is the color sensor seeing a certain color?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n COLOR: {\n type: ArgumentType.STRING,\n menu: 'COLOR',\n defaultValue: BoostColor.ANY\n }\n }\n },\n {\n opcode: 'whenTilted',\n text: formatMessage({\n id: 'boost.whenTilted',\n default: 'when tilted [TILT_DIRECTION_ANY]',\n description: 'check when tilted in a certain direction'\n }),\n func: 'isTilted',\n blockType: BlockType.HAT,\n arguments: {\n TILT_DIRECTION_ANY: {\n type: ArgumentType.STRING,\n menu: 'TILT_DIRECTION_ANY',\n defaultValue: BoostTiltDirection.ANY\n }\n }\n },\n {\n opcode: 'getTiltAngle',\n text: formatMessage({\n id: 'boost.getTiltAngle',\n default: 'tilt angle [TILT_DIRECTION]',\n description: 'the angle returned by the tilt sensor'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n TILT_DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'TILT_DIRECTION',\n defaultValue: BoostTiltDirection.UP\n }\n }\n },\n {\n opcode: 'setLightHue',\n text: formatMessage({\n id: 'boost.setLightHue',\n default: 'set light color to [HUE]',\n description: 'set the LED color'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n HUE: {\n type: ArgumentType.NUMBER,\n defaultValue: 50\n }\n }\n }\n ],\n menus: {\n MOTOR_ID: {\n acceptReporters: true,\n items: [\n {\n text: 'A',\n value: BoostMotorLabel.A\n },\n {\n text: 'B',\n value: BoostMotorLabel.B\n },\n {\n text: 'C',\n value: BoostMotorLabel.C\n },\n {\n text: 'D',\n value: BoostMotorLabel.D\n },\n {\n text: 'AB',\n value: BoostMotorLabel.AB\n },\n {\n text: 'ABCD',\n value: BoostMotorLabel.ALL\n }\n ]\n },\n MOTOR_REPORTER_ID: {\n acceptReporters: true,\n items: [\n {\n text: 'A',\n value: BoostMotorLabel.A\n },\n {\n text: 'B',\n value: BoostMotorLabel.B\n },\n {\n text: 'C',\n value: BoostMotorLabel.C\n },\n {\n text: 'D',\n value: BoostMotorLabel.D\n }\n ]\n },\n MOTOR_DIRECTION: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'boost.motorDirection.forward',\n default: 'this way',\n description:\n 'label for forward element in motor direction menu for LEGO Boost extension'\n }),\n value: BoostMotorDirection.FORWARD\n },\n {\n text: formatMessage({\n id: 'boost.motorDirection.backward',\n default: 'that way',\n description:\n 'label for backward element in motor direction menu for LEGO Boost extension'\n }),\n value: BoostMotorDirection.BACKWARD\n },\n {\n text: formatMessage({\n id: 'boost.motorDirection.reverse',\n default: 'reverse',\n description:\n 'label for reverse element in motor direction menu for LEGO Boost extension'\n }),\n value: BoostMotorDirection.REVERSE\n }\n ]\n },\n TILT_DIRECTION: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'boost.tiltDirection.up',\n default: 'up',\n description: 'label for up element in tilt direction menu for LEGO Boost extension'\n }),\n value: BoostTiltDirection.UP\n },\n {\n text: formatMessage({\n id: 'boost.tiltDirection.down',\n default: 'down',\n description: 'label for down element in tilt direction menu for LEGO Boost extension'\n }),\n value: BoostTiltDirection.DOWN\n },\n {\n text: formatMessage({\n id: 'boost.tiltDirection.left',\n default: 'left',\n description: 'label for left element in tilt direction menu for LEGO Boost extension'\n }),\n value: BoostTiltDirection.LEFT\n },\n {\n text: formatMessage({\n id: 'boost.tiltDirection.right',\n default: 'right',\n description: 'label for right element in tilt direction menu for LEGO Boost extension'\n }),\n value: BoostTiltDirection.RIGHT\n }\n ]\n },\n TILT_DIRECTION_ANY: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'boost.tiltDirection.up',\n default: 'up'\n }),\n value: BoostTiltDirection.UP\n },\n {\n text: formatMessage({\n id: 'boost.tiltDirection.down',\n default: 'down'\n }),\n value: BoostTiltDirection.DOWN\n },\n {\n text: formatMessage({\n id: 'boost.tiltDirection.left',\n default: 'left'\n }),\n value: BoostTiltDirection.LEFT\n },\n {\n text: formatMessage({\n id: 'boost.tiltDirection.right',\n default: 'right'\n }),\n value: BoostTiltDirection.RIGHT\n },\n {\n text: formatMessage({\n id: 'boost.tiltDirection.any',\n default: 'any',\n description: 'label for any element in tilt direction menu for LEGO Boost extension'\n }),\n value: BoostTiltDirection.ANY\n }\n ]\n },\n COLOR: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'boost.color.red',\n default: 'red',\n description: 'the color red'\n }),\n value: BoostColor.RED\n },\n {\n text: formatMessage({\n id: 'boost.color.blue',\n default: 'blue',\n description: 'the color blue'\n }),\n value: BoostColor.BLUE\n },\n {\n text: formatMessage({\n id: 'boost.color.green',\n default: 'green',\n description: 'the color green'\n }),\n value: BoostColor.GREEN\n },\n {\n text: formatMessage({\n id: 'boost.color.yellow',\n default: 'yellow',\n description: 'the color yellow'\n }),\n value: BoostColor.YELLOW\n },\n {\n text: formatMessage({\n id: 'boost.color.white',\n default: 'white',\n desription: 'the color white'\n }),\n value: BoostColor.WHITE\n },\n {\n text: formatMessage({\n id: 'boost.color.black',\n default: 'black',\n description: 'the color black'\n }),\n value: BoostColor.BLACK\n },\n {\n text: formatMessage({\n id: 'boost.color.any',\n default: 'any color',\n description: 'any color'\n }),\n value: BoostColor.ANY\n }\n ]\n }\n }\n };\n }\n\n /**\n * Turn specified motor(s) on for a specified duration.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to activate.\n * @property {int} DURATION - the amount of time to run the motors.\n * @return {Promise} - a promise which will resolve at the end of the duration.\n */\n motorOnFor (args) {\n // TODO: cast args.MOTOR_ID?\n let durationMS = Cast.toNumber(args.DURATION) * 1000;\n durationMS = MathUtil.clamp(durationMS, 0, 15000);\n return new Promise(resolve => {\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) motor.turnOnFor(durationMS);\n });\n\n // Run for some time even when no motor is connected\n setTimeout(resolve, durationMS);\n });\n }\n\n /**\n * Turn specified motor(s) on for a specified rotation in full rotations.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to activate.\n * @property {int} ROTATION - the amount of full rotations to turn the motors.\n * @return {Promise} - a promise which will resolve at the end of the duration.\n */\n motorOnForRotation (args) {\n // TODO: cast args.MOTOR_ID?\n let degrees = Cast.toNumber(args.ROTATION) * 360;\n // TODO: Clamps to 100 rotations. Consider changing.\n const sign = Math.sign(degrees);\n degrees = Math.abs(MathUtil.clamp(degrees, -360000, 360000));\n\n const motors = [];\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n motors.push(motorIndex);\n });\n\n /**\n * Checks that the motors given in args.MOTOR_ID exist,\n * and maps a promise for each of the motor-commands to an array.\n */\n const promises = motors.map(portID => {\n const motor = this._peripheral.motor(portID);\n if (motor) {\n // to avoid a hanging block if power is 0, return an immediately resolving promise.\n if (motor.power === 0) return Promise.resolve();\n return new Promise(resolve => {\n motor.turnOnForDegrees(degrees, sign);\n motor.pendingRotationPromise = resolve;\n });\n }\n return null;\n });\n /**\n * Make sure all promises are resolved, i.e. all motor-commands have completed.\n * To prevent the block from returning a value, an empty function is added to the .then\n */\n return Promise.all(promises).then(() => {});\n }\n\n /**\n * Turn specified motor(s) on indefinitely.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to activate.\n * @return {Promise} - a Promise that resolves after some delay.\n */\n motorOn (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) motor.turnOnForever();\n });\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BoostBLE.sendInterval);\n });\n }\n\n /**\n * Turn specified motor(s) off.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to deactivate.\n * @return {Promise} - a Promise that resolves after some delay.\n */\n motorOff (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) motor.turnOff();\n });\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BoostBLE.sendInterval);\n });\n }\n\n /**\n * Set the power level of the specified motor(s).\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to be affected.\n * @property {int} POWER - the new power level for the motor(s).\n * @return {Promise} - returns a promise to make sure the block yields.\n */\n setMotorPower (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100);\n switch (motor.status) {\n case BoostMotorState.ON_FOREVER:\n motor.turnOnForever();\n break;\n case BoostMotorState.ON_FOR_TIME:\n motor.turnOnFor(motor.pendingDurationTimeoutStartTime +\n motor.pendingDurationTimeoutDelay - Date.now());\n break;\n }\n }\n });\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BoostBLE.sendInterval);\n });\n }\n\n /**\n * Set the direction of rotation for specified motor(s).\n * If the direction is 'reverse' the motor(s) will be reversed individually.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to be affected.\n * @property {MotorDirection} MOTOR_DIRECTION - the new direction for the motor(s).\n * @return {Promise} - returns a promise to make sure the block yields.\n */\n setMotorDirection (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n switch (args.MOTOR_DIRECTION) {\n case BoostMotorDirection.FORWARD:\n motor.direction = 1;\n break;\n case BoostMotorDirection.BACKWARD:\n motor.direction = -1;\n break;\n case BoostMotorDirection.REVERSE:\n motor.direction = -motor.direction;\n break;\n default:\n log.warn(`Unknown motor direction in setMotorDirection: ${args.DIRECTION}`);\n break;\n }\n // keep the motor on if it's running, and update the pending timeout if needed\n if (motor) {\n switch (motor.status) {\n case BoostMotorState.ON_FOREVER:\n motor.turnOnForever();\n break;\n case BoostMotorState.ON_FOR_TIME:\n motor.turnOnFor(motor.pendingDurationTimeoutStartTime +\n motor.pendingDurationTimeoutDelay - Date.now());\n break;\n }\n }\n }\n });\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BoostBLE.sendInterval);\n });\n }\n\n /**\n * @param {object} args - the block's arguments.\n * @return {number} - returns the motor's position.\n */\n getMotorPosition (args) {\n let portID = null;\n switch (args.MOTOR_REPORTER_ID) {\n\n case BoostMotorLabel.A:\n portID = BoostPort.A;\n break;\n case BoostMotorLabel.B:\n portID = BoostPort.B;\n break;\n case BoostMotorLabel.C:\n portID = BoostPort.C;\n break;\n case BoostMotorLabel.D:\n portID = BoostPort.D;\n break;\n default:\n log.warn('Asked for a motor position that doesnt exist!');\n return false;\n }\n if (portID !== null && this._peripheral.motor(portID)) {\n let val = this._peripheral.motor(portID).position;\n // Boost motor A position direction is reversed by design\n // so we have to reverse the position here\n if (portID === BoostPort.A) {\n val *= -1;\n }\n return MathUtil.wrapClamp(val, 0, 360);\n }\n return 0;\n }\n\n /**\n * Call a callback for each motor indexed by the provided motor ID.\n * @param {MotorID} motorID - the ID specifier.\n * @param {Function} callback - the function to call with the numeric motor index for each motor.\n * @private\n */\n _forEachMotor (motorID, callback) {\n let motors;\n switch (motorID) {\n case BoostMotorLabel.A:\n motors = [BoostPort.A];\n break;\n case BoostMotorLabel.B:\n motors = [BoostPort.B];\n break;\n case BoostMotorLabel.C:\n motors = [BoostPort.C];\n break;\n case BoostMotorLabel.D:\n motors = [BoostPort.D];\n break;\n case BoostMotorLabel.AB:\n motors = [BoostPort.A, BoostPort.B];\n break;\n case BoostMotorLabel.ALL:\n motors = [BoostPort.A, BoostPort.B, BoostPort.C, BoostPort.D];\n break;\n default:\n log.warn(`Invalid motor ID: ${motorID}`);\n motors = [];\n break;\n }\n for (const index of motors) {\n callback(index);\n }\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} TILT_DIRECTION_ANY - the tilt direction to test (up, down, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n */\n whenTilted (args) {\n return this._isTilted(args.TILT_DIRECTION_ANY);\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} TILT_DIRECTION_ANY - the tilt direction to test (up, down, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n */\n isTilted (args) {\n return this._isTilted(args.TILT_DIRECTION_ANY);\n }\n\n /**\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} TILT_DIRECTION - the direction (up, down, left, right) to check.\n * @return {number} - the tilt sensor's angle in the specified direction.\n * Note that getTiltAngle(up) = -getTiltAngle(down) and getTiltAngle(left) = -getTiltAngle(right).\n */\n getTiltAngle (args) {\n return this._getTiltAngle(args.TILT_DIRECTION);\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {TiltDirection} direction - the tilt direction to test (up, down, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n * @private\n */\n _isTilted (direction) {\n switch (direction) {\n case BoostTiltDirection.ANY:\n return (Math.abs(this._peripheral.tiltX) >= Scratch3BoostBlocks.TILT_THRESHOLD) ||\n (Math.abs(this._peripheral.tiltY) >= Scratch3BoostBlocks.TILT_THRESHOLD);\n default:\n return this._getTiltAngle(direction) >= Scratch3BoostBlocks.TILT_THRESHOLD;\n }\n }\n\n /**\n * @param {TiltDirection} direction - the direction (up, down, left, right) to check.\n * @return {number} - the tilt sensor's angle in the specified direction.\n * Note that getTiltAngle(up) = -getTiltAngle(down) and getTiltAngle(left) = -getTiltAngle(right).\n * @private\n */\n _getTiltAngle (direction) {\n switch (direction) {\n case BoostTiltDirection.UP:\n return this._peripheral.tiltY > 90 ? 256 - this._peripheral.tiltY : -this._peripheral.tiltY;\n case BoostTiltDirection.DOWN:\n return this._peripheral.tiltY > 90 ? this._peripheral.tiltY - 256 : this._peripheral.tiltY;\n case BoostTiltDirection.LEFT:\n return this._peripheral.tiltX > 90 ? this._peripheral.tiltX - 256 : this._peripheral.tiltX;\n case BoostTiltDirection.RIGHT:\n return this._peripheral.tiltX > 90 ? 256 - this._peripheral.tiltX : -this._peripheral.tiltX;\n default:\n log.warn(`Unknown tilt direction in _getTiltAngle: ${direction}`);\n }\n }\n\n /**\n * Edge-triggering hat function, for when the vision sensor is detecting\n * a certain color.\n * @param {object} args - the block's arguments.\n * @return {boolean} - true when the color sensor senses the specified color.\n */\n whenColor (args) {\n if (args.COLOR === BoostColor.ANY) {\n // For \"any\" color, return true if the color is not \"none\", and\n // the color is different from the previous color detected. This\n // allows the hat to trigger when the color changes from one color\n // to another.\n return this._peripheral.color !== BoostColor.NONE &&\n this._peripheral.color !== this._peripheral.previousColor;\n }\n\n return args.COLOR === this._peripheral.color;\n }\n\n /**\n * A boolean reporter function, for whether the vision sensor is detecting\n * a certain color.\n * @param {object} args - the block's arguments.\n * @return {boolean} - true when the color sensor senses the specified color.\n */\n seeingColor (args) {\n if (args.COLOR === BoostColor.ANY) {\n return this._peripheral.color !== BoostColor.NONE;\n }\n\n return args.COLOR === this._peripheral.color;\n }\n\n /**\n * Set the LED's hue.\n * @param {object} args - the block's arguments.\n * @property {number} HUE - the hue to set, in the range [0,100].\n * @return {Promise} - a Promise that resolves after some delay.\n */\n setLightHue (args) {\n // Convert from [0,100] to [0,360]\n let inputHue = Cast.toNumber(args.HUE);\n inputHue = MathUtil.wrapClamp(inputHue, 0, 100);\n const hue = inputHue * 360 / 100;\n\n const rgbObject = color.hsvToRgb({h: hue, s: 1, v: 1});\n\n const rgbDecimal = color.rgbToDecimal(rgbObject);\n\n this._peripheral._led = inputHue;\n this._peripheral.setLED(rgbDecimal);\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BoostBLE.sendInterval);\n });\n }\n}\n\nmodule.exports = Scratch3BoostBlocks;\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Cast = require('../../util/cast');\nconst formatMessage = require('format-message');\nconst uid = require('../../util/uid');\nconst BT = require('../../io/bt');\nconst Base64Util = require('../../util/base64-util');\nconst MathUtil = require('../../util/math-util');\nconst RateLimiter = require('../../util/rateLimiter.js');\nconst log = require('../../util/log');\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSIwIDAgNDAgNDAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUwLjIgKDU1MDQ3KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5ldjMtYmxvY2staWNvbjwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPjwvZGVmcz4KICAgIDxnIGlkPSJldjMtYmxvY2staWNvbiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9ImV2MyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNS41MDAwMDAsIDMuNTAwMDAwKSIgZmlsbC1ydWxlPSJub256ZXJvIj4KICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS1wYXRoIiBzdHJva2U9IiM3Qzg3QTUiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgeD0iMC41IiB5PSIzLjU5IiB3aWR0aD0iMjgiIGhlaWdodD0iMjUuODEiIHJ4PSIxIj48L3JlY3Q+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtcGF0aCIgc3Ryb2tlPSIjN0M4N0E1IiBmaWxsPSIjRTZFN0U4IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHg9IjIuNSIgeT0iMC41IiB3aWR0aD0iMjQiIGhlaWdodD0iMzIiIHJ4PSIxIj48L3JlY3Q+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtcGF0aCIgc3Ryb2tlPSIjN0M4N0E1IiBmaWxsPSIjRkZGRkZGIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHg9IjIuNSIgeT0iMTQuNSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjEzIj48L3JlY3Q+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xNC41LDEwLjUgTDE0LjUsMTQuNSIgaWQ9IlNoYXBlIiBzdHJva2U9IiM3Qzg3QTUiIGZpbGw9IiNFNkU3RTgiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PC9wYXRoPgogICAgICAgICAgICA8cmVjdCBpZD0iUmVjdGFuZ2xlLXBhdGgiIGZpbGw9IiM0MTQ3NTciIHg9IjQuNSIgeT0iMi41IiB3aWR0aD0iMjAiIGhlaWdodD0iMTAiIHJ4PSIxIj48L3JlY3Q+CiAgICAgICAgICAgIDxyZWN0IGlkPSJSZWN0YW5nbGUtcGF0aCIgZmlsbD0iIzdDODdBNSIgb3BhY2l0eT0iMC41IiB4PSIxMy41IiB5PSIyMC4xMyIgd2lkdGg9IjIiIGhlaWdodD0iMiIgcng9IjAuNSI+PC9yZWN0PgogICAgICAgICAgICA8cGF0aCBkPSJNOS4wNiwyMC4xMyBMMTAuNTYsMjAuMTMgQzEwLjgzNjE0MjQsMjAuMTMgMTEuMDYsMjAuMzUzODU3NiAxMS4wNiwyMC42MyBMMTEuMDYsMjEuNjMgQzExLjA2LDIxLjkwNjE0MjQgMTAuODM2MTQyNCwyMi4xMyAxMC41NiwyMi4xMyBMOS4wNiwyMi4xMyBDOC41MDc3MTUyNSwyMi4xMyA4LjA2LDIxLjY4MjI4NDcgOC4wNiwyMS4xMyBDOC4wNiwyMC41Nzc3MTUzIDguNTA3NzE1MjUsMjAuMTMgOS4wNiwyMC4xMyBaIiBpZD0iU2hhcGUiIGZpbGw9IiM3Qzg3QTUiIG9wYWNpdHk9IjAuNSI+PC9wYXRoPgogICAgICAgICAgICA8cGF0aCBkPSJNMTguOTEsMjAuMTMgTDIwLjQyLDIwLjEzIEMyMC42OTYxNDI0LDIwLjEzIDIwLjkyLDIwLjM1Mzg1NzYgMjAuOTIsMjAuNjMgTDIwLjkyLDIxLjYzIEMyMC45MiwyMS45MDYxNDI0IDIwLjY5NjE0MjQsMjIuMTMgMjAuNDIsMjIuMTMgTDE4LjkyLDIyLjEzIEMxOC4zNjc3MTUzLDIyLjEzIDE3LjkyLDIxLjY4MjI4NDcgMTcuOTIsMjEuMTMgQzE3LjkxOTk3MjYsMjAuNTgxNTk3IDE4LjM2MTYyNDUsMjAuMTM1NDg0IDE4LjkxLDIwLjEzIFoiIGlkPSJTaGFwZSIgZmlsbD0iIzdDODdBNSIgb3BhY2l0eT0iMC41IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxOS40MjAwMDAsIDIxLjEzMDAwMCkgcm90YXRlKC0xODAuMDAwMDAwKSB0cmFuc2xhdGUoLTE5LjQyMDAwMCwgLTIxLjEzMDAwMCkgIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik04LjIzLDE3LjUgTDUsMTcuNSBDNC43MjM4NTc2MywxNy41IDQuNSwxNy4yNzYxNDI0IDQuNSwxNyBMNC41LDE0LjUgTDEwLjUsMTQuNSBMOC42NSwxNy4yOCBDOC41NTQ2Njk2MSwxNy40MTc5MDgyIDguMzk3NjUwMDYsMTcuNTAwMTU2NiA4LjIzLDE3LjUgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjN0M4N0E1IiBvcGFjaXR5PSIwLjUiPjwvcGF0aD4KICAgICAgICAgICAgPHBhdGggZD0iTTE4LjE1LDE4Ljg1IEwxNy42NSwxOS4zNSBDMTcuNTUyMzQxNiwxOS40NDQwNzU2IDE3LjQ5ODAzMzksMTkuNTc0NDE0MiAxNy41LDE5LjcxIEwxNy41LDIwIEMxNy41LDIwLjI3NjE0MjQgMTcuMjc2MTQyNCwyMC41IDE3LDIwLjUgTDE2LjUsMjAuNSBDMTYuMjIzODU3NiwyMC41IDE2LDIwLjI3NjE0MjQgMTYsMjAgQzE2LDE5LjcyMzg1NzYgMTUuNzc2MTQyNCwxOS41IDE1LjUsMTkuNSBMMTMuNSwxOS41IEMxMy4yMjM4NTc2LDE5LjUgMTMsMTkuNzIzODU3NiAxMywyMCBDMTMsMjAuMjc2MTQyNCAxMi43NzYxNDI0LDIwLjUgMTIuNSwyMC41IEwxMiwyMC41IEMxMS43MjM4NTc2LDIwLjUgMTEuNSwyMC4yNzYxNDI0IDExLjUsMjAgTDExLjUsMTkuNzEgQzExLjUwMTk2NjEsMTkuNTc0NDE0MiAxMS40NDc2NTg0LDE5LjQ0NDA3NTYgMTEuMzUsMTkuMzUgTDEwLjg1LDE4Ljg1IEMxMC42NTgyMTY3LDE4LjY1MjE4NjMgMTAuNjU4MjE2NywxOC4zMzc4MTM3IDEwLjg1LDE4LjE0IEwxMi4zNiwxNi42NSBDMTIuNDUwMjgwMywxNi41NTI4NjE3IDEyLjU3NzM5NjEsMTYuNDk4MzgzNSAxMi43MSwxNi41IEwxNi4yOSwxNi41IEMxNi40MjI2MDM5LDE2LjQ5ODM4MzUgMTYuNTQ5NzE5NywxNi41NTI4NjE3IDE2LjY0LDE2LjY1IEwxOC4xNSwxOC4xNCBDMTguMzQxNzgzMywxOC4zMzc4MTM3IDE4LjM0MTc4MzMsMTguNjUyMTg2MyAxOC4xNSwxOC44NSBaIiBpZD0iU2hhcGUiIGZpbGw9IiM3Qzg3QTUiIG9wYWNpdHk9IjAuNSI+PC9wYXRoPgogICAgICAgICAgICA8cGF0aCBkPSJNMTAuODUsMjMuNDUgTDExLjM1LDIyLjk1IEMxMS40NDc2NTg0LDIyLjg1NTkyNDQgMTEuNTAxOTY2MSwyMi43MjU1ODU4IDExLjUsMjIuNTkgTDExLjUsMjIuMyBDMTEuNSwyMi4wMjM4NTc2IDExLjcyMzg1NzYsMjEuOCAxMiwyMS44IEwxMi41LDIxLjggQzEyLjc3NjE0MjQsMjEuOCAxMywyMi4wMjM4NTc2IDEzLDIyLjMgQzEzLDIyLjU3NjE0MjQgMTMuMjIzODU3NiwyMi44IDEzLjUsMjIuOCBMMTUuNSwyMi44IEMxNS43NzYxNDI0LDIyLjggMTYsMjIuNTc2MTQyNCAxNiwyMi4zIEMxNiwyMi4wMjM4NTc2IDE2LjIyMzg1NzYsMjEuOCAxNi41LDIxLjggTDE3LDIxLjggQzE3LjI3NjE0MjQsMjEuOCAxNy41LDIyLjAyMzg1NzYgMTcuNSwyMi4zIEwxNy41LDIyLjU5IEMxNy40OTgwMzM5LDIyLjcyNTU4NTggMTcuNTUyMzQxNiwyMi44NTU5MjQ0IDE3LjY1LDIyLjk1IEwxOC4xNSwyMy40NSBDMTguMzQwNTcxNCwyMy42NDQ0MjE4IDE4LjM0MDU3MTQsMjMuOTU1NTc4MiAxOC4xNSwyNC4xNSBMMTYuNjQsMjUuNjUgQzE2LjU0OTcxOTcsMjUuNzQ3MTM4MyAxNi40MjI2MDM5LDI1LjgwMTYxNjUgMTYuMjksMjUuOCBMMTIuNzEsMjUuOCBDMTIuNTc3Mzk2MSwyNS44MDE2MTY1IDEyLjQ1MDI4MDMsMjUuNzQ3MTM4MyAxMi4zNiwyNS42NSBMMTAuODUsMjQuMTUgQzEwLjY1OTQyODYsMjMuOTU1NTc4MiAxMC42NTk0Mjg2LDIzLjY0NDQyMTggMTAuODUsMjMuNDUgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjN0M4N0E1IiBvcGFjaXR5PSIwLjUiPjwvcGF0aD4KICAgICAgICAgICAgPHBhdGggZD0iTTIxLjUsMjcuNSBMMjYuNSwyNy41IEwyNi41LDMxLjUgQzI2LjUsMzIuMDUyMjg0NyAyNi4wNTIyODQ3LDMyLjUgMjUuNSwzMi41IEwyMS41LDMyLjUgTDIxLjUsMjcuNSBaIiBpZD0iU2hhcGUiIHN0cm9rZT0iI0NDNEMyMyIgZmlsbD0iI0YxNUEyOSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48L3BhdGg+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4=';\n\n/**\n * String with Ev3 expected pairing pin.\n * @readonly\n */\nconst Ev3PairingPin = '1234';\n\n/**\n * A maximum number of BT message sends per second, to be enforced by the rate limiter.\n * @type {number}\n */\nconst BTSendRateMax = 40;\n\n/**\n * Enum for Ev3 parameter encodings of various argument and return values.\n * Found in the 'EV3 Firmware Developer Kit', section4, page 9, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n *\n * The format for these values is:\n * 0xxxxxxx for Short Format\n * 1ttt-bbb for Long Format\n *\n * @readonly\n * @enum {number}\n */\nconst Ev3Encoding = {\n ONE_BYTE: 0x81, // = 0b1000-001, \"1 byte to follow\"\n TWO_BYTES: 0x82, // = 0b1000-010, \"2 bytes to follow\"\n FOUR_BYTES: 0x83, // = 0b1000-011, \"4 bytes to follow\"\n GLOBAL_VARIABLE_ONE_BYTE: 0xE1, // = 0b1110-001, \"1 byte to follow\"\n GLOBAL_CONSTANT_INDEX_0: 0x20, // = 0b00100000\n GLOBAL_VARIABLE_INDEX_0: 0x60 // = 0b01100000\n};\n\n/**\n * Enum for Ev3 direct command types.\n * Found in the 'EV3 Communication Developer Kit', section 4, page 24, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n * @readonly\n * @enum {number}\n */\nconst Ev3Command = {\n DIRECT_COMMAND_REPLY: 0x00,\n DIRECT_COMMAND_NO_REPLY: 0x80,\n DIRECT_REPLY: 0x02\n};\n\n/**\n * Enum for Ev3 commands opcodes.\n * Found in the 'EV3 Firmware Developer Kit', section 4, page 10, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n * @readonly\n * @enum {number}\n */\nconst Ev3Opcode = {\n OPOUTPUT_STEP_SPEED: 0xAE,\n OPOUTPUT_TIME_SPEED: 0xAF,\n OPOUTPUT_STOP: 0xA3,\n OPOUTPUT_RESET: 0xA2,\n OPOUTPUT_STEP_SYNC: 0xB0,\n OPOUTPUT_TIME_SYNC: 0xB1,\n OPOUTPUT_GET_COUNT: 0xB3,\n OPSOUND: 0x94,\n OPSOUND_CMD_TONE: 1,\n OPSOUND_CMD_STOP: 0,\n OPINPUT_DEVICE_LIST: 0x98,\n OPINPUT_READSI: 0x9D\n};\n\n/**\n * Enum for Ev3 values used as arguments to various opcodes.\n * Found in the 'EV3 Firmware Developer Kit', section4, page 10-onwards, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n * @readonly\n * @enum {number}\n */\nconst Ev3Args = {\n LAYER: 0, // always 0, chained EV3s not supported\n COAST: 0,\n BRAKE: 1,\n RAMP: 50, // time in milliseconds\n DO_NOT_CHANGE_TYPE: 0,\n MAX_DEVICES: 32 // 'Normally 32' from pg. 46\n};\n\n/**\n * Enum for Ev3 device type numbers.\n * Found in the 'EV3 Firmware Developer Kit', section 5, page 100, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n * @readonly\n * @enum {string}\n */\nconst Ev3Device = {\n 29: 'color',\n 30: 'ultrasonic',\n 32: 'gyro',\n 16: 'touch',\n 8: 'mediumMotor',\n 7: 'largeMotor',\n 126: 'none',\n 125: 'none'\n};\n\n/**\n * Enum for Ev3 device modes.\n * Found in the 'EV3 Firmware Developer Kit', section 5, page 100, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n * @readonly\n * @enum {number}\n */\nconst Ev3Mode = {\n touch: 0, // touch\n color: 1, // ambient\n ultrasonic: 1, // inch\n none: 0\n};\n\n/**\n * Enum for Ev3 device labels used in the Scratch blocks/UI.\n * @readonly\n * @enum {string}\n */\nconst Ev3Label = {\n touch: 'button',\n color: 'brightness',\n ultrasonic: 'distance'\n};\n\n/**\n * Manage power, direction, and timers for one EV3 motor.\n */\nclass EV3Motor {\n\n /**\n * Construct a EV3 Motor instance, which could be of type 'largeMotor' or\n * 'mediumMotor'.\n *\n * @param {EV3} parent - the EV3 peripheral which owns this motor.\n * @param {int} index - the zero-based index of this motor on its parent peripheral.\n * @param {string} type - the type of motor (i.e. 'largeMotor' or 'mediumMotor').\n */\n constructor (parent, index, type) {\n /**\n * The EV3 peripheral which owns this motor.\n * @type {EV3}\n * @private\n */\n this._parent = parent;\n\n /**\n * The zero-based index of this motor on its parent peripheral.\n * @type {int}\n * @private\n */\n this._index = index;\n\n /**\n * The type of EV3 motor this could be: 'largeMotor' or 'mediumMotor'.\n * @type {string}\n * @private\n */\n this._type = type;\n\n /**\n * This motor's current direction: 1 for \"clockwise\" or -1 for \"counterclockwise\"\n * @type {number}\n * @private\n */\n this._direction = 1;\n\n /**\n * This motor's current power level, in the range [0,100].\n * @type {number}\n * @private\n */\n this._power = 50;\n\n /**\n * This motor's current position, in the range [0,360].\n * @type {number}\n * @private\n */\n this._position = 0;\n\n /**\n * An ID for the current coast command, to help override multiple coast\n * commands sent in succession.\n * @type {number}\n * @private\n */\n this._commandID = null;\n\n /**\n * A delay, in milliseconds, to add to coasting, to make sure that a brake\n * first takes effect if one was sent.\n * @type {number}\n * @private\n */\n this._coastDelay = 1000;\n }\n\n /**\n * @return {string} - this motor's type: 'largeMotor' or 'mediumMotor'\n */\n get type () {\n return this._type;\n }\n\n /**\n * @param {string} value - this motor's new type: 'largeMotor' or 'mediumMotor'\n */\n set type (value) {\n this._type = value;\n }\n\n /**\n * @return {int} - this motor's current direction: 1 for \"clockwise\" or -1 for \"counterclockwise\"\n */\n get direction () {\n return this._direction;\n }\n\n /**\n * @param {int} value - this motor's new direction: 1 for \"clockwise\" or -1 for \"counterclockwise\"\n */\n set direction (value) {\n if (value < 0) {\n this._direction = -1;\n } else {\n this._direction = 1;\n }\n }\n\n /**\n * @return {int} - this motor's current power level, in the range [0,100].\n */\n get power () {\n return this._power;\n }\n\n /**\n * @param {int} value - this motor's new power level, in the range [0,100].\n */\n set power (value) {\n this._power = value;\n }\n\n /**\n * @return {int} - this motor's current position, in the range [-inf,inf].\n */\n get position () {\n return this._position;\n }\n\n /**\n * @param {int} array - this motor's new position, in the range [0,360].\n */\n set position (array) {\n // tachoValue from Paula\n let value = array[0] + (array[1] * 256) + (array[2] * 256 * 256) + (array[3] * 256 * 256 * 256);\n if (value > 0x7fffffff) {\n value = value - 0x100000000;\n }\n this._position = value;\n }\n\n /**\n * Turn this motor on for a specific duration.\n * Found in the 'EV3 Firmware Developer Kit', page 56, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n *\n * Opcode arguments:\n * (Data8) LAYER – Specify chain layer number [0 - 3]\n * (Data8) NOS – Output bit field [0x00 – 0x0F]\n * (Data8) SPEED – Power level, [-100 – 100]\n * (Data32) STEP1 – Time in milliseconds for ramp up\n * (Data32) STEP2 – Time in milliseconds for continues run\n * (Data32) STEP3 – Time in milliseconds for ramp down\n * (Data8) BRAKE - Specify break level [0: Float, 1: Break]\n *\n * @param {number} milliseconds - run the motor for this long.\n */\n turnOnFor (milliseconds) {\n if (this._power === 0) return;\n\n const port = this._portMask(this._index);\n let n = milliseconds;\n let speed = this._power * this._direction;\n const ramp = Ev3Args.RAMP;\n\n let byteCommand = [];\n byteCommand[0] = Ev3Opcode.OPOUTPUT_TIME_SPEED;\n\n // If speed is less than zero, make it positive and multiply the input\n // value by -1\n if (speed < 0) {\n speed = -1 * speed;\n n = -1 * n;\n }\n // If the input value is less than 0\n const dir = (n < 0) ? 0x100 - speed : speed; // step negative or positive\n n = Math.abs(n);\n // Setup motor run duration and ramping behavior\n let rampup = ramp;\n let rampdown = ramp;\n let run = n - (ramp * 2);\n if (run < 0) {\n rampup = Math.floor(n / 2);\n run = 0;\n rampdown = n - rampup;\n }\n // Generate motor command values\n const runcmd = this._runValues(run);\n byteCommand = byteCommand.concat([\n Ev3Args.LAYER,\n port,\n Ev3Encoding.ONE_BYTE,\n dir & 0xff,\n Ev3Encoding.ONE_BYTE,\n rampup\n ]).concat(runcmd.concat([\n Ev3Encoding.ONE_BYTE,\n rampdown,\n Ev3Args.BRAKE\n ]));\n\n const cmd = this._parent.generateCommand(\n Ev3Command.DIRECT_COMMAND_NO_REPLY,\n byteCommand\n );\n\n this._parent.send(cmd);\n\n this.coastAfter(milliseconds);\n }\n\n /**\n * Set the motor to coast after a specified amount of time.\n * @param {number} time - the time in milliseconds.\n */\n coastAfter (time) {\n if (this._power === 0) return;\n\n // Set the motor command id to check before starting coast\n const commandId = uid();\n this._commandID = commandId;\n\n // Send coast message\n setTimeout(() => {\n // Do not send coast if another motor command changed the command id.\n if (this._commandID === commandId) {\n this.coast();\n this._commandID = null;\n }\n }, time + this._coastDelay); // add a delay so the brake takes effect\n }\n\n /**\n * Set the motor to coast.\n */\n coast () {\n if (this._power === 0) return;\n\n const cmd = this._parent.generateCommand(\n Ev3Command.DIRECT_COMMAND_NO_REPLY,\n [\n Ev3Opcode.OPOUTPUT_STOP,\n Ev3Args.LAYER,\n this._portMask(this._index), // port output bit field\n Ev3Args.COAST\n ]\n );\n\n this._parent.send(cmd, false); // don't use rate limiter to ensure motor stops\n }\n\n /**\n * Generate motor run values for a given input.\n * @param {number} run - run input.\n * @return {array} - run values as a byte array.\n */\n _runValues (run) {\n // If run duration is less than max 16-bit integer\n if (run < 0x7fff) {\n return [\n Ev3Encoding.TWO_BYTES,\n run & 0xff,\n (run >> 8) & 0xff\n ];\n }\n\n // Run forever\n return [\n Ev3Encoding.FOUR_BYTES,\n run & 0xff,\n (run >> 8) & 0xff,\n (run >> 16) & 0xff,\n (run >> 24) & 0xff\n ];\n }\n\n /**\n * Return a port value for the EV3 that is in the format for 'output bit field'\n * as 1/2/4/8, generally needed for motor ports, instead of the typical 0/1/2/3.\n * The documentation in the 'EV3 Firmware Developer Kit' for motor port arguments\n * is sometimes mistaken, but we believe motor ports are mostly addressed this way.\n * @param {number} port - the port number to convert to an 'output bit field'.\n * @return {number} - the converted port number.\n */\n _portMask (port) {\n return Math.pow(2, port);\n }\n}\n\nclass EV3 {\n\n constructor (runtime, extensionId) {\n\n /**\n * The Scratch 3.0 runtime used to trigger the green flag button.\n * @type {Runtime}\n * @private\n */\n this._runtime = runtime;\n this._runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this));\n\n /**\n * The id of the extension this peripheral belongs to.\n */\n this._extensionId = extensionId;\n\n /**\n * A list of the names of the sensors connected in ports 1,2,3,4.\n * @type {string[]}\n * @private\n */\n this._sensorPorts = [];\n\n /**\n * A list of the names of the motors connected in ports A,B,C,D.\n * @type {string[]}\n * @private\n */\n this._motorPorts = [];\n\n /**\n * The state of all sensor values.\n * @type {string[]}\n * @private\n */\n this._sensors = {\n distance: 0,\n brightness: 0,\n buttons: [0, 0, 0, 0]\n };\n\n /**\n * The motors which this EV3 could possibly have connected.\n * @type {string[]}\n * @private\n */\n this._motors = [null, null, null, null];\n\n /**\n * The polling interval, in milliseconds.\n * @type {number}\n * @private\n */\n this._pollingInterval = 150;\n\n /**\n * The polling interval ID.\n * @type {number}\n * @private\n */\n this._pollingIntervalID = null;\n\n /**\n * The counter keeping track of polling cycles.\n * @type {string[]}\n * @private\n */\n this._pollingCounter = 0;\n\n /**\n * The Bluetooth socket connection for reading/writing peripheral data.\n * @type {BT}\n * @private\n */\n this._bt = null;\n this._runtime.registerPeripheralExtension(extensionId, this);\n\n /**\n * A rate limiter utility, to help limit the rate at which we send BT messages\n * over the socket to Scratch Link to a maximum number of sends per second.\n * @type {RateLimiter}\n * @private\n */\n this._rateLimiter = new RateLimiter(BTSendRateMax);\n\n this.reset = this.reset.bind(this);\n this._onConnect = this._onConnect.bind(this);\n this._onMessage = this._onMessage.bind(this);\n this._pollValues = this._pollValues.bind(this);\n }\n\n get distance () {\n let value = this._sensors.distance > 100 ? 100 : this._sensors.distance;\n value = value < 0 ? 0 : value;\n value = Math.round(100 * value) / 100;\n\n return value;\n }\n\n get brightness () {\n return this._sensors.brightness;\n }\n\n /**\n * Access a particular motor on this peripheral.\n * @param {int} index - the zero-based index of the desired motor.\n * @return {EV3Motor} - the EV3Motor instance, if any, at that index.\n */\n motor (index) {\n return this._motors[index];\n }\n\n isButtonPressed (port) {\n return this._sensors.buttons[port] === 1;\n }\n\n beep (freq, time) {\n const cmd = this.generateCommand(\n Ev3Command.DIRECT_COMMAND_NO_REPLY,\n [\n Ev3Opcode.OPSOUND,\n Ev3Opcode.OPSOUND_CMD_TONE,\n Ev3Encoding.ONE_BYTE,\n 2,\n Ev3Encoding.TWO_BYTES,\n freq,\n freq >> 8,\n Ev3Encoding.TWO_BYTES,\n time,\n time >> 8\n ]\n );\n\n this.send(cmd);\n }\n\n stopAll () {\n this.stopAllMotors();\n this.stopSound();\n }\n\n stopSound () {\n const cmd = this.generateCommand(\n Ev3Command.DIRECT_COMMAND_NO_REPLY,\n [\n Ev3Opcode.OPSOUND,\n Ev3Opcode.OPSOUND_CMD_STOP\n ]\n );\n\n this.send(cmd, false); // don't use rate limiter to ensure sound stops\n }\n\n stopAllMotors () {\n this._motors.forEach(motor => {\n if (motor) {\n motor.coast();\n }\n });\n }\n\n /**\n * Called by the runtime when user wants to scan for an EV3 peripheral.\n */\n scan () {\n if (this._bt) {\n this._bt.disconnect();\n }\n this._bt = new BT(this._runtime, this._extensionId, {\n majorDeviceClass: 8,\n minorDeviceClass: 1\n }, this._onConnect, this.reset, this._onMessage);\n }\n\n /**\n * Called by the runtime when user wants to connect to a certain EV3 peripheral.\n * @param {number} id - the id of the peripheral to connect to.\n */\n connect (id) {\n if (this._bt) {\n this._bt.connectPeripheral(id, Ev3PairingPin);\n }\n }\n\n /**\n * Called by the runtime when user wants to disconnect from the EV3 peripheral.\n */\n disconnect () {\n if (this._bt) {\n this._bt.disconnect();\n }\n\n this.reset();\n }\n\n /**\n * Reset all the state and timeout/interval ids.\n */\n reset () {\n this._sensorPorts = [];\n this._motorPorts = [];\n this._sensors = {\n distance: 0,\n brightness: 0,\n buttons: [0, 0, 0, 0]\n };\n this._motors = [null, null, null, null];\n\n if (this._pollingIntervalID) {\n window.clearInterval(this._pollingIntervalID);\n this._pollingIntervalID = null;\n }\n }\n\n /**\n * Called by the runtime to detect whether the EV3 peripheral is connected.\n * @return {boolean} - the connected state.\n */\n isConnected () {\n let connected = false;\n if (this._bt) {\n connected = this._bt.isConnected();\n }\n return connected;\n }\n\n /**\n * Send a message to the peripheral BT socket.\n * @param {Uint8Array} message - the message to send.\n * @param {boolean} [useLimiter=true] - if true, use the rate limiter\n * @return {Promise} - a promise result of the send operation.\n */\n send (message, useLimiter = true) {\n if (!this.isConnected()) return Promise.resolve();\n\n if (useLimiter) {\n if (!this._rateLimiter.okayToSend()) return Promise.resolve();\n }\n\n return this._bt.sendMessage({\n message: Base64Util.uint8ArrayToBase64(message),\n encoding: 'base64'\n });\n }\n\n /**\n * Genrates direct commands that are sent to the EV3 as a single or compounded byte arrays.\n * See 'EV3 Communication Developer Kit', section 4, page 24 at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits.\n *\n * Direct commands are one of two types:\n * DIRECT_COMMAND_NO_REPLY = a direct command where no reply is expected\n * DIRECT_COMMAND_REPLY = a direct command where a reply is expected, and the\n * number and length of returned values needs to be specified.\n *\n * The direct command byte array sent takes the following format:\n * Byte 0 - 1: Command size, Little Endian. Command size not including these 2 bytes\n * Byte 2 - 3: Message counter, Little Endian. Forth running counter\n * Byte 4: Command type. Either DIRECT_COMMAND_REPLY or DIRECT_COMMAND_NO_REPLY\n * Byte 5 - 6: Reservation (allocation) of global and local variables using a compressed format\n * (globals reserved in byte 5 and the 2 lsb of byte 6, locals reserved in the upper\n * 6 bits of byte 6) – see documentation for more details.\n * Byte 7 - n: Byte codes as a single command or compound commands (I.e. more commands composed\n * as a small program)\n *\n * @param {number} type - the direct command type.\n * @param {string} byteCommands - a compound array of EV3 Opcode + arguments.\n * @param {number} allocation - the allocation of global and local vars needed for replies.\n * @return {array} - generated complete command byte array, with header and compounded commands.\n */\n generateCommand (type, byteCommands, allocation = 0) {\n\n // Header (Bytes 0 - 6)\n let command = [];\n command[2] = 0; // Message counter unused for now\n command[3] = 0; // Message counter unused for now\n command[4] = type;\n command[5] = allocation & 0xFF;\n command[6] = allocation >> 8 && 0xFF;\n\n // Bytecodes (Bytes 7 - n)\n command = command.concat(byteCommands);\n\n // Calculate command length minus first two header bytes\n const len = command.length - 2;\n command[0] = len & 0xFF;\n command[1] = len >> 8 && 0xFF;\n\n return command;\n }\n\n /**\n * When the EV3 peripheral connects, start polling for sensor and motor values.\n * @private\n */\n _onConnect () {\n this._pollingIntervalID = window.setInterval(this._pollValues, this._pollingInterval);\n }\n\n /**\n * Poll the EV3 for sensor and motor input values, based on the list of\n * known connected sensors and motors. This is sent as many compound commands\n * in a direct command, with a reply expected.\n *\n * See 'EV3 Firmware Developer Kit', section 4.8, page 46, at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits\n * for a list of polling/input device commands and their arguments.\n *\n * @private\n */\n _pollValues () {\n if (!this.isConnected()) {\n window.clearInterval(this._pollingIntervalID);\n return;\n }\n\n const cmds = []; // compound command\n let allocation = 0;\n let sensorCount = 0;\n\n // Reset the list of devices every 20 counts\n if (this._pollingCounter % 20 === 0) {\n // GET DEVICE LIST\n cmds[0] = Ev3Opcode.OPINPUT_DEVICE_LIST;\n cmds[1] = Ev3Encoding.ONE_BYTE;\n cmds[2] = Ev3Args.MAX_DEVICES;\n cmds[3] = Ev3Encoding.GLOBAL_VARIABLE_INDEX_0;\n cmds[4] = Ev3Encoding.GLOBAL_VARIABLE_ONE_BYTE;\n cmds[5] = Ev3Encoding.GLOBAL_CONSTANT_INDEX_0;\n\n // Command and payload lengths\n allocation = 33;\n\n this._updateDevices = true;\n } else {\n // GET SENSOR VALUES FOR CONNECTED SENSORS\n let index = 0;\n for (let i = 0; i < 4; i++) {\n if (this._sensorPorts[i] !== 'none') {\n cmds[index + 0] = Ev3Opcode.OPINPUT_READSI;\n cmds[index + 1] = Ev3Args.LAYER;\n cmds[index + 2] = i; // PORT\n cmds[index + 3] = Ev3Args.DO_NOT_CHANGE_TYPE;\n cmds[index + 4] = Ev3Mode[this._sensorPorts[i]];\n cmds[index + 5] = Ev3Encoding.GLOBAL_VARIABLE_ONE_BYTE;\n cmds[index + 6] = sensorCount * 4; // GLOBAL INDEX\n index += 7;\n }\n sensorCount++;\n }\n\n // GET MOTOR POSITION VALUES, EVEN IF NO MOTOR PRESENT\n for (let i = 0; i < 4; i++) {\n cmds[index + 0] = Ev3Opcode.OPOUTPUT_GET_COUNT;\n cmds[index + 1] = Ev3Args.LAYER;\n cmds[index + 2] = i; // PORT (incorrectly specified as 'Output bit field' in LEGO docs)\n cmds[index + 3] = Ev3Encoding.GLOBAL_VARIABLE_ONE_BYTE;\n cmds[index + 4] = sensorCount * 4; // GLOBAL INDEX\n index += 5;\n sensorCount++;\n }\n\n // Command and payload lengths\n allocation = sensorCount * 4;\n }\n\n const cmd = this.generateCommand(\n Ev3Command.DIRECT_COMMAND_REPLY,\n cmds,\n allocation\n );\n\n this.send(cmd);\n\n this._pollingCounter++;\n }\n\n /**\n * Message handler for incoming EV3 reply messages, either a list of connected\n * devices (sensors and motors) or the values of the connected sensors and motors.\n *\n * See 'EV3 Communication Developer Kit', section 4.1, page 24 at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits\n * for more details on direct reply formats.\n *\n * The direct reply byte array sent takes the following format:\n * Byte 0 – 1: Reply size, Little Endian. Reply size not including these 2 bytes\n * Byte 2 – 3: Message counter, Little Endian. Equals the Direct Command\n * Byte 4: Reply type. Either DIRECT_REPLY or DIRECT_REPLY_ERROR\n * Byte 5 - n: Resonse buffer. I.e. the content of the by the Command reserved global variables.\n * I.e. if the command reserved 64 bytes, these bytes will be placed in the reply\n * packet as the bytes 5 to 68.\n *\n * See 'EV3 Firmware Developer Kit', section 4.8, page 56 at\n * https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits\n * for direct response buffer formats for various commands.\n *\n * @param {object} params - incoming message parameters\n * @private\n */\n _onMessage (params) {\n const message = params.message;\n const data = Base64Util.base64ToUint8Array(message);\n\n if (data[4] !== Ev3Command.DIRECT_REPLY) {\n return;\n }\n\n if (this._updateDevices) {\n\n // PARSE DEVICE LIST\n for (let i = 0; i < 4; i++) {\n const deviceType = Ev3Device[data[i + 5]];\n // if returned device type is null, use 'none'\n this._sensorPorts[i] = deviceType ? deviceType : 'none';\n }\n for (let i = 0; i < 4; i++) {\n const deviceType = Ev3Device[data[i + 21]];\n // if returned device type is null, use 'none'\n this._motorPorts[i] = deviceType ? deviceType : 'none';\n }\n for (let m = 0; m < 4; m++) {\n const type = this._motorPorts[m];\n if (type !== 'none' && !this._motors[m]) {\n // add new motor if don't already have one\n this._motors[m] = new EV3Motor(this, m, type);\n }\n if (type === 'none' && this._motors[m]) {\n // clear old motor\n this._motors[m] = null;\n }\n }\n this._updateDevices = false;\n\n // eslint-disable-next-line no-undefined\n } else if (!this._sensorPorts.includes(undefined) && !this._motorPorts.includes(undefined)) {\n\n // PARSE SENSOR VALUES\n let offset = 5; // start reading sensor values at byte 5\n for (let i = 0; i < 4; i++) {\n // array 2 float\n const buffer = new Uint8Array([\n data[offset],\n data[offset + 1],\n data[offset + 2],\n data[offset + 3]\n ]).buffer;\n const view = new DataView(buffer);\n const value = view.getFloat32(0, true);\n\n if (Ev3Label[this._sensorPorts[i]] === 'button') {\n // Read a button value per port\n this._sensors.buttons[i] = value ? value : 0;\n } else if (Ev3Label[this._sensorPorts[i]]) { // if valid\n // Read brightness / distance values and set to 0 if null\n this._sensors[Ev3Label[this._sensorPorts[i]]] = value ? value : 0;\n }\n offset += 4;\n }\n\n // PARSE MOTOR POSITION VALUES, EVEN IF NO MOTOR PRESENT\n for (let i = 0; i < 4; i++) {\n const positionArray = [\n data[offset],\n data[offset + 1],\n data[offset + 2],\n data[offset + 3]\n ];\n if (this._motors[i]) {\n this._motors[i].position = positionArray;\n }\n offset += 4;\n }\n\n }\n }\n}\n\n/**\n * Enum for motor port names.\n * Note: if changed, will break compatibility with previously saved projects.\n * @readonly\n * @enum {string}\n */\nconst Ev3MotorMenu = ['A', 'B', 'C', 'D'];\n\n/**\n * Enum for sensor port names.\n * Note: if changed, will break compatibility with previously saved projects.\n * @readonly\n * @enum {string}\n */\nconst Ev3SensorMenu = ['1', '2', '3', '4'];\n\nclass Scratch3Ev3Blocks {\n\n /**\n * The ID of the extension.\n * @return {string} the id\n */\n static get EXTENSION_ID () {\n return 'ev3';\n }\n\n /**\n * Creates a new instance of the EV3 extension.\n * @param {object} runtime VM runtime\n * @constructor\n */\n constructor (runtime) {\n /**\n * The Scratch 3.0 runtime.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n // Create a new EV3 peripheral instance\n this._peripheral = new EV3(this.runtime, Scratch3Ev3Blocks.EXTENSION_ID);\n\n this._playNoteForPicker = this._playNoteForPicker.bind(this);\n this.runtime.on('PLAY_NOTE', this._playNoteForPicker);\n }\n\n /**\n * Define the EV3 extension.\n * @return {object} Extension description.\n */\n getInfo () {\n return {\n id: Scratch3Ev3Blocks.EXTENSION_ID,\n name: 'LEGO EV3',\n blockIconURI: blockIconURI,\n showStatusButton: true,\n blocks: [\n {\n opcode: 'motorTurnClockwise',\n text: formatMessage({\n id: 'ev3.motorTurnClockwise',\n default: 'motor [PORT] turn this way for [TIME] seconds',\n description: 'turn a motor clockwise for some time'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n PORT: {\n type: ArgumentType.STRING,\n menu: 'motorPorts',\n defaultValue: 0\n },\n TIME: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n }\n },\n {\n opcode: 'motorTurnCounterClockwise',\n text: formatMessage({\n id: 'ev3.motorTurnCounterClockwise',\n default: 'motor [PORT] turn that way for [TIME] seconds',\n description: 'turn a motor counter-clockwise for some time'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n PORT: {\n type: ArgumentType.STRING,\n menu: 'motorPorts',\n defaultValue: 0\n },\n TIME: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n }\n },\n {\n opcode: 'motorSetPower',\n text: formatMessage({\n id: 'ev3.motorSetPower',\n default: 'motor [PORT] set power [POWER] %',\n description: 'set a motor\\'s power to some value'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n PORT: {\n type: ArgumentType.STRING,\n menu: 'motorPorts',\n defaultValue: 0\n },\n POWER: {\n type: ArgumentType.NUMBER,\n defaultValue: 100\n }\n }\n },\n {\n opcode: 'getMotorPosition',\n text: formatMessage({\n id: 'ev3.getMotorPosition',\n default: 'motor [PORT] position',\n description: 'get the measured degrees a motor has turned'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n PORT: {\n type: ArgumentType.STRING,\n menu: 'motorPorts',\n defaultValue: 0\n }\n }\n },\n {\n opcode: 'whenButtonPressed',\n text: formatMessage({\n id: 'ev3.whenButtonPressed',\n default: 'when button [PORT] pressed',\n description: 'when a button connected to a port is pressed'\n }),\n blockType: BlockType.HAT,\n arguments: {\n PORT: {\n type: ArgumentType.STRING,\n menu: 'sensorPorts',\n defaultValue: 0\n }\n }\n },\n {\n opcode: 'whenDistanceLessThan',\n text: formatMessage({\n id: 'ev3.whenDistanceLessThan',\n default: 'when distance < [DISTANCE]',\n description: 'when the value measured by the distance sensor is less than some value'\n }),\n blockType: BlockType.HAT,\n arguments: {\n DISTANCE: {\n type: ArgumentType.NUMBER,\n defaultValue: 5\n }\n }\n },\n {\n opcode: 'whenBrightnessLessThan',\n text: formatMessage({\n id: 'ev3.whenBrightnessLessThan',\n default: 'when brightness < [DISTANCE]',\n description: 'when value measured by brightness sensor is less than some value'\n }),\n blockType: BlockType.HAT,\n arguments: {\n DISTANCE: {\n type: ArgumentType.NUMBER,\n defaultValue: 50\n }\n }\n },\n {\n opcode: 'buttonPressed',\n text: formatMessage({\n id: 'ev3.buttonPressed',\n default: 'button [PORT] pressed?',\n description: 'is a button on some port pressed?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n PORT: {\n type: ArgumentType.STRING,\n menu: 'sensorPorts',\n defaultValue: 0\n }\n }\n },\n {\n opcode: 'getDistance',\n text: formatMessage({\n id: 'ev3.getDistance',\n default: 'distance',\n description: 'gets measured distance'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'getBrightness',\n text: formatMessage({\n id: 'ev3.getBrightness',\n default: 'brightness',\n description: 'gets measured brightness'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'beep',\n text: formatMessage({\n id: 'ev3.beepNote',\n default: 'beep note [NOTE] for [TIME] secs',\n description: 'play some note on EV3 for some time'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n NOTE: {\n type: ArgumentType.NOTE,\n defaultValue: 60\n },\n TIME: {\n type: ArgumentType.NUMBER,\n defaultValue: 0.5\n }\n }\n }\n ],\n menus: {\n motorPorts: {\n acceptReporters: true,\n items: this._formatMenu(Ev3MotorMenu)\n },\n sensorPorts: {\n acceptReporters: true,\n items: this._formatMenu(Ev3SensorMenu)\n }\n }\n };\n }\n\n motorTurnClockwise (args) {\n const port = Cast.toNumber(args.PORT);\n let time = Cast.toNumber(args.TIME) * 1000;\n time = MathUtil.clamp(time, 0, 15000);\n\n return new Promise(resolve => {\n this._forEachMotor(port, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.direction = 1;\n motor.turnOnFor(time);\n }\n });\n\n // Run for some time even when no motor is connected\n setTimeout(resolve, time);\n });\n }\n\n motorTurnCounterClockwise (args) {\n const port = Cast.toNumber(args.PORT);\n let time = Cast.toNumber(args.TIME) * 1000;\n time = MathUtil.clamp(time, 0, 15000);\n\n return new Promise(resolve => {\n this._forEachMotor(port, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.direction = -1;\n motor.turnOnFor(time);\n }\n });\n\n // Run for some time even when no motor is connected\n setTimeout(resolve, time);\n });\n }\n\n motorSetPower (args) {\n const port = Cast.toNumber(args.PORT);\n const power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100);\n\n this._forEachMotor(port, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.power = power;\n }\n });\n }\n\n getMotorPosition (args) {\n const port = Cast.toNumber(args.PORT);\n\n if (![0, 1, 2, 3].includes(port)) {\n return;\n }\n\n const motor = this._peripheral.motor(port);\n let position = 0;\n if (motor) {\n position = MathUtil.wrapClamp(motor.position, 0, 360);\n }\n\n return position;\n }\n\n whenButtonPressed (args) {\n const port = Cast.toNumber(args.PORT);\n\n if (![0, 1, 2, 3].includes(port)) {\n return;\n }\n\n return this._peripheral.isButtonPressed(port);\n }\n\n whenDistanceLessThan (args) {\n const distance = MathUtil.clamp(Cast.toNumber(args.DISTANCE), 0, 100);\n\n return this._peripheral.distance < distance;\n }\n\n whenBrightnessLessThan (args) {\n const brightness = MathUtil.clamp(Cast.toNumber(args.DISTANCE), 0, 100);\n\n return this._peripheral.brightness < brightness;\n }\n\n buttonPressed (args) {\n const port = Cast.toNumber(args.PORT);\n\n if (![0, 1, 2, 3].includes(port)) {\n return;\n }\n\n return this._peripheral.isButtonPressed(port);\n }\n\n getDistance () {\n return this._peripheral.distance;\n }\n\n getBrightness () {\n return this._peripheral.brightness;\n }\n\n _playNoteForPicker (note, category) {\n if (category !== this.getInfo().name) return;\n this.beep({\n NOTE: note,\n TIME: 0.25\n });\n }\n\n beep (args) {\n const note = MathUtil.clamp(Cast.toNumber(args.NOTE), 47, 99); // valid EV3 sounds\n let time = Cast.toNumber(args.TIME) * 1000;\n time = MathUtil.clamp(time, 0, 3000);\n\n if (time === 0) {\n return; // don't send a beep time of 0\n }\n\n return new Promise(resolve => {\n // https://en.wikipedia.org/wiki/MIDI_tuning_standard#Frequency_values\n const freq = Math.pow(2, ((note - 69 + 12) / 12)) * 440;\n this._peripheral.beep(freq, time);\n\n // Run for some time even when no piezo is connected.\n setTimeout(resolve, time);\n });\n }\n\n /**\n * Call a callback for each motor indexed by the provided motor ID.\n *\n * Note: This way of looping through motors is currently unnecessary, but could be\n * useful if an 'all motors' option is added in the future (see WeDo2 extension).\n *\n * @param {MotorID} motorID - the ID specifier.\n * @param {Function} callback - the function to call with the numeric motor index for each motor.\n * @private\n */\n _forEachMotor (motorID, callback) {\n let motors;\n switch (motorID) {\n case 0:\n motors = [0];\n break;\n case 1:\n motors = [1];\n break;\n case 2:\n motors = [2];\n break;\n case 3:\n motors = [3];\n break;\n default:\n log.warn(`Invalid motor ID: ${motorID}`);\n motors = [];\n break;\n }\n for (const index of motors) {\n callback(index);\n }\n }\n\n /**\n * Formats menus into a format suitable for block menus, and loading previously\n * saved projects:\n * [\n * {\n * text: label,\n * value: index\n * },\n * {\n * text: label,\n * value: index\n * },\n * etc...\n * ]\n *\n * @param {array} menu - a menu to format.\n * @return {object} - a formatted menu as an object.\n * @private\n */\n _formatMenu (menu) {\n const m = [];\n for (let i = 0; i < menu.length; i++) {\n const obj = {};\n obj.text = menu[i];\n obj.value = i.toString();\n m.push(obj);\n }\n return m;\n }\n}\n\nmodule.exports = Scratch3Ev3Blocks;\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst log = require('../../util/log');\nconst formatMessage = require('format-message');\nconst MathUtil = require('../../util/math-util');\nconst BLE = require('../../io/ble');\nconst godirect = require('@vernier/godirect/dist/godirect.min.umd.js');\nconst ScratchLinkDeviceAdapter = require('./scratch-link-device-adapter');\n\n/**\n * Icon png to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABGdBTUEAALGPC/xhBQAACCNJREFUeAHtnGtsFFUUgM+dfXbbbbcWaKHSFgrlkWgkJCb6A4kmJfiHIBYBpcFfRg1GEkmEVAvhFYw/TExMxGoICAECiZEIIUQCiiT4gh+KILRQCi2ENIV2t/ue6zl3u2Upu4XuzO4csCe587iPmXO/OWfunTszV4ABWfflQU+0p+9bTcLzEmS5gUPlvagAcVMXcMpnK1u+evW8QLYKaNkWpHKxnt6dQsqFjxo80p10Jt1vx7t30n62Ys+2IJUTUpDlqUNomgYutwsjhZFD5r6slBAOhUHX9YTe6D1GTmrIAhFeBZ2c4JFCpBiggmwlBR7pTGLUewxZYBIUWV7yqgb7g8lotuukt5ihqyELHCSEbusk931ExMxbjSkWSNxEyr3vysxZLFHWnDuT0CtFV6OKmmOBRrV4hMubZoGmMZA6lHTfgsLeHnBEIiCxUY86XRDw+sBfOgZ0m820U5lxIFYAncF+GNvVDo5QaLBu1ClyYTyF4tvd8lZltQgXFA6mW73BxoVt0ShUXG2VCp4QQdDEFqez4Bm7p7gaO0of422r3x4Ji/KrbdIexu4SE2FjgWO6OkCLx6gt6gxOiNV92tiY+ni1Ye1nu7dpQfk35ikru9EBN6unsEDIwgLJPQv8dwCfT3WPt+iFIfAUqM3vL7vpjmuz0KX1gkAfOMN33dxKkjwA9vsTDIS8uubdBZcyAWlqWtohQbRSuru/L1O2vMazAGiLxRKVFqDgDEdAaHCN0kU8Ply2vKWxABhzJZ5ipC6qHlRzfJxVz99S49GdYQEw7PYkuAmokZJ6fumlQUqiNpVSQ56i9JnyHMsCYMRdADGHk0ZyHM1b976XicH0rXtWYR57FPNSGQ7CAiCBCJQ8oXhI0FdmBiPfVnl9ZZmz5DmFDcA+HwIUOEYMcjL2+e57PbBp04HxONI4ifIEKC8TYQMwhs+7IU+hwBFOYQvB5qF8grbwJnRfQXnIhbkIG4AExF+ScE00w0X3AZLwisrDyH1JH1YAA8UlIG029FRZsu6TPfVJiIltWYIjMTLgLUlGs1izeRYmGtS383t9wnu7G2J6fH/Tln2LNUdExGLxvZSOQ1qCS/+P9CFhBZAUuj12PHgCvRJHZ7w4EnhYjya6hXGHQ2Jaxj4ilbVC2AFEUNBVXSdKb3WC29+rmISKiqFn7ARBadyEHUACFHM64VZlDTdWafVh1Yik1ZB5JEsLJGaVtosw37ld4TscWQHX4+oRWO1zWrAEWCR6oMnTCEXijmI1234MVvsPgV+WcmKndGHpwlNtZwbhkZYEkuI4CkuAXfpk0HGAPym0TXEchaUL39Br4JvQeljk+lwxOxBeCRQ3UrFHI+AMBsEV6gcnhlwIS4BU0RORV1V42EqnwnLgSyo3AsM3eA9bPOt8bAEOV6NUWGRZ9FYvHSx6R0pfYgkMmk2DCH1+Z7KwB5gKazjLGgpLgUOAuRZWALnDSncxLAOYCmskbqjhe02h5d6y0sFKF5cXgI8LrLwB9PTeGew6POwNnptlpYOVLi4nFjjuWts957rnBk8tomoZ+bjhPcqOcCcnAG34EaTqOjxmsNKxzQnAkX5wronsOry6zIn66ThljLNcg+W1a2Gi55+MCg6XcKl3NuxrbxouS87TLAcY1V0QV5+8jLyuEekeeSGTS1gOcM/lZpOrlN/DsRzOyi8CY2fLuwUum/wR1BT+ZUzrDKUv9D4LB9rXZEjNTfRjZYFS5r86ebfA3W0bcmMKFh01/5fMoorm6rSjAA2SNc2F8dvmQVWCgdy8fxg8gcEN0pWez80QUyyQFAqn/N9mhmK5PAYN7adecCPnMsUCCZ7U8ari4IGb87wJeKFDA/MlmHXBDVkgTR1CV4/gaThKzBoeKYpuSzqSrqSzEiFuJDayWxqyQJp3RUhYSKfWUSEz5iDIrhrZl8I5b37JvrTBT3wdpd43cOqT/WiJhq6ikQpkW5a8BxuS/X219uXZHoPKmdMUGdEgpWzTll3Kr95Z8VJK7N3NL7b/qHY2rnmdjd6G7oF3q/b/3RoFaPDajwIcBWiQgMHioxZoEKChfqDBc2csnmxtM2ZglMDKArFvduhBbLDv9sOD8oymA0xBCHVtl6+c7ey6Ibdt+3ox7WOoxMCmD4i68PrZkBQaEDUe1tnVqSyyfl79+vr6evz1C2jKogkYWEEc0JnViiZRqKuoqJiZtEJcn0GIsykewzhW2jJVZjzBamxsfK79ase/5MoXL106TnEDwfq36qgIF6HGjKyqFsNkDGMwUNxEDEmIHQTxyNGjH1AchvumBcC4vAuXVpiA+TDYMFDXiiZFoN+SrmMI7tixo/v3337diNtQUzNpPq1RChIra5ccAFKDUEwYLra2fnXu3PmtA0gojqbaVUNl23ft+pPiPW73U7RGYdGH5QCQYCg93C73075S34I5c+ZQa0s/B1Njou51tVVVatJAXcrED3Q4EI5plgsHgAQiSiRCoRD9ECeam9fPo32UJzFQYwJLlix9mdZ9fb1naY2iyiQ2rVtyAEi199Pi5M8/tdB62vRpzceOH3+toaHBh61w2clTp96sqq5ehUnxw0eO7KA8KKpMYtO6JZcOKTUeNRhsp0+ffmtilYI1VLf4+Qvn1784d+5ezEfW144hMR05blglpDgHSbqxt6Wl5Y8ZM6afKq8oL7LZHd54PH7H7w+cOPj9dx8uXbLk+ICynbhm4cJDr7LVMKmhoP5dphaWoFGrHMTAQrgBJCjkFdQHpPntqCUmiWCge14PBsvdFnUYlP8AMAKfKIKmYukAAAAASUVORK5CYII=';\n\n/**\n * Icon png to be displayed in the blocks category menu, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst menuIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAALGPC/xhBQAAA9dJREFUWAnNmE2IFEcUgF/9dE/v7LoaM9kkK4JBRA0EFBIPRm85hBAvEXHXwyo5eFE87GFcReMkObgJiQnkkJzEg9n8HIJixKNe1IMKihgiCbviwV11V3d0d3pmuqsqr5ppcEnb3TNVggVFVVe9eu+r97qqq4tASqp8/fsboQgmU0TMugi571K29bPy9ovPU8Sf16HbpQj3EkYFBcJcr5Am2nZfs94AIWVfqMQeHNwhICUBZ4ypUIA/X2sbIm2AW8AJK0lkEP6TJpfqwXgg4QxmF/fB7Gtvxk1G5ZKHU1CqTgPJoSUXYJYeohSUJu+qrqdVUGh2/pVX4VFffx77WaqBZkrkEFj271+qWH0sXcU3FBzyQe/Mg7B//LbKMTRTxNiDbsMHHjTJlyM7HEJIBHXs2KXFj+oTNSdoQOCYLS5jD9IwBMm5H8NplwwPb/QV4yEIcycaAza9IuA76B38fuz1OF5RXUkmHCdu6rg0BpSMgV/sAe7DdzGFrvvdi0D3mSZjQA0wt7REQsY+iWF0XbfFzyal8SLRxuteD+Du4h4Z/flbqaBHibAQtZmQtcZaAZSMwtTylaR/4vaw1ju5YhWG10pwwAqghmp2FeHO2+t11WqyM80W0m7vAOhsM1kD7CGz8L57Jsq6bitZC/GcWgLf1H6KuHT92cTDAFy/BgXMXm0OCpgV50Bo9kK3BqiBboabQMMU/WoL5im4jToeq/AIgXsiRx5KKCjcwPEsiAv/BQMu9EwyDHXd/3kqCOSzDk6t5/YglQKKeJwq+PNRmJI8kwSTaj1HZy5AhSHqnXkIvU9mMUwEw4Q5wTM57LUtkg8QPw/cdcBJ+PhvKJ0Gj80nGq6JXrg6/XFiX97GXIBpyqTieKpKViOl+WEhWXMaUavvvdIZ8Giy5+Lh3bwKm/t+Be3JazMfxc1tldY26rastiHcsQevTG9pw0znovkAcRWHzSDKnZtaOJLSfMFLB5RqtRBS4LbCurqLCy0YPkU3C0IIPEimMqR2ei7ZX2+KQdRi/WahNT/GmfOD4Vyzhx/66pcjp85dUvcmp6J8+txldXh07PPskdkS+V6EbD0vTOKlB0x9B/O6BS8ULly9PgE6x4kDPR/XX5pyYKj8xcCucsUmkNUQE0JvKKm2VioVK5HRE7UKOHbi6B94RzP+93jtpC0vWgXUF0hr3ipuw8uadwd3jXxoA9IK4Pah8t6BneV9GgjD28Svw1mlxFobgFbeFTz13cKbth93fDryp2CEq0a4hTA+aAPQ/ESJFDdvXLzzzrqNjlTqOP6uDeFf0uhvJ0ZP2QD8D6ZzU6u8YIbBAAAAAElFTkSuQmCC';\n\n/**\n * Enum for Vernier godirect protocol.\n * @readonly\n * @enum {string}\n */\nconst BLEUUID = {\n service: 'd91714ef-28b9-4f91-ba16-f0d9a604f112',\n commandChar: 'f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb',\n responseChar: 'b41e6675-a329-40e0-aa01-44d2f444babe'\n};\n\n/**\n * A time interval to wait (in milliseconds) before reporting to the BLE socket\n * that data has stopped coming from the peripheral.\n */\nconst BLETimeout = 4500;\n\n/**\n * A string to report to the BLE socket when the GdxFor has stopped receiving data.\n * @type {string}\n */\nconst BLEDataStoppedError = 'Force and Acceleration extension stopped receiving data';\n\n/**\n * Sensor ID numbers for the GDX-FOR.\n */\nconst GDXFOR_SENSOR = {\n FORCE: 1,\n ACCELERATION_X: 2,\n ACCELERATION_Y: 3,\n ACCELERATION_Z: 4,\n SPIN_SPEED_X: 5,\n SPIN_SPEED_Y: 6,\n SPIN_SPEED_Z: 7\n};\n\n/**\n * The update rate, in milliseconds, for sensor data input from the peripheral.\n */\nconst GDXFOR_UPDATE_RATE = 80;\n\n/**\n * Threshold for pushing and pulling force, for the whenForcePushedOrPulled hat block.\n * @type {number}\n */\nconst FORCE_THRESHOLD = 5;\n\n/**\n * Threshold for acceleration magnitude, for the \"shaken\" gesture.\n * @type {number}\n */\nconst SHAKEN_THRESHOLD = 30;\n\n/**\n * Threshold for acceleration magnitude, to check if we are facing up.\n * @type {number}\n */\nconst FACING_THRESHOLD = 9;\n\n/**\n * An offset for the facing threshold, used to check that we are no longer facing up.\n * @type {number}\n */\nconst FACING_THRESHOLD_OFFSET = 5;\n\n/**\n * Threshold for acceleration magnitude, below which we are in freefall.\n * @type {number}\n */\nconst FREEFALL_THRESHOLD = 0.5;\n\n/**\n * Factor used to account for influence of rotation during freefall.\n * @type {number}\n */\nconst FREEFALL_ROTATION_FACTOR = 0.3;\n\n/**\n * Threshold in degrees for reporting that the sensor is tilted.\n * @type {number}\n */\nconst TILT_THRESHOLD = 15;\n\n/**\n * Acceleration due to gravity, in m/s^2.\n * @type {number}\n */\nconst GRAVITY = 9.8;\n\n/**\n * Manage communication with a GDX-FOR peripheral over a Scratch Link client socket.\n */\nclass GdxFor {\n\n /**\n * Construct a GDX-FOR communication object.\n * @param {Runtime} runtime - the Scratch 3.0 runtime\n * @param {string} extensionId - the id of the extension\n */\n constructor (runtime, extensionId) {\n\n /**\n * The Scratch 3.0 runtime used to trigger the green flag button.\n * @type {Runtime}\n * @private\n */\n this._runtime = runtime;\n\n /**\n * The BluetoothLowEnergy connection socket for reading/writing peripheral data.\n * @type {BLE}\n * @private\n */\n this._ble = null;\n\n /**\n * An @vernier/godirect Device\n * @type {Device}\n * @private\n */\n this._device = null;\n\n this._runtime.registerPeripheralExtension(extensionId, this);\n\n /**\n * The id of the extension this peripheral belongs to.\n */\n this._extensionId = extensionId;\n\n /**\n * The most recently received value for each sensor.\n * @type {Object.}\n * @private\n */\n this._sensors = {\n force: 0,\n accelerationX: 0,\n accelerationY: 0,\n accelerationZ: 0,\n spinSpeedX: 0,\n spinSpeedY: 0,\n spinSpeedZ: 0\n };\n\n /**\n * Interval ID for data reading timeout.\n * @type {number}\n * @private\n */\n this._timeoutID = null;\n\n this.reset = this.reset.bind(this);\n this._onConnect = this._onConnect.bind(this);\n }\n\n\n /**\n * Called by the runtime when user wants to scan for a peripheral.\n */\n scan () {\n if (this._ble) {\n this._ble.disconnect();\n }\n\n this._ble = new BLE(this._runtime, this._extensionId, {\n filters: [\n {namePrefix: 'GDX-FOR'}\n ],\n optionalServices: [\n BLEUUID.service\n ]\n }, this._onConnect, this.reset);\n }\n\n /**\n * Called by the runtime when user wants to connect to a certain peripheral.\n * @param {number} id - the id of the peripheral to connect to.\n */\n connect (id) {\n if (this._ble) {\n this._ble.connectPeripheral(id);\n }\n }\n\n /**\n * Called by the runtime when a user exits the connection popup.\n * Disconnect from the GDX FOR.\n */\n disconnect () {\n if (this._ble) {\n this._ble.disconnect();\n }\n\n this.reset();\n }\n\n /**\n * Reset all the state and timeout/interval ids.\n */\n reset () {\n this._sensors = {\n force: 0,\n accelerationX: 0,\n accelerationY: 0,\n accelerationZ: 0,\n spinSpeedX: 0,\n spinSpeedY: 0,\n spinSpeedZ: 0\n };\n\n if (this._timeoutID) {\n window.clearInterval(this._timeoutID);\n this._timeoutID = null;\n }\n }\n\n /**\n * Return true if connected to the goforce device.\n * @return {boolean} - whether the goforce is connected.\n */\n isConnected () {\n let connected = false;\n if (this._ble) {\n connected = this._ble.isConnected();\n }\n return connected;\n }\n\n /**\n * Starts reading data from peripheral after BLE has connected to it.\n * @private\n */\n _onConnect () {\n const adapter = new ScratchLinkDeviceAdapter(this._ble, BLEUUID);\n godirect.createDevice(adapter, {open: true, startMeasurements: false}).then(device => {\n // Setup device\n this._device = device;\n this._device.keepValues = false; // todo: possibly remove after updating Vernier godirect module\n\n // Enable sensors\n this._device.sensors.forEach(sensor => {\n sensor.setEnabled(true);\n });\n\n // Set sensor value-update behavior\n this._device.on('measurements-started', () => {\n const enabledSensors = this._device.sensors.filter(s => s.enabled);\n enabledSensors.forEach(sensor => {\n sensor.on('value-changed', s => {\n this._onSensorValueChanged(s);\n });\n });\n this._timeoutID = window.setInterval(\n () => this._ble.handleDisconnectError(BLEDataStoppedError),\n BLETimeout\n );\n });\n\n // Start device\n this._device.start(GDXFOR_UPDATE_RATE);\n });\n }\n\n /**\n * Handler for sensor value changes from the goforce device.\n * @param {object} sensor - goforce device sensor whose value has changed\n * @private\n */\n _onSensorValueChanged (sensor) {\n switch (sensor.number) {\n case GDXFOR_SENSOR.FORCE:\n // Normalize the force, which can be measured between -50 and 50 N,\n // to be a value between -100 and 100.\n this._sensors.force = MathUtil.clamp(sensor.value * 2, -100, 100);\n break;\n case GDXFOR_SENSOR.ACCELERATION_X:\n this._sensors.accelerationX = sensor.value;\n break;\n case GDXFOR_SENSOR.ACCELERATION_Y:\n this._sensors.accelerationY = sensor.value;\n break;\n case GDXFOR_SENSOR.ACCELERATION_Z:\n this._sensors.accelerationZ = sensor.value;\n break;\n case GDXFOR_SENSOR.SPIN_SPEED_X:\n this._sensors.spinSpeedX = this._spinSpeedFromGyro(sensor.value);\n break;\n case GDXFOR_SENSOR.SPIN_SPEED_Y:\n this._sensors.spinSpeedY = this._spinSpeedFromGyro(sensor.value);\n break;\n case GDXFOR_SENSOR.SPIN_SPEED_Z:\n this._sensors.spinSpeedZ = this._spinSpeedFromGyro(sensor.value);\n break;\n }\n // cancel disconnect timeout and start a new one\n window.clearInterval(this._timeoutID);\n this._timeoutID = window.setInterval(\n () => this._ble.handleDisconnectError(BLEDataStoppedError),\n BLETimeout\n );\n }\n\n _spinSpeedFromGyro (val) {\n const framesPerSec = 1000 / this._runtime.currentStepTime;\n val = MathUtil.radToDeg(val);\n val = val / framesPerSec; // convert to from degrees per sec to degrees per frame\n val = val * -1;\n return val;\n }\n\n getForce () {\n return this._sensors.force;\n }\n\n getTiltFrontBack (back = false) {\n const x = this.getAccelerationX();\n const y = this.getAccelerationY();\n const z = this.getAccelerationZ();\n\n // Compute the yz unit vector\n const y2 = y * y;\n const z2 = z * z;\n let value = y2 + z2;\n value = Math.sqrt(value);\n\n // For sufficiently small zy vector values we are essentially at 90 degrees.\n // The following snaps to 90 and avoids divide-by-zero errors.\n // The snap factor was derived through observation -- just enough to\n // still allow single degree steps up to 90 (..., 87, 88, 89, 90).\n if (value < 0.35) {\n value = (x < 0) ? 90 : -90;\n } else {\n value = x / value;\n value = Math.atan(value);\n value = MathUtil.radToDeg(value) * -1;\n }\n\n // Back is the inverse of front\n if (back) value *= -1;\n\n return value;\n }\n\n getTiltLeftRight (right = false) {\n const x = this.getAccelerationX();\n const y = this.getAccelerationY();\n const z = this.getAccelerationZ();\n\n // Compute the yz unit vector\n const x2 = x * x;\n const z2 = z * z;\n let value = x2 + z2;\n value = Math.sqrt(value);\n\n // For sufficiently small zy vector values we are essentially at 90 degrees.\n // The following snaps to 90 and avoids divide-by-zero errors.\n // The snap factor was derived through observation -- just enough to\n // still allow single degree steps up to 90 (..., 87, 88, 89, 90).\n if (value < 0.35) {\n value = (y < 0) ? 90 : -90;\n } else {\n value = y / value;\n value = Math.atan(value);\n value = MathUtil.radToDeg(value) * -1;\n }\n\n // Right is the inverse of left\n if (right) value *= -1;\n\n return value;\n }\n\n getAccelerationX () {\n return this._sensors.accelerationX;\n }\n\n getAccelerationY () {\n return this._sensors.accelerationY;\n }\n\n getAccelerationZ () {\n return this._sensors.accelerationZ;\n }\n\n getSpinSpeedX () {\n return this._sensors.spinSpeedX;\n }\n\n getSpinSpeedY () {\n return this._sensors.spinSpeedY;\n }\n\n getSpinSpeedZ () {\n return this._sensors.spinSpeedZ;\n }\n}\n\n/**\n * Enum for pushed and pulled menu options.\n * @readonly\n * @enum {string}\n */\nconst PushPullValues = {\n PUSHED: 'pushed',\n PULLED: 'pulled'\n};\n\n/**\n * Enum for motion gesture menu options.\n * @readonly\n * @enum {string}\n */\nconst GestureValues = {\n SHAKEN: 'shaken',\n STARTED_FALLING: 'started falling',\n TURNED_FACE_UP: 'turned face up',\n TURNED_FACE_DOWN: 'turned face down'\n};\n\n/**\n * Enum for tilt axis menu options.\n * @readonly\n * @enum {string}\n */\nconst TiltAxisValues = {\n FRONT: 'front',\n BACK: 'back',\n LEFT: 'left',\n RIGHT: 'right',\n ANY: 'any'\n};\n\n/**\n * Enum for axis menu options.\n * @readonly\n * @enum {string}\n */\nconst AxisValues = {\n X: 'x',\n Y: 'y',\n Z: 'z'\n};\n\n/**\n * Scratch 3.0 blocks to interact with a GDX-FOR peripheral.\n */\nclass Scratch3GdxForBlocks {\n\n /**\n * @return {string} - the name of this extension.\n */\n static get EXTENSION_NAME () {\n return 'Force and Acceleration';\n }\n\n /**\n * @return {string} - the ID of this extension.\n */\n static get EXTENSION_ID () {\n return 'gdxfor';\n }\n\n get AXIS_MENU () {\n return [\n {\n text: 'x',\n value: AxisValues.X\n },\n {\n text: 'y',\n value: AxisValues.Y\n },\n {\n text: 'z',\n value: AxisValues.Z\n }\n ];\n }\n\n get TILT_MENU () {\n return [\n {\n text: formatMessage({\n id: 'gdxfor.tiltDirectionMenu.front',\n default: 'front',\n description: 'label for front element in tilt direction picker for gdxfor extension'\n }),\n value: TiltAxisValues.FRONT\n },\n {\n text: formatMessage({\n id: 'gdxfor.tiltDirectionMenu.back',\n default: 'back',\n description: 'label for back element in tilt direction picker for gdxfor extension'\n }),\n value: TiltAxisValues.BACK\n },\n {\n text: formatMessage({\n id: 'gdxfor.tiltDirectionMenu.left',\n default: 'left',\n description: 'label for left element in tilt direction picker for gdxfor extension'\n }),\n value: TiltAxisValues.LEFT\n },\n {\n text: formatMessage({\n id: 'gdxfor.tiltDirectionMenu.right',\n default: 'right',\n description: 'label for right element in tilt direction picker for gdxfor extension'\n }),\n value: TiltAxisValues.RIGHT\n }\n ];\n }\n\n get TILT_MENU_ANY () {\n return [\n ...this.TILT_MENU,\n {\n text: formatMessage({\n id: 'gdxfor.tiltDirectionMenu.any',\n default: 'any',\n description: 'label for any direction element in tilt direction picker for gdxfor extension'\n }),\n value: TiltAxisValues.ANY\n }\n ];\n }\n\n get PUSH_PULL_MENU () {\n return [\n {\n text: formatMessage({\n id: 'gdxfor.pushed',\n default: 'pushed',\n description: 'the force sensor was pushed inward'\n }),\n value: PushPullValues.PUSHED\n },\n {\n text: formatMessage({\n id: 'gdxfor.pulled',\n default: 'pulled',\n description: 'the force sensor was pulled outward'\n }),\n value: PushPullValues.PULLED\n }\n ];\n }\n\n get GESTURE_MENU () {\n return [\n {\n text: formatMessage({\n id: 'gdxfor.shaken',\n default: 'shaken',\n description: 'the sensor was shaken'\n }),\n value: GestureValues.SHAKEN\n },\n {\n text: formatMessage({\n id: 'gdxfor.startedFalling',\n default: 'started falling',\n description: 'the sensor started free falling'\n }),\n value: GestureValues.STARTED_FALLING\n },\n {\n text: formatMessage({\n id: 'gdxfor.turnedFaceUp',\n default: 'turned face up',\n description: 'the sensor was turned to face up'\n }),\n value: GestureValues.TURNED_FACE_UP\n },\n {\n text: formatMessage({\n id: 'gdxfor.turnedFaceDown',\n default: 'turned face down',\n description: 'the sensor was turned to face down'\n }),\n value: GestureValues.TURNED_FACE_DOWN\n }\n ];\n }\n\n /**\n * Construct a set of GDX-FOR blocks.\n * @param {Runtime} runtime - the Scratch 3.0 runtime.\n */\n constructor (runtime) {\n /**\n * The Scratch 3.0 runtime.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n // Create a new GdxFor peripheral instance\n this._peripheral = new GdxFor(this.runtime, Scratch3GdxForBlocks.EXTENSION_ID);\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: Scratch3GdxForBlocks.EXTENSION_ID,\n name: Scratch3GdxForBlocks.EXTENSION_NAME,\n blockIconURI: blockIconURI,\n menuIconURI: menuIconURI,\n showStatusButton: true,\n blocks: [\n {\n opcode: 'whenGesture',\n text: formatMessage({\n id: 'gdxfor.whenGesture',\n default: 'when [GESTURE]',\n description: 'when the sensor detects a gesture'\n }),\n blockType: BlockType.HAT,\n arguments: {\n GESTURE: {\n type: ArgumentType.STRING,\n menu: 'gestureOptions',\n defaultValue: GestureValues.SHAKEN\n }\n }\n },\n {\n opcode: 'whenForcePushedOrPulled',\n text: formatMessage({\n id: 'gdxfor.whenForcePushedOrPulled',\n default: 'when force sensor [PUSH_PULL]',\n description: 'when the force sensor is pushed or pulled'\n }),\n blockType: BlockType.HAT,\n arguments: {\n PUSH_PULL: {\n type: ArgumentType.STRING,\n menu: 'pushPullOptions',\n defaultValue: PushPullValues.PUSHED\n }\n }\n },\n {\n opcode: 'getForce',\n text: formatMessage({\n id: 'gdxfor.getForce',\n default: 'force',\n description: 'gets force'\n }),\n blockType: BlockType.REPORTER\n },\n '---',\n {\n opcode: 'whenTilted',\n text: formatMessage({\n id: 'gdxfor.whenTilted',\n default: 'when tilted [TILT]',\n description: 'when the sensor detects tilt'\n }),\n blockType: BlockType.HAT,\n arguments: {\n TILT: {\n type: ArgumentType.STRING,\n menu: 'tiltAnyOptions',\n defaultValue: TiltAxisValues.ANY\n }\n }\n },\n {\n opcode: 'isTilted',\n text: formatMessage({\n id: 'gdxfor.isTilted',\n default: 'tilted [TILT]?',\n description: 'is the device tilted?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n TILT: {\n type: ArgumentType.STRING,\n menu: 'tiltAnyOptions',\n defaultValue: TiltAxisValues.ANY\n }\n }\n },\n {\n opcode: 'getTilt',\n text: formatMessage({\n id: 'gdxfor.getTilt',\n default: 'tilt angle [TILT]',\n description: 'gets tilt'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n TILT: {\n type: ArgumentType.STRING,\n menu: 'tiltOptions',\n defaultValue: TiltAxisValues.FRONT\n }\n }\n },\n '---',\n {\n opcode: 'isFreeFalling',\n text: formatMessage({\n id: 'gdxfor.isFreeFalling',\n default: 'falling?',\n description: 'is the device in free fall?'\n }),\n blockType: BlockType.BOOLEAN\n },\n {\n opcode: 'getSpinSpeed',\n text: formatMessage({\n id: 'gdxfor.getSpin',\n default: 'spin speed [DIRECTION]',\n description: 'gets spin speed'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'axisOptions',\n defaultValue: AxisValues.Z\n }\n }\n },\n {\n opcode: 'getAcceleration',\n text: formatMessage({\n id: 'gdxfor.getAcceleration',\n default: 'acceleration [DIRECTION]',\n description: 'gets acceleration'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'axisOptions',\n defaultValue: AxisValues.X\n }\n }\n }\n ],\n menus: {\n pushPullOptions: {\n acceptReporters: true,\n items: this.PUSH_PULL_MENU\n },\n gestureOptions: {\n acceptReporters: true,\n items: this.GESTURE_MENU\n },\n axisOptions: {\n acceptReporters: true,\n items: this.AXIS_MENU\n },\n tiltOptions: {\n acceptReporters: true,\n items: this.TILT_MENU\n },\n tiltAnyOptions: {\n acceptReporters: true,\n items: this.TILT_MENU_ANY\n }\n }\n };\n }\n\n whenForcePushedOrPulled (args) {\n switch (args.PUSH_PULL) {\n case PushPullValues.PUSHED:\n return this._peripheral.getForce() < FORCE_THRESHOLD * -1;\n case PushPullValues.PULLED:\n return this._peripheral.getForce() > FORCE_THRESHOLD;\n default:\n log.warn(`unknown push/pull value in whenForcePushedOrPulled: ${args.PUSH_PULL}`);\n return false;\n }\n }\n\n getForce () {\n return Math.round(this._peripheral.getForce());\n }\n\n whenGesture (args) {\n switch (args.GESTURE) {\n case GestureValues.SHAKEN:\n return this.gestureMagnitude() > SHAKEN_THRESHOLD;\n case GestureValues.STARTED_FALLING:\n return this.isFreeFalling();\n case GestureValues.TURNED_FACE_UP:\n return this._isFacing(GestureValues.TURNED_FACE_UP);\n case GestureValues.TURNED_FACE_DOWN:\n return this._isFacing(GestureValues.TURNED_FACE_DOWN);\n default:\n log.warn(`unknown gesture value in whenGesture: ${args.GESTURE}`);\n return false;\n }\n }\n\n _isFacing (direction) {\n if (typeof this._facingUp === 'undefined') {\n this._facingUp = false;\n }\n if (typeof this._facingDown === 'undefined') {\n this._facingDown = false;\n }\n\n // If the sensor is already facing up or down, reduce the threshold.\n // This prevents small fluctations in acceleration while it is being\n // turned from causing the hat block to trigger multiple times.\n let threshold = FACING_THRESHOLD;\n if (this._facingUp || this._facingDown) {\n threshold -= FACING_THRESHOLD_OFFSET;\n }\n\n this._facingUp = this._peripheral.getAccelerationZ() > threshold;\n this._facingDown = this._peripheral.getAccelerationZ() < threshold * -1;\n\n switch (direction) {\n case GestureValues.TURNED_FACE_UP:\n return this._facingUp;\n case GestureValues.TURNED_FACE_DOWN:\n return this._facingDown;\n default:\n return false;\n }\n }\n\n whenTilted (args) {\n return this._isTilted(args.TILT);\n }\n\n isTilted (args) {\n return this._isTilted(args.TILT);\n }\n\n getTilt (args) {\n return this._getTiltAngle(args.TILT);\n }\n\n _isTilted (direction) {\n switch (direction) {\n case TiltAxisValues.ANY:\n return this._getTiltAngle(TiltAxisValues.FRONT) > TILT_THRESHOLD ||\n this._getTiltAngle(TiltAxisValues.BACK) > TILT_THRESHOLD ||\n this._getTiltAngle(TiltAxisValues.LEFT) > TILT_THRESHOLD ||\n this._getTiltAngle(TiltAxisValues.RIGHT) > TILT_THRESHOLD;\n default:\n return this._getTiltAngle(direction) > TILT_THRESHOLD;\n }\n }\n\n _getTiltAngle (direction) {\n // Tilt values are calculated using acceleration due to gravity,\n // so we need to return 0 when the peripheral is not connected.\n if (!this._peripheral.isConnected()) {\n return 0;\n }\n\n switch (direction) {\n case TiltAxisValues.FRONT:\n return Math.round(this._peripheral.getTiltFrontBack(true));\n case TiltAxisValues.BACK:\n return Math.round(this._peripheral.getTiltFrontBack(false));\n case TiltAxisValues.LEFT:\n return Math.round(this._peripheral.getTiltLeftRight(true));\n case TiltAxisValues.RIGHT:\n return Math.round(this._peripheral.getTiltLeftRight(false));\n default:\n log.warn(`Unknown direction in getTilt: ${direction}`);\n }\n }\n\n getSpinSpeed (args) {\n switch (args.DIRECTION) {\n case AxisValues.X:\n return Math.round(this._peripheral.getSpinSpeedX());\n case AxisValues.Y:\n return Math.round(this._peripheral.getSpinSpeedY());\n case AxisValues.Z:\n return Math.round(this._peripheral.getSpinSpeedZ());\n default:\n log.warn(`Unknown direction in getSpinSpeed: ${args.DIRECTION}`);\n }\n }\n\n getAcceleration (args) {\n switch (args.DIRECTION) {\n case AxisValues.X:\n return Math.round(this._peripheral.getAccelerationX());\n case AxisValues.Y:\n return Math.round(this._peripheral.getAccelerationY());\n case AxisValues.Z:\n return Math.round(this._peripheral.getAccelerationZ());\n default:\n log.warn(`Unknown direction in getAcceleration: ${args.DIRECTION}`);\n }\n }\n\n /**\n * @param {number} x - x axis vector\n * @param {number} y - y axis vector\n * @param {number} z - z axis vector\n * @return {number} - the magnitude of a three dimension vector.\n */\n magnitude (x, y, z) {\n return Math.sqrt((x * x) + (y * y) + (z * z));\n }\n\n accelMagnitude () {\n return this.magnitude(\n this._peripheral.getAccelerationX(),\n this._peripheral.getAccelerationY(),\n this._peripheral.getAccelerationZ()\n );\n }\n\n gestureMagnitude () {\n return this.accelMagnitude() - GRAVITY;\n }\n\n spinMagnitude () {\n return this.magnitude(\n this._peripheral.getSpinSpeedX(),\n this._peripheral.getSpinSpeedY(),\n this._peripheral.getSpinSpeedZ()\n );\n }\n\n isFreeFalling () {\n // When the peripheral is not connected, the acceleration magnitude\n // is 0 instead of ~9.8, which ends up calculating as a positive\n // free fall; so we need to return 'false' here to prevent returning 'true'.\n if (!this._peripheral.isConnected()) {\n return false;\n }\n\n const accelMag = this.accelMagnitude();\n const spinMag = this.spinMagnitude();\n\n // We want to account for rotation during freefall,\n // so we tack on a an estimated \"rotational effect\"\n // The FREEFALL_ROTATION_FACTOR const is used to both scale the\n // gyro measurements and convert them to radians/second.\n // So, we compare our accel magnitude against:\n // FREEFALL_THRESHOLD + (some_scaled_magnitude_of_rotation).\n const ffThresh = FREEFALL_THRESHOLD + (FREEFALL_ROTATION_FACTOR * spinMag);\n\n return accelMag < ffThresh;\n }\n}\n\nmodule.exports = Scratch3GdxForBlocks;\n","const Base64Util = require('../../util/base64-util');\n\n/**\n * Adapter class\n */\nclass ScratchLinkDeviceAdapter {\n constructor (socket, {service, commandChar, responseChar}) {\n this.socket = socket;\n\n this._service = service;\n this._commandChar = commandChar;\n this._responseChar = responseChar;\n this._onResponse = this._onResponse.bind(this);\n this._deviceOnResponse = null;\n }\n\n get godirectAdapter () {\n return true;\n }\n\n writeCommand (commandBuffer) {\n const data = Base64Util.uint8ArrayToBase64(commandBuffer);\n\n return this.socket\n .write(this._service, this._commandChar, data, 'base64');\n }\n\n setup ({onResponse}) {\n this._deviceOnResponse = onResponse;\n return this.socket\n .startNotifications(this._service, this._responseChar, this._onResponse);\n\n // TODO:\n // How do we find out from scratch link if communication closes?\n }\n\n _onResponse (base64) {\n const array = Base64Util.base64ToUint8Array(base64);\n const response = new DataView(array.buffer);\n return this._deviceOnResponse(response);\n }\n}\n\nmodule.exports = ScratchLinkDeviceAdapter;\n","const formatMessage = require('format-message');\nconst ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Cast = require('../../util/cast');\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHN0eWxlPi5zdDJ7ZmlsbDpyZWR9LnN0M3tmaWxsOiNlMGUwZTB9LnN0NHtmaWxsOm5vbmU7c3Ryb2tlOiM2NjY7c3Ryb2tlLXdpZHRoOi41O3N0cm9rZS1taXRlcmxpbWl0OjEwfTwvc3R5bGU+PHBhdGggZD0iTTM1IDI4SDVhMSAxIDAgMCAxLTEtMVYxMmMwLS42LjQtMSAxLTFoMzBjLjUgMCAxIC40IDEgMXYxNWMwIC41LS41IDEtMSAxeiIgZmlsbD0iI2ZmZiIgaWQ9IkxheWVyXzYiLz48ZyBpZD0iTGF5ZXJfNCI+PHBhdGggY2xhc3M9InN0MiIgZD0iTTQgMjVoMzJ2Mi43SDR6TTEzIDI0aC0yLjJhMSAxIDAgMCAxLTEtMXYtOS43YzAtLjYuNC0xIDEtMUgxM2MuNiAwIDEgLjQgMSAxVjIzYzAgLjYtLjUgMS0xIDF6Ii8+PHBhdGggY2xhc3M9InN0MiIgZD0iTTYuMSAxOS4zdi0yLjJjMC0uNS40LTEgMS0xaDkuN2MuNSAwIDEgLjUgMSAxdjIuMmMwIC41LS41IDEtMSAxSDcuMWExIDEgMCAwIDEtMS0xeiIvPjxjaXJjbGUgY2xhc3M9InN0MiIgY3g9IjIyLjgiIGN5PSIxOC4yIiByPSIzLjQiLz48Y2lyY2xlIGNsYXNzPSJzdDIiIGN4PSIzMC42IiBjeT0iMTguMiIgcj0iMy40Ii8+PHBhdGggY2xhc3M9InN0MiIgZD0iTTQuMiAyN2gzMS45di43SDQuMnoiLz48L2c+PGcgaWQ9IkxheWVyXzUiPjxjaXJjbGUgY2xhc3M9InN0MyIgY3g9IjIyLjgiIGN5PSIxOC4yIiByPSIyLjMiLz48Y2lyY2xlIGNsYXNzPSJzdDMiIGN4PSIzMC42IiBjeT0iMTguMiIgcj0iMi4zIi8+PHBhdGggY2xhc3M9InN0MyIgZD0iTTEyLjUgMjIuOWgtMS4yYy0uMyAwLS41LS4yLS41LS41VjE0YzAtLjMuMi0uNS41LS41aDEuMmMuMyAwIC41LjIuNS41djguNGMwIC4zLS4yLjUtLjUuNXoiLz48cGF0aCBjbGFzcz0ic3QzIiBkPSJNNy4yIDE4Ljd2LTEuMmMwLS4zLjItLjUuNS0uNWg4LjRjLjMgMCAuNS4yLjUuNXYxLjJjMCAuMy0uMi41LS41LjVINy43Yy0uMyAwLS41LS4yLS41LS41ek00IDI2aDMydjJINHoiLz48L2c+PGcgaWQ9IkxheWVyXzMiPjxwYXRoIGNsYXNzPSJzdDQiIGQ9Ik0zNS4yIDI3LjlINC44YTEgMSAwIDAgMS0xLTFWMTIuMWMwLS42LjUtMSAxLTFoMzAuNWMuNSAwIDEgLjQgMSAxVjI3YTEgMSAwIDAgMS0xLjEuOXoiLz48cGF0aCBjbGFzcz0ic3Q0IiBkPSJNMzUuMiAyNy45SDQuOGExIDEgMCAwIDEtMS0xVjEyLjFjMC0uNi41LTEgMS0xaDMwLjVjLjUgMCAxIC40IDEgMVYyN2ExIDEgMCAwIDEtMS4xLjl6Ii8+PC9nPjwvc3ZnPg==';\n\n/**\n * Length of the buffer to store key presses for the \"when keys pressed in order\" hat\n * @type {number}\n */\nconst KEY_BUFFER_LENGTH = 100;\n\n/**\n * Timeout in milliseconds to reset the completed flag for a sequence.\n * @type {number}\n */\nconst SEQUENCE_HAT_TIMEOUT = 100;\n\n/**\n * An id for the space key on a keyboard.\n */\nconst KEY_ID_SPACE = 'SPACE';\n\n/**\n * An id for the left arrow key on a keyboard.\n */\nconst KEY_ID_LEFT = 'LEFT';\n\n/**\n * An id for the right arrow key on a keyboard.\n */\nconst KEY_ID_RIGHT = 'RIGHT';\n\n/**\n * An id for the up arrow key on a keyboard.\n */\nconst KEY_ID_UP = 'UP';\n\n/**\n * An id for the down arrow key on a keyboard.\n */\nconst KEY_ID_DOWN = 'DOWN';\n\n/**\n * Names used by keyboard io for keys used in scratch.\n * @enum {string}\n */\nconst SCRATCH_KEY_NAME = {\n [KEY_ID_SPACE]: 'space',\n [KEY_ID_LEFT]: 'left arrow',\n [KEY_ID_UP]: 'up arrow',\n [KEY_ID_RIGHT]: 'right arrow',\n [KEY_ID_DOWN]: 'down arrow'\n};\n\n/**\n * Class for the makey makey blocks in Scratch 3.0\n * @constructor\n */\nclass Scratch3MakeyMakeyBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n /**\n * A toggle that alternates true and false each frame, so that an\n * edge-triggered hat can trigger on every other frame.\n * @type {boolean}\n */\n this.frameToggle = false;\n\n // Set an interval that toggles the frameToggle every frame.\n setInterval(() => {\n this.frameToggle = !this.frameToggle;\n }, this.runtime.currentStepTime);\n\n this.keyPressed = this.keyPressed.bind(this);\n this.runtime.on('KEY_PRESSED', this.keyPressed);\n\n this._clearkeyPressBuffer = this._clearkeyPressBuffer.bind(this);\n this.runtime.on('PROJECT_STOP_ALL', this._clearkeyPressBuffer);\n\n /*\n * An object containing a set of sequence objects.\n * These are the key sequences currently being detected by the \"when\n * keys pressed in order\" hat block. Each sequence is keyed by its\n * string representation (the sequence's value in the menu, which is a\n * string of KEY_IDs separated by spaces). Each sequence object\n * has an array property (an array of KEY_IDs) and a boolean\n * completed property that is true when the sequence has just been\n * pressed.\n * @type {object}\n */\n this.sequences = {};\n\n /*\n * An array of the key codes of recently pressed keys.\n * @type {array}\n */\n this.keyPressBuffer = [];\n }\n\n /*\n * Localized short-form names of the space bar and arrow keys, for use in the\n * displayed menu items of the \"when keys pressed in order\" block.\n * @type {object}\n */\n get KEY_TEXT_SHORT () {\n return {\n [KEY_ID_SPACE]: formatMessage({\n id: 'makeymakey.spaceKey',\n default: 'space',\n description: 'The space key on a computer keyboard.'\n }),\n [KEY_ID_LEFT]: formatMessage({\n id: 'makeymakey.leftArrowShort',\n default: 'left',\n description: 'Short name for the left arrow key on a computer keyboard.'\n }),\n [KEY_ID_UP]: formatMessage({\n id: 'makeymakey.upArrowShort',\n default: 'up',\n description: 'Short name for the up arrow key on a computer keyboard.'\n }),\n [KEY_ID_RIGHT]: formatMessage({\n id: 'makeymakey.rightArrowShort',\n default: 'right',\n description: 'Short name for the right arrow key on a computer keyboard.'\n }),\n [KEY_ID_DOWN]: formatMessage({\n id: 'makeymakey.downArrowShort',\n default: 'down',\n description: 'Short name for the down arrow key on a computer keyboard.'\n })\n };\n }\n\n /*\n * An array of strings of KEY_IDs representing the default set of\n * key sequences for use by the \"when keys pressed in order\" block.\n * @type {array}\n */\n get DEFAULT_SEQUENCES () {\n return [\n `${KEY_ID_LEFT} ${KEY_ID_UP} ${KEY_ID_RIGHT}`,\n `${KEY_ID_RIGHT} ${KEY_ID_UP} ${KEY_ID_LEFT}`,\n `${KEY_ID_LEFT} ${KEY_ID_RIGHT}`,\n `${KEY_ID_RIGHT} ${KEY_ID_LEFT}`,\n `${KEY_ID_UP} ${KEY_ID_DOWN}`,\n `${KEY_ID_DOWN} ${KEY_ID_UP}`,\n `${KEY_ID_UP} ${KEY_ID_RIGHT} ${KEY_ID_DOWN} ${KEY_ID_LEFT}`,\n `${KEY_ID_UP} ${KEY_ID_LEFT} ${KEY_ID_DOWN} ${KEY_ID_RIGHT}`,\n `${KEY_ID_UP} ${KEY_ID_UP} ${KEY_ID_DOWN} ${KEY_ID_DOWN} ` +\n `${KEY_ID_LEFT} ${KEY_ID_RIGHT} ${KEY_ID_LEFT} ${KEY_ID_RIGHT}`\n ];\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: 'makeymakey',\n name: 'Makey Makey',\n blockIconURI: blockIconURI,\n blocks: [\n {\n opcode: 'whenMakeyKeyPressed',\n text: formatMessage({\n id: 'makeymakey.whenKeyPressed',\n default: 'when [KEY] key pressed',\n description: 'when a keyboard key is pressed'\n }),\n blockType: BlockType.HAT,\n arguments: {\n KEY: {\n type: ArgumentType.STRING,\n menu: 'KEY',\n defaultValue: KEY_ID_SPACE\n }\n }\n },\n {\n opcode: 'whenCodePressed',\n text: formatMessage({\n id: 'makeymakey.whenKeysPressedInOrder',\n default: 'when [SEQUENCE] pressed in order',\n description: 'when a sequence of keyboard keys is pressed in a specific order'\n }),\n blockType: BlockType.HAT,\n arguments: {\n SEQUENCE: {\n type: ArgumentType.STRING,\n menu: 'SEQUENCE',\n defaultValue: this.DEFAULT_SEQUENCES[0]\n }\n }\n }\n ],\n menus: {\n KEY: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'makeymakey.spaceKey',\n default: 'space',\n description: 'The space key on a computer keyboard.'\n }),\n value: KEY_ID_SPACE\n },\n {\n text: formatMessage({\n id: 'makeymakey.upArrow',\n default: 'up arrow',\n description: 'The up arrow key on a computer keyboard.'\n }),\n value: KEY_ID_UP\n },\n {\n text: formatMessage({\n id: 'makeymakey.downArrow',\n default: 'down arrow',\n description: 'The down arrow key on a computer keyboard.'\n }),\n value: KEY_ID_DOWN\n },\n {\n text: formatMessage({\n id: 'makeymakey.rightArrow',\n default: 'right arrow',\n description: 'The right arrow key on a computer keyboard.'\n }),\n value: KEY_ID_RIGHT\n },\n {\n text: formatMessage({\n id: 'makeymakey.leftArrow',\n default: 'left arrow',\n description: 'The left arrow key on a computer keyboard.'\n }),\n value: KEY_ID_LEFT\n },\n {text: 'w', value: 'w'},\n {text: 'a', value: 'a'},\n {text: 's', value: 's'},\n {text: 'd', value: 'd'},\n {text: 'f', value: 'f'},\n {text: 'g', value: 'g'}\n ]\n },\n SEQUENCE: {\n acceptReporters: true,\n items: this.buildSequenceMenu(this.DEFAULT_SEQUENCES)\n }\n }\n };\n }\n\n /*\n * Build the menu of key sequences.\n * @param {array} sequencesArray an array of strings of KEY_IDs.\n * @returns {array} an array of objects with text and value properties.\n */\n buildSequenceMenu (sequencesArray) {\n return sequencesArray.map(\n str => this.getMenuItemForSequenceString(str)\n );\n }\n\n /*\n * Create a menu item for a sequence string.\n * @param {string} sequenceString a string of KEY_IDs.\n * @return {object} an object with text and value properties.\n */\n getMenuItemForSequenceString (sequenceString) {\n let sequenceArray = sequenceString.split(' ');\n sequenceArray = sequenceArray.map(str => this.KEY_TEXT_SHORT[str]);\n return {\n text: sequenceArray.join(' '),\n value: sequenceString\n };\n }\n\n /*\n * Check whether a keyboard key is currently pressed.\n * Also, toggle the results of the test on alternate frames, so that the\n * hat block fires repeatedly.\n * @param {object} args - the block arguments.\n * @property {number} KEY - a key code.\n * @param {object} util - utility object provided by the runtime.\n */\n whenMakeyKeyPressed (args, util) {\n let key = args.KEY;\n // Convert the key arg, if it is a KEY_ID, to the key name used by\n // the Keyboard io module.\n if (SCRATCH_KEY_NAME[args.KEY]) {\n key = SCRATCH_KEY_NAME[args.KEY];\n }\n const isDown = util.ioQuery('keyboard', 'getKeyIsDown', [key]);\n return (isDown && this.frameToggle);\n }\n\n /*\n * A function called on the KEY_PRESSED event, to update the key press\n * buffer and check if any of the key sequences have been completed.\n * @param {string} key A scratch key name.\n */\n keyPressed (key) {\n // Store only the first word of the Scratch key name, so that e.g. when\n // \"left arrow\" is pressed, we store \"LEFT\", which matches KEY_ID_LEFT\n key = key.split(' ')[0];\n key = key.toUpperCase();\n this.keyPressBuffer.push(key);\n // Keep the buffer under the length limit\n if (this.keyPressBuffer.length > KEY_BUFFER_LENGTH) {\n this.keyPressBuffer.shift();\n }\n // Check the buffer for each sequence in use\n for (const str in this.sequences) {\n const arr = this.sequences[str].array;\n // Bail out if we don't have enough presses for this sequence\n if (this.keyPressBuffer.length < arr.length) {\n continue;\n }\n let missFlag = false;\n // Slice the buffer to the length of the sequence we're checking\n const bufferSegment = this.keyPressBuffer.slice(-1 * arr.length);\n for (let i = 0; i < arr.length; i++) {\n if (arr[i] !== bufferSegment[i]) {\n missFlag = true;\n }\n }\n // If the miss flag is false, the sequence matched the buffer\n if (!missFlag) {\n this.sequences[str].completed = true;\n // Clear the completed flag after a timeout. This is necessary because\n // the hat is edge-triggered (not event triggered). Multiple hats\n // may be checking the same sequence, so this timeout gives them enough\n // time to all trigger before resetting the flag.\n setTimeout(() => {\n this.sequences[str].completed = false;\n }, SEQUENCE_HAT_TIMEOUT);\n }\n }\n }\n\n /**\n * Clear the key press buffer.\n */\n _clearkeyPressBuffer () {\n this.keyPressBuffer = [];\n }\n\n /*\n * Add a key sequence to the set currently being checked on each key press.\n * @param {string} sequenceString a string of space-separated KEY_IDs.\n * @param {array} sequenceArray an array of KEY_IDs.\n */\n addSequence (sequenceString, sequenceArray) {\n // If we already have this sequence string, return.\n if (this.sequences.hasOwnProperty(sequenceString)) {\n return;\n }\n this.sequences[sequenceString] = {\n array: sequenceArray,\n completed: false\n };\n }\n\n /*\n * Check whether a key sequence was recently completed.\n * @param {object} args The block arguments.\n * @property {number} SEQUENCE A string of KEY_IDs.\n */\n whenCodePressed (args) {\n const sequenceString = Cast.toString(args.SEQUENCE).toUpperCase();\n const sequenceArray = sequenceString.split(' ');\n if (sequenceArray.length < 2) {\n return;\n }\n this.addSequence(sequenceString, sequenceArray);\n\n return this.sequences[sequenceString].completed;\n }\n}\nmodule.exports = Scratch3MakeyMakeyBlocks;\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst log = require('../../util/log');\nconst cast = require('../../util/cast');\nconst formatMessage = require('format-message');\nconst BLE = require('../../io/ble');\nconst Base64Util = require('../../util/base64-util');\n\n/**\n * Icon png to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAACXBIWXMAABYlAAAWJQFJUiTwAAAKcElEQVR42u2cfXAU9RnHv7u3L3d7l9yR5PIGXO7MkQKaYiCUWqJhFGvRMk4JZXSc8aXVaSmiYlthVHQEW99FxiIdrVY6teiMdoa+ICqhIqgQAsjwMgYDOQKXl7uY17u9293b3f5x5JKYe8+FJGSfvzbP/n77e/azz+95nt9v90KoqgpN0hdSQ6AB1ABqADWAmmgANYAaQA2gJhpADeBEE2q8GPLaWzu/CslyiY4k9dOn5uijtXGd7+jWkaReVpT3Hrhv6d0awEFC07rgD+ZeYYnXprhwigUAvjj0zbjxQCLebozT7iDzK1ZUWCru2K7L//6MVC8ue45Blz8n6rlQ815QtuohOlXiEdy/AUqPa6y59Mkh6Q1345GNja6m7pHEQKNl3t0704EXat4L6fSOmOeEI1vHKzwAyNJR9MPFpRUPOu0ONm2A0xatWaTLm5WfDrzvAppA8AbiG03fC8CQNkDKZK2YrPAuRrhpifJERsuYywveJc7CqcIDMAyeLm82dEXzw39I/qjXkpr3QuW9lxfAdOABGAKPslWDnbsy7Jl8BxTeM3SqmO0gaA5U6c3jymup0YSn9JyLee67wpTfBQAQjmyF3HFqiJcRtDECjy5dAmbmcgQPvjjxl3Lx4IVjnD/5cE1zkWtyP34VBGcdKLJnLgc9cznk1kMXFdzEn8KJ4KUqqsSHvcxWDf7j1UM8UPr6/YgHhhX8xAaYaXgAIB7fBnbuSrBzV8aNgarEQ/z6/YkLcDTg9V9XlXjQtuqoU1TpcUHlvZDOfDiuyh5qPMCLrJ1bDw3EuUtx81N/BH3pjQBJQ2HMF5V6iKfeRchVm9kkMtrwxmSdobeA9daBde8GwVlBcFYofS1Jw0vaAy9HeJHQwBUPzIBvGxDc92Rmp/BowJs10wkAONfsBs8HAAAltqngOAO8HZ3o6OiMqcvLy4E1Lwc8H8C5ZndMXdLJa/qNacNLCDBw/O8nFUNWxp/64+tWAwBefe1tHKg7CgC4/9d3ori4EHv3HcDrb26PqVt2602ovvaHaGlpw+8ffSamLqXYmya8jG8mpFy6iGLkWLh4HAwG4+r6j4VBfaPpLgU8IMGO9MLqW2pYQ9aQokuR5dgXIwCC1CUcNMj3hpdvLAdSF54EYpCHooRA0Swomo2pC0kCQpIAkqTA6LmYupgxL0X7m78+aG10NXVkpIwxsAwWXncDCESHLkohfPbpbiT6ZFPPZQ9fC0e58Wi6wTDj6UbT/rQAyiERS2pW4Kc3LQDLRO8miCEAKj7d83FcTxyLJJJJ+9MCqKoq9HomMrgkSThxsgEcZ8AMpwMkSYJlKDA0DVUFiHGWRDJp/4jXwqIo4uFHnkZXdw8AYGbZFXhs3WqQJDkhkkim7E8KoMlkxKbnn8DBunrwUli3e8/+yOAA0HjmHDq7upGXm5PUoDUr7hmWRB5Zt3FYwoime+vtd/H6G9uGJIxouniSyP6H7v8FystnY80jGzIA0MihsMAKu20aTp3JzFb6WCWRuDUvHwByw8cOhw2FBVaYjNzIAba1e3Hfb9aiq7MTNStuBwAsvr4KO3d9GnmKztIS5EyxTJiVSDT7p04tipx/9MnnYc7ORlu7NzMxsK3di5AkDHgGw2DTC+uHBeGJshJJZL/fxyMQEDKbRAiCQDAoQhBDYBkKNE2j4uqrhpUBoiSBIMZfEhkN+1NeiWSqEB2rlUg69md0JRIQRHy86z8jXsqNVRLJlP0jqgNJXXgAgjbCcONmCHUvQ+44NWG2s/rtH5Mt/ciToo0wLH4JBGO6LLazRiJk2vBYy4gHHw/bWSN+LZBKEhkMjzn/CaSiKgQOvJDyFB7L7axUJWNJZDA8IhQA1boPin7KZbMSGfUYyFx9b3hXg/cCsoBA2Z0AoYOaxlcC4+mdyCUDKBzanLFBJ3USyaRMuiSSKZmUSSSTMimTCABUlblRU9kAZ0E39p+eii21c+EL0jHbOwu6sfaWgyjND//U4oP6MmzZnfi79XT7mfQSNi7bh0JzOLG19XBY/89r49pYVebGqhuOosDsh1+gsWV3BXYdd2Q+BlaVuXFv9bHgkSbzk+vfcVRyjHhi47J9cftsXLYf7T36Ix8cLHlo6ydlv6qpPI2qssRZcuOy/Wjp4k5s+2zG+offKqtcUt6kJtNv7S0H0RtkvEufXTB/6bML5je2Wy7UVDbEbF9o9mPDsv2oP5v75vbPS26rP5u3fdXiozDppcwDrKlswOlWy9E//DX09Mt/azh8zzNM1RybF86C7pheVGD240CDeX3NWtfml94Rt+0+Mf3Lm8qbEnpfgdmPs+3G9+564vTT//pM/GrHYduWRP0AYOEMN/5S61xT92Vtfd2XtfWb/vu91fHALyxzw9tnkB/cTD5w+2Ou9375HHtfa7exM5mxRpKFaafdQQKgAcDERs98/foLHrXdaXfoABi8vczhWO2/28/TRR5z2h00gKymNl1ton79oigq6bQ7dE67Q+ew9mb1h4FYYwVESgLAXLSRa+3mWpIdK+UYuPiq89f8+XfT/+ftZQ4vLm9ZmUyfdcsv1M2fWfRaUCK8i8vdK1u6ktuAWPWTsztm24o/cnnYHUsrWzd1+fVJ9XtqxbG3XzFdNcPTawjcueibpxK1t+X26f/9R8a953jub4typOvm2b1XnvUmv8JKWMZcaZffX3XDERRP8cGaFRjWxtPLoZvXY4oxgPBNEsgxBhCUKEzL6Ru+JydS8Ak0giKFgESDJFQoKmCgQzAwIfQEWETzmoBIwd2VNaStu8uEHGO4Buz06zHHFv0dRkefAZ1+PQx0KNK2eIoPLCUj2zDc275qzgcBFWv+cf3IyxgTK2KOzQufEM5kfpGF12eGPSf8DXN+No/87HDWiwYYALw+M6ym8AscAxO++X7xCTRM7EDQzht0Da8v/NWo1dQDAxNCocUXs+303IGHdaptOmYXnh/SLlZbV+fwnwJm6UXEm/ojqgM/PFmJQ81OPHfrtqT7bN23BE8seTflYLvz5DwYGQHLKz5Puo/XZ8aLtT+D1dSDuxbsGQIymmz48DbwIguOESJOcce8XaO3oVpZ8k3Em5KVVAAMFnuOB9as1MbimCBunn04vBmR40ls29Wfgxf1KMn1gBdY+MXUCvK4ANvPndpLzrLzALjBN2VPwrDBksgLYkn1jBMp90nVY2++8vAw3RlPeLNYVZSPAEgjKWP6ZCn4lF+gMdnE08spQb73RQB9aXtgo6tJcNodf8rWz3L//Br340UW3sExEkXrFFKSSUVHqkRfkJZ8QSZk5gS6hw9H+GyDQAclSs41BVmSUIn+toAKIUTJskKoQUknCxKlkISKb/sM0NMyyVAhXW+AlYosfgOgQlUJVadTSUWBKoQoudvPioPbenq5oIUTaRUqenhWKi3oyVIUqKpKREoLggDhF6hQb4CV9LRM9rctMPN6glChp2SdTqeSskwoAECSKnG61fzFR/XsGu+FhmONriYl7TImsjoYKJyZSeB8CoBQo6spqU8TCO1fgE7gDVUNoCYaQA2gBlADqAHURAOoAdQAagA10QCOgfwfNp/hXbfBMCAAAAAASUVORK5CYII=';\n\n/**\n * Enum for micro:bit BLE command protocol.\n * https://github.com/LLK/scratch-microbit-firmware/blob/master/protocol.md\n * @readonly\n * @enum {number}\n */\nconst BLECommand = {\n CMD_PIN_CONFIG: 0x80,\n CMD_DISPLAY_TEXT: 0x81,\n CMD_DISPLAY_LED: 0x82\n};\n\n\n/**\n * A time interval to wait (in milliseconds) before reporting to the BLE socket\n * that data has stopped coming from the peripheral.\n */\nconst BLETimeout = 4500;\n\n/**\n * A time interval to wait (in milliseconds) while a block that sends a BLE message is running.\n * @type {number}\n */\nconst BLESendInterval = 100;\n\n/**\n * A string to report to the BLE socket when the micro:bit has stopped receiving data.\n * @type {string}\n */\nconst BLEDataStoppedError = 'micro:bit extension stopped receiving data';\n\n/**\n * Enum for micro:bit protocol.\n * https://github.com/LLK/scratch-microbit-firmware/blob/master/protocol.md\n * @readonly\n * @enum {string}\n */\nconst BLEUUID = {\n service: 0xf005,\n rxChar: '5261da01-fa7e-42ab-850b-7c80220097cc',\n txChar: '5261da02-fa7e-42ab-850b-7c80220097cc'\n};\n\n/**\n * Manage communication with a MicroBit peripheral over a Scrath Link client socket.\n */\nclass MicroBit {\n\n /**\n * Construct a MicroBit communication object.\n * @param {Runtime} runtime - the Scratch 3.0 runtime\n * @param {string} extensionId - the id of the extension\n */\n constructor (runtime, extensionId) {\n\n /**\n * The Scratch 3.0 runtime used to trigger the green flag button.\n * @type {Runtime}\n * @private\n */\n this._runtime = runtime;\n\n /**\n * The BluetoothLowEnergy connection socket for reading/writing peripheral data.\n * @type {BLE}\n * @private\n */\n this._ble = null;\n this._runtime.registerPeripheralExtension(extensionId, this);\n\n /**\n * The id of the extension this peripheral belongs to.\n */\n this._extensionId = extensionId;\n\n /**\n * The most recently received value for each sensor.\n * @type {Object.}\n * @private\n */\n this._sensors = {\n tiltX: 0,\n tiltY: 0,\n buttonA: 0,\n buttonB: 0,\n touchPins: [0, 0, 0],\n gestureState: 0,\n ledMatrixState: new Uint8Array(5)\n };\n\n /**\n * The most recently received value for each gesture.\n * @type {Object.}\n * @private\n */\n this._gestures = {\n moving: false,\n move: {\n active: false,\n timeout: false\n },\n shake: {\n active: false,\n timeout: false\n },\n jump: {\n active: false,\n timeout: false\n }\n };\n\n /**\n * Interval ID for data reading timeout.\n * @type {number}\n * @private\n */\n this._timeoutID = null;\n\n /**\n * A flag that is true while we are busy sending data to the BLE socket.\n * @type {boolean}\n * @private\n */\n this._busy = false;\n\n /**\n * ID for a timeout which is used to clear the busy flag if it has been\n * true for a long time.\n */\n this._busyTimeoutID = null;\n\n this.reset = this.reset.bind(this);\n this._onConnect = this._onConnect.bind(this);\n this._onMessage = this._onMessage.bind(this);\n }\n\n /**\n * @param {string} text - the text to display.\n * @return {Promise} - a Promise that resolves when writing to peripheral.\n */\n displayText (text) {\n const output = new Uint8Array(text.length);\n for (let i = 0; i < text.length; i++) {\n output[i] = text.charCodeAt(i);\n }\n return this.send(BLECommand.CMD_DISPLAY_TEXT, output);\n }\n\n /**\n * @param {Uint8Array} matrix - the matrix to display.\n * @return {Promise} - a Promise that resolves when writing to peripheral.\n */\n displayMatrix (matrix) {\n return this.send(BLECommand.CMD_DISPLAY_LED, matrix);\n }\n\n /**\n * @return {number} - the latest value received for the tilt sensor's tilt about the X axis.\n */\n get tiltX () {\n return this._sensors.tiltX;\n }\n\n /**\n * @return {number} - the latest value received for the tilt sensor's tilt about the Y axis.\n */\n get tiltY () {\n return this._sensors.tiltY;\n }\n\n /**\n * @return {boolean} - the latest value received for the A button.\n */\n get buttonA () {\n return this._sensors.buttonA;\n }\n\n /**\n * @return {boolean} - the latest value received for the B button.\n */\n get buttonB () {\n return this._sensors.buttonB;\n }\n\n /**\n * @return {number} - the latest value received for the motion gesture states.\n */\n get gestureState () {\n return this._sensors.gestureState;\n }\n\n /**\n * @return {Uint8Array} - the current state of the 5x5 LED matrix.\n */\n get ledMatrixState () {\n return this._sensors.ledMatrixState;\n }\n\n /**\n * Called by the runtime when user wants to scan for a peripheral.\n */\n scan () {\n if (this._ble) {\n this._ble.disconnect();\n }\n this._ble = new BLE(this._runtime, this._extensionId, {\n filters: [\n {services: [BLEUUID.service]}\n ]\n }, this._onConnect, this.reset);\n }\n\n /**\n * Called by the runtime when user wants to connect to a certain peripheral.\n * @param {number} id - the id of the peripheral to connect to.\n */\n connect (id) {\n if (this._ble) {\n this._ble.connectPeripheral(id);\n }\n }\n\n /**\n * Disconnect from the micro:bit.\n */\n disconnect () {\n if (this._ble) {\n this._ble.disconnect();\n }\n\n this.reset();\n }\n\n /**\n * Reset all the state and timeout/interval ids.\n */\n reset () {\n if (this._timeoutID) {\n window.clearTimeout(this._timeoutID);\n this._timeoutID = null;\n }\n }\n\n /**\n * Return true if connected to the micro:bit.\n * @return {boolean} - whether the micro:bit is connected.\n */\n isConnected () {\n let connected = false;\n if (this._ble) {\n connected = this._ble.isConnected();\n }\n return connected;\n }\n\n /**\n * Send a message to the peripheral BLE socket.\n * @param {number} command - the BLE command hex.\n * @param {Uint8Array} message - the message to write\n */\n send (command, message) {\n if (!this.isConnected()) return;\n if (this._busy) return;\n\n // Set a busy flag so that while we are sending a message and waiting for\n // the response, additional messages are ignored.\n this._busy = true;\n\n // Set a timeout after which to reset the busy flag. This is used in case\n // a BLE message was sent for which we never received a response, because\n // e.g. the peripheral was turned off after the message was sent. We reset\n // the busy flag after a while so that it is possible to try again later.\n this._busyTimeoutID = window.setTimeout(() => {\n this._busy = false;\n }, 5000);\n\n const output = new Uint8Array(message.length + 1);\n output[0] = command; // attach command to beginning of message\n for (let i = 0; i < message.length; i++) {\n output[i + 1] = message[i];\n }\n const data = Base64Util.uint8ArrayToBase64(output);\n\n this._ble.write(BLEUUID.service, BLEUUID.txChar, data, 'base64', true).then(\n () => {\n this._busy = false;\n window.clearTimeout(this._busyTimeoutID);\n }\n );\n }\n\n /**\n * Starts reading data from peripheral after BLE has connected to it.\n * @private\n */\n _onConnect () {\n this._ble.read(BLEUUID.service, BLEUUID.rxChar, true, this._onMessage);\n this._timeoutID = window.setTimeout(\n () => this._ble.handleDisconnectError(BLEDataStoppedError),\n BLETimeout\n );\n }\n\n /**\n * Process the sensor data from the incoming BLE characteristic.\n * @param {object} base64 - the incoming BLE data.\n * @private\n */\n _onMessage (base64) {\n // parse data\n const data = Base64Util.base64ToUint8Array(base64);\n\n this._sensors.tiltX = data[1] | (data[0] << 8);\n if (this._sensors.tiltX > (1 << 15)) this._sensors.tiltX -= (1 << 16);\n this._sensors.tiltY = data[3] | (data[2] << 8);\n if (this._sensors.tiltY > (1 << 15)) this._sensors.tiltY -= (1 << 16);\n\n this._sensors.buttonA = data[4];\n this._sensors.buttonB = data[5];\n\n this._sensors.touchPins[0] = data[6];\n this._sensors.touchPins[1] = data[7];\n this._sensors.touchPins[2] = data[8];\n\n this._sensors.gestureState = data[9];\n\n // cancel disconnect timeout and start a new one\n window.clearTimeout(this._timeoutID);\n this._timeoutID = window.setTimeout(\n () => this._ble.handleDisconnectError(BLEDataStoppedError),\n BLETimeout\n );\n }\n\n /**\n * @param {number} pin - the pin to check touch state.\n * @return {number} - the latest value received for the touch pin states.\n * @private\n */\n _checkPinState (pin) {\n return this._sensors.touchPins[pin];\n }\n}\n\n/**\n * Enum for tilt sensor direction.\n * @readonly\n * @enum {string}\n */\nconst MicroBitTiltDirection = {\n FRONT: 'front',\n BACK: 'back',\n LEFT: 'left',\n RIGHT: 'right',\n ANY: 'any'\n};\n\n/**\n * Enum for micro:bit gestures.\n * @readonly\n * @enum {string}\n */\nconst MicroBitGestures = {\n MOVED: 'moved',\n SHAKEN: 'shaken',\n JUMPED: 'jumped'\n};\n\n/**\n * Enum for micro:bit buttons.\n * @readonly\n * @enum {string}\n */\nconst MicroBitButtons = {\n A: 'A',\n B: 'B',\n ANY: 'any'\n};\n\n/**\n * Enum for micro:bit pin states.\n * @readonly\n * @enum {string}\n */\nconst MicroBitPinState = {\n ON: 'on',\n OFF: 'off'\n};\n\n/**\n * Scratch 3.0 blocks to interact with a MicroBit peripheral.\n */\nclass Scratch3MicroBitBlocks {\n\n /**\n * @return {string} - the name of this extension.\n */\n static get EXTENSION_NAME () {\n return 'micro:bit';\n }\n\n /**\n * @return {string} - the ID of this extension.\n */\n static get EXTENSION_ID () {\n return 'microbit';\n }\n\n /**\n * @return {number} - the tilt sensor counts as \"tilted\" if its tilt angle meets or exceeds this threshold.\n */\n static get TILT_THRESHOLD () {\n return 15;\n }\n\n /**\n * @return {array} - text and values for each buttons menu element\n */\n get BUTTONS_MENU () {\n return [\n {\n text: 'A',\n value: MicroBitButtons.A\n },\n {\n text: 'B',\n value: MicroBitButtons.B\n },\n {\n text: formatMessage({\n id: 'microbit.buttonsMenu.any',\n default: 'any',\n description: 'label for \"any\" element in button picker for micro:bit extension'\n }),\n value: MicroBitButtons.ANY\n }\n ];\n }\n\n /**\n * @return {array} - text and values for each gestures menu element\n */\n get GESTURES_MENU () {\n return [\n {\n text: formatMessage({\n id: 'microbit.gesturesMenu.moved',\n default: 'moved',\n description: 'label for moved gesture in gesture picker for micro:bit extension'\n }),\n value: MicroBitGestures.MOVED\n },\n {\n text: formatMessage({\n id: 'microbit.gesturesMenu.shaken',\n default: 'shaken',\n description: 'label for shaken gesture in gesture picker for micro:bit extension'\n }),\n value: MicroBitGestures.SHAKEN\n },\n {\n text: formatMessage({\n id: 'microbit.gesturesMenu.jumped',\n default: 'jumped',\n description: 'label for jumped gesture in gesture picker for micro:bit extension'\n }),\n value: MicroBitGestures.JUMPED\n }\n ];\n }\n\n /**\n * @return {array} - text and values for each pin state menu element\n */\n get PIN_STATE_MENU () {\n return [\n {\n text: formatMessage({\n id: 'microbit.pinStateMenu.on',\n default: 'on',\n description: 'label for on element in pin state picker for micro:bit extension'\n }),\n value: MicroBitPinState.ON\n },\n {\n text: formatMessage({\n id: 'microbit.pinStateMenu.off',\n default: 'off',\n description: 'label for off element in pin state picker for micro:bit extension'\n }),\n value: MicroBitPinState.OFF\n }\n ];\n }\n\n /**\n * @return {array} - text and values for each tilt direction menu element\n */\n get TILT_DIRECTION_MENU () {\n return [\n {\n text: formatMessage({\n id: 'microbit.tiltDirectionMenu.front',\n default: 'front',\n description: 'label for front element in tilt direction picker for micro:bit extension'\n }),\n value: MicroBitTiltDirection.FRONT\n },\n {\n text: formatMessage({\n id: 'microbit.tiltDirectionMenu.back',\n default: 'back',\n description: 'label for back element in tilt direction picker for micro:bit extension'\n }),\n value: MicroBitTiltDirection.BACK\n },\n {\n text: formatMessage({\n id: 'microbit.tiltDirectionMenu.left',\n default: 'left',\n description: 'label for left element in tilt direction picker for micro:bit extension'\n }),\n value: MicroBitTiltDirection.LEFT\n },\n {\n text: formatMessage({\n id: 'microbit.tiltDirectionMenu.right',\n default: 'right',\n description: 'label for right element in tilt direction picker for micro:bit extension'\n }),\n value: MicroBitTiltDirection.RIGHT\n }\n ];\n }\n\n /**\n * @return {array} - text and values for each tilt direction (plus \"any\") menu element\n */\n get TILT_DIRECTION_ANY_MENU () {\n return [\n ...this.TILT_DIRECTION_MENU,\n {\n text: formatMessage({\n id: 'microbit.tiltDirectionMenu.any',\n default: 'any',\n description: 'label for any direction element in tilt direction picker for micro:bit extension'\n }),\n value: MicroBitTiltDirection.ANY\n }\n ];\n }\n\n /**\n * Construct a set of MicroBit blocks.\n * @param {Runtime} runtime - the Scratch 3.0 runtime.\n */\n constructor (runtime) {\n /**\n * The Scratch 3.0 runtime.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n // Create a new MicroBit peripheral instance\n this._peripheral = new MicroBit(this.runtime, Scratch3MicroBitBlocks.EXTENSION_ID);\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: Scratch3MicroBitBlocks.EXTENSION_ID,\n name: Scratch3MicroBitBlocks.EXTENSION_NAME,\n blockIconURI: blockIconURI,\n showStatusButton: true,\n blocks: [\n {\n opcode: 'whenButtonPressed',\n text: formatMessage({\n id: 'microbit.whenButtonPressed',\n default: 'when [BTN] button pressed',\n description: 'when the selected button on the micro:bit is pressed'\n }),\n blockType: BlockType.HAT,\n arguments: {\n BTN: {\n type: ArgumentType.STRING,\n menu: 'buttons',\n defaultValue: MicroBitButtons.A\n }\n }\n },\n {\n opcode: 'isButtonPressed',\n text: formatMessage({\n id: 'microbit.isButtonPressed',\n default: '[BTN] button pressed?',\n description: 'is the selected button on the micro:bit pressed?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n BTN: {\n type: ArgumentType.STRING,\n menu: 'buttons',\n defaultValue: MicroBitButtons.A\n }\n }\n },\n '---',\n {\n opcode: 'whenGesture',\n text: formatMessage({\n id: 'microbit.whenGesture',\n default: 'when [GESTURE]',\n description: 'when the selected gesture is detected by the micro:bit'\n }),\n blockType: BlockType.HAT,\n arguments: {\n GESTURE: {\n type: ArgumentType.STRING,\n menu: 'gestures',\n defaultValue: MicroBitGestures.MOVED\n }\n }\n },\n '---',\n {\n opcode: 'displaySymbol',\n text: formatMessage({\n id: 'microbit.displaySymbol',\n default: 'display [MATRIX]',\n description: 'display a pattern on the micro:bit display'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MATRIX: {\n type: ArgumentType.MATRIX,\n defaultValue: '0101010101100010101000100'\n }\n }\n },\n {\n opcode: 'displayText',\n text: formatMessage({\n id: 'microbit.displayText',\n default: 'display text [TEXT]',\n description: 'display text on the micro:bit display'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n TEXT: {\n type: ArgumentType.STRING,\n defaultValue: formatMessage({\n id: 'microbit.defaultTextToDisplay',\n default: 'Hello!',\n description: `default text to display.\n IMPORTANT - the micro:bit only supports letters a-z, A-Z.\n Please substitute a default word in your language\n that can be written with those characters,\n substitute non-accented characters or leave it as \"Hello!\".\n Check the micro:bit site documentation for details`\n })\n }\n }\n },\n {\n opcode: 'displayClear',\n text: formatMessage({\n id: 'microbit.clearDisplay',\n default: 'clear display',\n description: 'display nothing on the micro:bit display'\n }),\n blockType: BlockType.COMMAND\n },\n '---',\n {\n opcode: 'whenTilted',\n text: formatMessage({\n id: 'microbit.whenTilted',\n default: 'when tilted [DIRECTION]',\n description: 'when the micro:bit is tilted in a direction'\n }),\n blockType: BlockType.HAT,\n arguments: {\n DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'tiltDirectionAny',\n defaultValue: MicroBitTiltDirection.ANY\n }\n }\n },\n {\n opcode: 'isTilted',\n text: formatMessage({\n id: 'microbit.isTilted',\n default: 'tilted [DIRECTION]?',\n description: 'is the micro:bit is tilted in a direction?'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'tiltDirectionAny',\n defaultValue: MicroBitTiltDirection.ANY\n }\n }\n },\n {\n opcode: 'getTiltAngle',\n text: formatMessage({\n id: 'microbit.tiltAngle',\n default: 'tilt angle [DIRECTION]',\n description: 'how much the micro:bit is tilted in a direction'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'tiltDirection',\n defaultValue: MicroBitTiltDirection.FRONT\n }\n }\n },\n '---',\n {\n opcode: 'whenPinConnected',\n text: formatMessage({\n id: 'microbit.whenPinConnected',\n default: 'when pin [PIN] connected',\n description: 'when the pin detects a connection to Earth/Ground'\n\n }),\n blockType: BlockType.HAT,\n arguments: {\n PIN: {\n type: ArgumentType.STRING,\n menu: 'touchPins',\n defaultValue: '0'\n }\n }\n }\n ],\n menus: {\n buttons: {\n acceptReporters: true,\n items: this.BUTTONS_MENU\n },\n gestures: {\n acceptReporters: true,\n items: this.GESTURES_MENU\n },\n pinState: {\n acceptReporters: true,\n items: this.PIN_STATE_MENU\n },\n tiltDirection: {\n acceptReporters: true,\n items: this.TILT_DIRECTION_MENU\n },\n tiltDirectionAny: {\n acceptReporters: true,\n items: this.TILT_DIRECTION_ANY_MENU\n },\n touchPins: {\n acceptReporters: true,\n items: ['0', '1', '2']\n }\n }\n };\n }\n\n /**\n * Test whether the A or B button is pressed\n * @param {object} args - the block's arguments.\n * @return {boolean} - true if the button is pressed.\n */\n whenButtonPressed (args) {\n if (args.BTN === 'any') {\n return this._peripheral.buttonA | this._peripheral.buttonB;\n } else if (args.BTN === 'A') {\n return this._peripheral.buttonA;\n } else if (args.BTN === 'B') {\n return this._peripheral.buttonB;\n }\n return false;\n }\n\n /**\n * Test whether the A or B button is pressed\n * @param {object} args - the block's arguments.\n * @return {boolean} - true if the button is pressed.\n */\n isButtonPressed (args) {\n if (args.BTN === 'any') {\n return (this._peripheral.buttonA | this._peripheral.buttonB) !== 0;\n } else if (args.BTN === 'A') {\n return this._peripheral.buttonA !== 0;\n } else if (args.BTN === 'B') {\n return this._peripheral.buttonB !== 0;\n }\n return false;\n }\n\n /**\n * Test whether the micro:bit is moving\n * @param {object} args - the block's arguments.\n * @return {boolean} - true if the micro:bit is moving.\n */\n whenGesture (args) {\n const gesture = cast.toString(args.GESTURE);\n if (gesture === 'moved') {\n return (this._peripheral.gestureState >> 2) & 1;\n } else if (gesture === 'shaken') {\n return this._peripheral.gestureState & 1;\n } else if (gesture === 'jumped') {\n return (this._peripheral.gestureState >> 1) & 1;\n }\n return false;\n }\n\n /**\n * Display a predefined symbol on the 5x5 LED matrix.\n * @param {object} args - the block's arguments.\n * @return {Promise} - a Promise that resolves after a tick.\n */\n displaySymbol (args) {\n const symbol = cast.toString(args.MATRIX).replace(/\\s/g, '');\n const reducer = (accumulator, c, index) => {\n const value = (c === '0') ? accumulator : accumulator + Math.pow(2, index);\n return value;\n };\n const hex = symbol.split('').reduce(reducer, 0);\n if (hex !== null) {\n this._peripheral.ledMatrixState[0] = hex & 0x1F;\n this._peripheral.ledMatrixState[1] = (hex >> 5) & 0x1F;\n this._peripheral.ledMatrixState[2] = (hex >> 10) & 0x1F;\n this._peripheral.ledMatrixState[3] = (hex >> 15) & 0x1F;\n this._peripheral.ledMatrixState[4] = (hex >> 20) & 0x1F;\n this._peripheral.displayMatrix(this._peripheral.ledMatrixState);\n }\n\n return new Promise(resolve => {\n setTimeout(() => {\n resolve();\n }, BLESendInterval);\n });\n }\n\n /**\n * Display text on the 5x5 LED matrix.\n * @param {object} args - the block's arguments.\n * @return {Promise} - a Promise that resolves after the text is done printing.\n * Note the limit is 19 characters\n * The print time is calculated by multiplying the number of horizontal pixels\n * by the default scroll delay of 120ms.\n * The number of horizontal pixels = 6px for each character in the string,\n * 1px before the string, and 5px after the string.\n */\n displayText (args) {\n const text = String(args.TEXT).substring(0, 19);\n if (text.length > 0) this._peripheral.displayText(text);\n const yieldDelay = 120 * ((6 * text.length) + 6);\n\n return new Promise(resolve => {\n setTimeout(() => {\n resolve();\n }, yieldDelay);\n });\n }\n\n /**\n * Turn all 5x5 matrix LEDs off.\n * @return {Promise} - a Promise that resolves after a tick.\n */\n displayClear () {\n for (let i = 0; i < 5; i++) {\n this._peripheral.ledMatrixState[i] = 0;\n }\n this._peripheral.displayMatrix(this._peripheral.ledMatrixState);\n\n return new Promise(resolve => {\n setTimeout(() => {\n resolve();\n }, BLESendInterval);\n });\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} DIRECTION - the tilt direction to test (front, back, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n */\n whenTilted (args) {\n return this._isTilted(args.DIRECTION);\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} DIRECTION - the tilt direction to test (front, back, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n */\n isTilted (args) {\n return this._isTilted(args.DIRECTION);\n }\n\n /**\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} DIRECTION - the direction (front, back, left, right) to check.\n * @return {number} - the tilt sensor's angle in the specified direction.\n * Note that getTiltAngle(front) = -getTiltAngle(back) and getTiltAngle(left) = -getTiltAngle(right).\n */\n getTiltAngle (args) {\n return this._getTiltAngle(args.DIRECTION);\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {TiltDirection} direction - the tilt direction to test (front, back, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n * @private\n */\n _isTilted (direction) {\n switch (direction) {\n case MicroBitTiltDirection.ANY:\n return (Math.abs(this._peripheral.tiltX / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD) ||\n (Math.abs(this._peripheral.tiltY / 10) >= Scratch3MicroBitBlocks.TILT_THRESHOLD);\n default:\n return this._getTiltAngle(direction) >= Scratch3MicroBitBlocks.TILT_THRESHOLD;\n }\n }\n\n /**\n * @param {TiltDirection} direction - the direction (front, back, left, right) to check.\n * @return {number} - the tilt sensor's angle in the specified direction.\n * Note that getTiltAngle(front) = -getTiltAngle(back) and getTiltAngle(left) = -getTiltAngle(right).\n * @private\n */\n _getTiltAngle (direction) {\n switch (direction) {\n case MicroBitTiltDirection.FRONT:\n return Math.round(this._peripheral.tiltY / -10);\n case MicroBitTiltDirection.BACK:\n return Math.round(this._peripheral.tiltY / 10);\n case MicroBitTiltDirection.LEFT:\n return Math.round(this._peripheral.tiltX / -10);\n case MicroBitTiltDirection.RIGHT:\n return Math.round(this._peripheral.tiltX / 10);\n default:\n log.warn(`Unknown tilt direction in _getTiltAngle: ${direction}`);\n }\n }\n\n /**\n * @param {object} args - the block's arguments.\n * @return {boolean} - the touch pin state.\n * @private\n */\n whenPinConnected (args) {\n const pin = parseInt(args.PIN, 10);\n if (isNaN(pin)) return;\n if (pin < 0 || pin > 2) return false;\n return this._peripheral._checkPinState(pin);\n }\n}\n\nmodule.exports = Scratch3MicroBitBlocks;\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Clone = require('../../util/clone');\nconst Cast = require('../../util/cast');\nconst formatMessage = require('format-message');\nconst MathUtil = require('../../util/math-util');\nconst Timer = require('../../util/timer');\n\n/**\n * The instrument and drum sounds, loaded as static assets.\n * @type {object}\n */\nlet assetData = {};\ntry {\n assetData = require('./manifest');\n} catch (e) {\n // Non-webpack environment, don't worry about assets.\n}\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PHRpdGxlPm11c2ljLWJsb2NrLWljb248L3RpdGxlPjxkZWZzPjxwYXRoIGQ9Ik0zMi4xOCAyNS44NzRDMzIuNjM2IDI4LjE1NyAzMC41MTIgMzAgMjcuNDMzIDMwYy0zLjA3IDAtNS45MjMtMS44NDMtNi4zNzItNC4xMjYtLjQ1OC0yLjI4NSAxLjY2NS00LjEzNiA0Ljc0My00LjEzNi42NDcgMCAxLjI4My4wODQgMS44OS4yMzQuMzM4LjA4Ni42MzcuMTguOTM4LjMwMi44Ny0uMDItLjEwNC0yLjI5NC0xLjgzNS0xMi4yMy0yLjEzNC0xMi4zMDIgMy4wNi0xLjg3IDguNzY4LTIuNzUyIDUuNzA4LS44ODUuMDc2IDQuODItMy42NSAzLjg0NC0zLjcyNC0uOTg3LTQuNjUtNy4xNTMuMjYzIDE0LjczOHptLTE2Ljk5OCA1Ljk5QzE1LjYzIDM0LjE0OCAxMy41MDcgMzYgMTAuNDQgMzZjLTMuMDcgMC01LjkyMi0xLjg1Mi02LjM4LTQuMTM2LS40NDgtMi4yODQgMS42NzQtNC4xMzUgNC43NS00LjEzNSAxLjAwMyAwIDEuOTc1LjE5NiAyLjg1NS41NDMuODIyLS4wNTUtLjE1LTIuMzc3LTEuODYyLTEyLjIyOC0yLjEzMy0xMi4zMDMgMy4wNi0xLjg3IDguNzY0LTIuNzUzIDUuNzA2LS44OTQuMDc2IDQuODItMy42NDggMy44MzQtMy43MjQtLjk4Ny00LjY1LTcuMTUyLjI2MiAxNC43Mzh6IiBpZD0iYSIvPjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjx1c2UgZmlsbD0iI0ZGRiIgeGxpbms6aHJlZj0iI2EiLz48cGF0aCBzdHJva2Utb3BhY2l0eT0iLjEiIHN0cm9rZT0iIzAwMCIgZD0iTTI4LjQ1NiAyMS42NzVjLS4wMS0uMzEyLS4wODctLjgyNS0uMjU2LTEuNzAyLS4wOTYtLjQ5NS0uNjEyLTMuMDIyLS43NTMtMy43My0uMzk1LTEuOTgtLjc2LTMuOTItMS4xNDItNi4xMTMtLjczMi00LjIyMy0uNjkzLTYuMDUuMzQ0LTYuNTI3LjUtLjIzIDEuMDYtLjA4IDEuODQuMzUuNDE0LjIyNyAyLjE4MiAxLjM2NSAyLjA3IDEuMjk2IDEuOTk0IDEuMjQyIDMuNDY0IDEuNzc0IDQuOTMgMS41NDggMS41MjYtLjIzNyAyLjUwNC0uMDYgMi44NzYuNjE4LjM0OC42MzUuMDE1IDEuNDE2LS43MyAyLjE4LTEuNDcyIDEuNTE2LTMuOTc1IDIuNTE0LTUuODQ4IDIuMDIzLS44MjItLjIyLTEuMjM4LS40NjUtMi4zOC0xLjI2N2wtLjA5NS0uMDY2Yy4wNDcuNTkzLjI2NCAxLjc0LjcxNyAzLjgwMy4yOTQgMS4zMzYgMi4wOCA5LjE4NyAyLjYzNyAxMS42NzRsLjAwMi4wMTJjLjUyOCAyLjYzNy0xLjg3MyA0LjcyNC01LjIzNiA0LjcyNC0zLjI5IDAtNi4zNjMtMS45ODgtNi44NjItNC41MjgtLjUzLTIuNjQgMS44NzMtNC43MzQgNS4yMzMtNC43MzQuNjcyIDAgMS4zNDcuMDg1IDIuMDE0LjI1LjIyNy4wNTcuNDM2LjExOC42MzYuMTg3em0tMTYuOTk2IDUuOTljLS4wMS0uMzE4LS4wOS0uODM4LS4yNjYtMS43MzctLjA5LS40Ni0uNTk1LTIuOTM3LS43NTMtMy43MjctLjM5LTEuOTYtLjc1LTMuODktMS4xMy02LjA3LS43MzItNC4yMjMtLjY5Mi02LjA1LjM0NC02LjUyNi41MDItLjIzIDEuMDYtLjA4MiAxLjg0LjM1LjQxNS4yMjcgMi4xODIgMS4zNjQgMi4wNyAxLjI5NSAxLjk5MyAxLjI0MiAzLjQ2MiAxLjc3NCA0LjkyNiAxLjU0OCAxLjUyNS0uMjQgMi41MDQtLjA2NCAyLjg3Ni42MTQuMzQ4LjYzNS4wMTUgMS40MTUtLjcyOCAyLjE4LTEuNDc0IDEuNTE3LTMuOTc3IDIuNTEzLTUuODQ3IDIuMDE3LS44Mi0uMjItMS4yMzYtLjQ2NC0yLjM3OC0xLjI2N2wtLjA5NS0uMDY1Yy4wNDcuNTkzLjI2NCAxLjc0LjcxNyAzLjgwMi4yOTQgMS4zMzcgMi4wNzggOS4xOSAyLjYzNiAxMS42NzVsLjAwMy4wMTNjLjUxNyAyLjYzOC0xLjg4NCA0LjczMi01LjIzNCA0LjczMi0zLjI4NyAwLTYuMzYtMS45OTMtNi44Ny00LjU0LS41Mi0yLjY0IDEuODg0LTQuNzMgNS4yNC00LjczLjkwNSAwIDEuODAzLjE1IDIuNjUuNDM2eiIvPjwvZz48L3N2Zz4=';\n\n/**\n * Icon svg to be displayed in the category menu, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst menuIconURI = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE2LjA5IDEyLjkzN2MuMjI4IDEuMTQxLS44MzMgMi4wNjMtMi4zNzMgMi4wNjMtMS41MzUgMC0yLjk2Mi0uOTIyLTMuMTg2LTIuMDYzLS4yMy0xLjE0Mi44MzMtMi4wNjggMi4zNzItMi4wNjguMzIzIDAgLjY0MS4wNDIuOTQ1LjExN2EzLjUgMy41IDAgMCAxIC40NjguMTUxYy40MzUtLjAxLS4wNTItMS4xNDctLjkxNy02LjExNC0xLjA2Ny02LjE1MiAxLjUzLS45MzUgNC4zODQtMS4zNzcgMi44NTQtLjQ0Mi4wMzggMi40MS0xLjgyNSAxLjkyMi0xLjg2Mi0uNDkzLTIuMzI1LTMuNTc3LjEzMiA3LjM3ek03LjQ2IDguNTYzYy0xLjg2Mi0uNDkzLTIuMzI1LTMuNTc2LjEzIDcuMzdDNy44MTYgMTcuMDczIDYuNzU0IDE4IDUuMjIgMThjLTEuNTM1IDAtMi45NjEtLjkyNi0zLjE5LTIuMDY4LS4yMjQtMS4xNDIuODM3LTIuMDY3IDIuMzc1LTIuMDY3LjUwMSAwIC45ODcuMDk4IDEuNDI3LjI3Mi40MTItLjAyOC0uMDc0LTEuMTg5LS45My02LjExNEMzLjgzNCAxLjg3IDYuNDMgNy4wODcgOS4yODIgNi42NDZjMi44NTQtLjQ0Ny4wMzggMi40MS0xLjgyMyAxLjkxN3oiIGZpbGw9IiM1NzVFNzUiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==';\n\n/**\n * Class for the music-related blocks in Scratch 3.0\n * @param {Runtime} runtime - the runtime instantiating this block package.\n * @constructor\n */\nclass Scratch3MusicBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n /**\n * The number of drum and instrument sounds currently being played simultaneously.\n * @type {number}\n * @private\n */\n this._concurrencyCounter = 0;\n\n /**\n * An array of sound players, one for each drum sound.\n * @type {Array}\n * @private\n */\n this._drumPlayers = [];\n\n /**\n * An array of arrays of sound players. Each instrument has one or more audio players.\n * @type {Array[]}\n * @private\n */\n this._instrumentPlayerArrays = [];\n\n /**\n * An array of arrays of sound players. Each instrument mya have an audio player for each playable note.\n * @type {Array[]}\n * @private\n */\n this._instrumentPlayerNoteArrays = [];\n\n /**\n * An array of audio bufferSourceNodes. Each time you play an instrument or drum sound,\n * a bufferSourceNode is created. We keep references to them to make sure their onended\n * events can fire.\n * @type {Array}\n * @private\n */\n this._bufferSources = [];\n\n this._loadAllSounds();\n\n this._onTargetCreated = this._onTargetCreated.bind(this);\n this.runtime.on('targetWasCreated', this._onTargetCreated);\n\n this._playNoteForPicker = this._playNoteForPicker.bind(this);\n this.runtime.on('PLAY_NOTE', this._playNoteForPicker);\n }\n\n /**\n * Decode the full set of drum and instrument sounds, and store the audio buffers in arrays.\n */\n _loadAllSounds () {\n const loadingPromises = [];\n this.DRUM_INFO.forEach((drumInfo, index) => {\n const filePath = `drums/${drumInfo.fileName}`;\n const promise = this._storeSound(filePath, index, this._drumPlayers);\n loadingPromises.push(promise);\n });\n this.INSTRUMENT_INFO.forEach((instrumentInfo, instrumentIndex) => {\n this._instrumentPlayerArrays[instrumentIndex] = [];\n this._instrumentPlayerNoteArrays[instrumentIndex] = [];\n instrumentInfo.samples.forEach((sample, noteIndex) => {\n const filePath = `instruments/${instrumentInfo.dirName}/${sample}`;\n const promise = this._storeSound(filePath, noteIndex, this._instrumentPlayerArrays[instrumentIndex]);\n loadingPromises.push(promise);\n });\n });\n Promise.all(loadingPromises).then(() => {\n // @TODO: Update the extension status indicator.\n });\n }\n\n /**\n * Decode a sound and store the player in an array.\n * @param {string} filePath - the audio file name.\n * @param {number} index - the index at which to store the audio player.\n * @param {array} playerArray - the array of players in which to store it.\n * @return {Promise} - a promise which will resolve once the sound has been stored.\n */\n _storeSound (filePath, index, playerArray) {\n const fullPath = `${filePath}.mp3`;\n\n if (!assetData[fullPath]) return;\n\n // The sound player has already been downloaded via the manifest file required above.\n const soundBuffer = assetData[fullPath];\n\n return this._decodeSound(soundBuffer).then(player => {\n playerArray[index] = player;\n });\n }\n\n /**\n * Decode a sound and return a promise with the audio buffer.\n * @param {ArrayBuffer} soundBuffer - a buffer containing the encoded audio.\n * @return {Promise} - a promise which will resolve once the sound has decoded.\n */\n _decodeSound (soundBuffer) {\n const engine = this.runtime.audioEngine;\n\n if (!engine) {\n return Promise.reject(new Error('No Audio Context Detected'));\n }\n\n // Check for newer promise-based API\n return engine.decodeSoundPlayer({data: {buffer: soundBuffer}});\n }\n\n /**\n * Create data for a menu in scratch-blocks format, consisting of an array of objects with text and\n * value properties. The text is a translated string, and the value is one-indexed.\n * @param {object[]} info - An array of info objects each having a name property.\n * @return {array} - An array of objects with text and value properties.\n * @private\n */\n _buildMenu (info) {\n return info.map((entry, index) => {\n const obj = {};\n obj.text = entry.name;\n obj.value = String(index + 1);\n return obj;\n });\n }\n\n /**\n * An array of info about each drum.\n * @type {object[]}\n * @param {string} name - the translatable name to display in the drums menu.\n * @param {string} fileName - the name of the audio file containing the drum sound.\n */\n get DRUM_INFO () {\n return [\n {\n name: formatMessage({\n id: 'music.drumSnare',\n default: '(1) Snare Drum',\n description: 'Sound of snare drum as used in a standard drum kit'\n }),\n fileName: '1-snare'\n },\n {\n name: formatMessage({\n id: 'music.drumBass',\n default: '(2) Bass Drum',\n description: 'Sound of bass drum as used in a standard drum kit'\n }),\n fileName: '2-bass-drum'\n },\n {\n name: formatMessage({\n id: 'music.drumSideStick',\n default: '(3) Side Stick',\n description: 'Sound of a drum stick hitting the side of a drum (usually the snare)'\n }),\n fileName: '3-side-stick'\n },\n {\n name: formatMessage({\n id: 'music.drumCrashCymbal',\n default: '(4) Crash Cymbal',\n description: 'Sound of a drum stick hitting a crash cymbal'\n }),\n fileName: '4-crash-cymbal'\n },\n {\n name: formatMessage({\n id: 'music.drumOpenHiHat',\n default: '(5) Open Hi-Hat',\n description: 'Sound of a drum stick hitting a hi-hat while open'\n }),\n fileName: '5-open-hi-hat'\n },\n {\n name: formatMessage({\n id: 'music.drumClosedHiHat',\n default: '(6) Closed Hi-Hat',\n description: 'Sound of a drum stick hitting a hi-hat while closed'\n }),\n fileName: '6-closed-hi-hat'\n },\n {\n name: formatMessage({\n id: 'music.drumTambourine',\n default: '(7) Tambourine',\n description: 'Sound of a tambourine being struck'\n }),\n fileName: '7-tambourine'\n },\n {\n name: formatMessage({\n id: 'music.drumHandClap',\n default: '(8) Hand Clap',\n description: 'Sound of two hands clapping together'\n }),\n fileName: '8-hand-clap'\n },\n {\n name: formatMessage({\n id: 'music.drumClaves',\n default: '(9) Claves',\n description: 'Sound of claves being struck together'\n }),\n fileName: '9-claves'\n },\n {\n name: formatMessage({\n id: 'music.drumWoodBlock',\n default: '(10) Wood Block',\n description: 'Sound of a wood block being struck'\n }),\n fileName: '10-wood-block'\n },\n {\n name: formatMessage({\n id: 'music.drumCowbell',\n default: '(11) Cowbell',\n description: 'Sound of a cowbell being struck'\n }),\n fileName: '11-cowbell'\n },\n {\n name: formatMessage({\n id: 'music.drumTriangle',\n default: '(12) Triangle',\n description: 'Sound of a triangle (instrument) being struck'\n }),\n fileName: '12-triangle'\n },\n {\n name: formatMessage({\n id: 'music.drumBongo',\n default: '(13) Bongo',\n description: 'Sound of a bongo being struck'\n }),\n fileName: '13-bongo'\n },\n {\n name: formatMessage({\n id: 'music.drumConga',\n default: '(14) Conga',\n description: 'Sound of a conga being struck'\n }),\n fileName: '14-conga'\n },\n {\n name: formatMessage({\n id: 'music.drumCabasa',\n default: '(15) Cabasa',\n description: 'Sound of a cabasa being shaken'\n }),\n fileName: '15-cabasa'\n },\n {\n name: formatMessage({\n id: 'music.drumGuiro',\n default: '(16) Guiro',\n description: 'Sound of a guiro being played'\n }),\n fileName: '16-guiro'\n },\n {\n name: formatMessage({\n id: 'music.drumVibraslap',\n default: '(17) Vibraslap',\n description: 'Sound of a Vibraslap being played'\n }),\n fileName: '17-vibraslap'\n },\n {\n name: formatMessage({\n id: 'music.drumCuica',\n default: '(18) Cuica',\n description: 'Sound of a cuica being played'\n }),\n fileName: '18-cuica'\n }\n ];\n }\n\n /**\n * An array of info about each instrument.\n * @type {object[]}\n * @param {string} name - the translatable name to display in the instruments menu.\n * @param {string} dirName - the name of the directory containing audio samples for this instrument.\n * @param {number} [releaseTime] - an optional duration for the release portion of each note.\n * @param {number[]} samples - an array of numbers representing the MIDI note number for each\n * sampled sound used to play this instrument.\n */\n get INSTRUMENT_INFO () {\n return [\n {\n name: formatMessage({\n id: 'music.instrumentPiano',\n default: '(1) Piano',\n description: 'Sound of a piano'\n }),\n dirName: '1-piano',\n releaseTime: 0.5,\n samples: [24, 36, 48, 60, 72, 84, 96, 108]\n },\n {\n name: formatMessage({\n id: 'music.instrumentElectricPiano',\n default: '(2) Electric Piano',\n description: 'Sound of an electric piano'\n }),\n dirName: '2-electric-piano',\n releaseTime: 0.5,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentOrgan',\n default: '(3) Organ',\n description: 'Sound of an organ'\n }),\n dirName: '3-organ',\n releaseTime: 0.5,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentGuitar',\n default: '(4) Guitar',\n description: 'Sound of an accoustic guitar'\n }),\n dirName: '4-guitar',\n releaseTime: 0.5,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentElectricGuitar',\n default: '(5) Electric Guitar',\n description: 'Sound of an electric guitar'\n }),\n dirName: '5-electric-guitar',\n releaseTime: 0.5,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentBass',\n default: '(6) Bass',\n description: 'Sound of an accoustic upright bass'\n }),\n dirName: '6-bass',\n releaseTime: 0.25,\n samples: [36, 48]\n },\n {\n name: formatMessage({\n id: 'music.instrumentPizzicato',\n default: '(7) Pizzicato',\n description: 'Sound of a string instrument (e.g. violin) being plucked'\n }),\n dirName: '7-pizzicato',\n releaseTime: 0.25,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentCello',\n default: '(8) Cello',\n description: 'Sound of a cello being played with a bow'\n }),\n dirName: '8-cello',\n releaseTime: 0.1,\n samples: [36, 48, 60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentTrombone',\n default: '(9) Trombone',\n description: 'Sound of a trombone being played'\n }),\n dirName: '9-trombone',\n samples: [36, 48, 60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentClarinet',\n default: '(10) Clarinet',\n description: 'Sound of a clarinet being played'\n }),\n dirName: '10-clarinet',\n samples: [48, 60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentSaxophone',\n default: '(11) Saxophone',\n description: 'Sound of a saxophone being played'\n }),\n dirName: '11-saxophone',\n samples: [36, 60, 84]\n },\n {\n name: formatMessage({\n id: 'music.instrumentFlute',\n default: '(12) Flute',\n description: 'Sound of a flute being played'\n }),\n dirName: '12-flute',\n samples: [60, 72]\n },\n {\n name: formatMessage({\n id: 'music.instrumentWoodenFlute',\n default: '(13) Wooden Flute',\n description: 'Sound of a wooden flute being played'\n }),\n dirName: '13-wooden-flute',\n samples: [60, 72]\n },\n {\n name: formatMessage({\n id: 'music.instrumentBassoon',\n default: '(14) Bassoon',\n description: 'Sound of a bassoon being played'\n }),\n dirName: '14-bassoon',\n samples: [36, 48, 60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentChoir',\n default: '(15) Choir',\n description: 'Sound of a choir singing'\n }),\n dirName: '15-choir',\n releaseTime: 0.25,\n samples: [48, 60, 72]\n },\n {\n name: formatMessage({\n id: 'music.instrumentVibraphone',\n default: '(16) Vibraphone',\n description: 'Sound of a vibraphone being struck'\n }),\n dirName: '16-vibraphone',\n releaseTime: 0.5,\n samples: [60, 72]\n },\n {\n name: formatMessage({\n id: 'music.instrumentMusicBox',\n default: '(17) Music Box',\n description: 'Sound of a music box playing'\n }),\n dirName: '17-music-box',\n releaseTime: 0.25,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentSteelDrum',\n default: '(18) Steel Drum',\n description: 'Sound of a steel drum being struck'\n }),\n dirName: '18-steel-drum',\n releaseTime: 0.5,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentMarimba',\n default: '(19) Marimba',\n description: 'Sound of a marimba being struck'\n }),\n dirName: '19-marimba',\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentSynthLead',\n default: '(20) Synth Lead',\n description: 'Sound of a \"lead\" synthesizer being played'\n }),\n dirName: '20-synth-lead',\n releaseTime: 0.1,\n samples: [60]\n },\n {\n name: formatMessage({\n id: 'music.instrumentSynthPad',\n default: '(21) Synth Pad',\n description: 'Sound of a \"pad\" synthesizer being played'\n }),\n dirName: '21-synth-pad',\n releaseTime: 0.25,\n samples: [60]\n }\n ];\n }\n\n /**\n * An array that is a mapping from MIDI instrument numbers to Scratch instrument numbers.\n * @type {number[]}\n */\n get MIDI_INSTRUMENTS () {\n return [\n // Acoustic Grand, Bright Acoustic, Electric Grand, Honky-Tonk\n 1, 1, 1, 1,\n // Electric Piano 1, Electric Piano 2, Harpsichord, Clavinet\n 2, 2, 4, 4,\n // Celesta, Glockenspiel, Music Box, Vibraphone\n 17, 17, 17, 16,\n // Marimba, Xylophone, Tubular Bells, Dulcimer\n 19, 16, 17, 17,\n // Drawbar Organ, Percussive Organ, Rock Organ, Church Organ\n 3, 3, 3, 3,\n // Reed Organ, Accordion, Harmonica, Tango Accordion\n 3, 3, 3, 3,\n // Nylon String Guitar, Steel String Guitar, Electric Jazz Guitar, Electric Clean Guitar\n 4, 4, 5, 5,\n // Electric Muted Guitar, Overdriven Guitar,Distortion Guitar, Guitar Harmonics\n 5, 5, 5, 5,\n // Acoustic Bass, Electric Bass (finger), Electric Bass (pick), Fretless Bass\n 6, 6, 6, 6,\n // Slap Bass 1, Slap Bass 2, Synth Bass 1, Synth Bass 2\n 6, 6, 6, 6,\n // Violin, Viola, Cello, Contrabass\n 8, 8, 8, 8,\n // Tremolo Strings, Pizzicato Strings, Orchestral Strings, Timpani\n 8, 7, 8, 19,\n // String Ensemble 1, String Ensemble 2, SynthStrings 1, SynthStrings 2\n 8, 8, 8, 8,\n // Choir Aahs, Voice Oohs, Synth Voice, Orchestra Hit\n 15, 15, 15, 19,\n // Trumpet, Trombone, Tuba, Muted Trumpet\n 9, 9, 9, 9,\n // French Horn, Brass Section, SynthBrass 1, SynthBrass 2\n 9, 9, 9, 9,\n // Soprano Sax, Alto Sax, Tenor Sax, Baritone Sax\n 11, 11, 11, 11,\n // Oboe, English Horn, Bassoon, Clarinet\n 14, 14, 14, 10,\n // Piccolo, Flute, Recorder, Pan Flute\n 12, 12, 13, 13,\n // Blown Bottle, Shakuhachi, Whistle, Ocarina\n 13, 13, 12, 12,\n // Lead 1 (square), Lead 2 (sawtooth), Lead 3 (calliope), Lead 4 (chiff)\n 20, 20, 20, 20,\n // Lead 5 (charang), Lead 6 (voice), Lead 7 (fifths), Lead 8 (bass+lead)\n 20, 20, 20, 20,\n // Pad 1 (new age), Pad 2 (warm), Pad 3 (polysynth), Pad 4 (choir)\n 21, 21, 21, 21,\n // Pad 5 (bowed), Pad 6 (metallic), Pad 7 (halo), Pad 8 (sweep)\n 21, 21, 21, 21,\n // FX 1 (rain), FX 2 (soundtrack), FX 3 (crystal), FX 4 (atmosphere)\n 21, 21, 21, 21,\n // FX 5 (brightness), FX 6 (goblins), FX 7 (echoes), FX 8 (sci-fi)\n 21, 21, 21, 21,\n // Sitar, Banjo, Shamisen, Koto\n 4, 4, 4, 4,\n // Kalimba, Bagpipe, Fiddle, Shanai\n 17, 14, 8, 10,\n // Tinkle Bell, Agogo, Steel Drums, Woodblock\n 17, 17, 18, 19,\n // Taiko Drum, Melodic Tom, Synth Drum, Reverse Cymbal\n 1, 1, 1, 1,\n // Guitar Fret Noise, Breath Noise, Seashore, Bird Tweet\n 21, 21, 21, 21,\n // Telephone Ring, Helicopter, Applause, Gunshot\n 21, 21, 21, 21\n ];\n }\n\n /**\n * An array that is a mapping from MIDI drum numbers in range (35..81) to Scratch drum numbers.\n * It's in the format [drumNum, pitch, decay].\n * The pitch and decay properties are not currently being used.\n * @type {Array[]}\n */\n get MIDI_DRUMS () {\n return [\n [1, -4], // \"BassDrum\" in 2.0, \"Bass Drum\" in 3.0 (which was \"Tom\" in 2.0)\n [1, 0], // Same as just above\n [2, 0],\n [0, 0],\n [7, 0],\n [0, 2],\n [1, -6, 4],\n [5, 0],\n [1, -3, 3.2],\n [5, 0], // \"HiHatPedal\" in 2.0, \"Closed Hi-Hat\" in 3.0\n [1, 0, 3],\n [4, -8],\n [1, 4, 3],\n [1, 7, 2.7],\n [3, -8],\n [1, 10, 2.7],\n [4, -2],\n [3, -11],\n [4, 2],\n [6, 0],\n [3, 0, 3.5],\n [10, 0],\n [3, -8, 3.5],\n [16, -6],\n [4, 2],\n [12, 2],\n [12, 0],\n [13, 0, 0.2],\n [13, 0, 2],\n [13, -5, 2],\n [12, 12],\n [12, 5],\n [10, 19],\n [10, 12],\n [14, 0],\n [14, 0], // \"Maracas\" in 2.0, \"Cabasa\" in 3.0 (TODO: pitch up?)\n [17, 12],\n [17, 5],\n [15, 0], // \"GuiroShort\" in 2.0, \"Guiro\" in 3.0 (which was \"GuiroLong\" in 2.0) (TODO: decay?)\n [15, 0],\n [8, 0],\n [9, 0],\n [9, -4],\n [17, -5],\n [17, 0],\n [11, -6, 1],\n [11, -6, 3]\n ];\n }\n\n /**\n * The key to load & store a target's music-related state.\n * @type {string}\n */\n static get STATE_KEY () {\n return 'Scratch.music';\n }\n\n /**\n * The default music-related state, to be used when a target has no existing music state.\n * @type {MusicState}\n */\n static get DEFAULT_MUSIC_STATE () {\n return {\n currentInstrument: 0\n };\n }\n\n /**\n * The minimum and maximum MIDI note numbers, for clamping the input to play note.\n * @type {{min: number, max: number}}\n */\n static get MIDI_NOTE_RANGE () {\n return {min: 0, max: 130};\n }\n\n /**\n * The minimum and maximum beat values, for clamping the duration of play note, play drum and rest.\n * 100 beats at the default tempo of 60bpm is 100 seconds.\n * @type {{min: number, max: number}}\n */\n static get BEAT_RANGE () {\n return {min: 0, max: 100};\n }\n\n /** The minimum and maximum tempo values, in bpm.\n * @type {{min: number, max: number}}\n */\n static get TEMPO_RANGE () {\n return {min: 20, max: 500};\n }\n\n /**\n * The maximum number of sounds to allow to play simultaneously.\n * @type {number}\n */\n static get CONCURRENCY_LIMIT () {\n return 30;\n }\n\n /**\n * @param {Target} target - collect music state for this target.\n * @returns {MusicState} the mutable music state associated with that target. This will be created if necessary.\n * @private\n */\n _getMusicState (target) {\n let musicState = target.getCustomState(Scratch3MusicBlocks.STATE_KEY);\n if (!musicState) {\n musicState = Clone.simple(Scratch3MusicBlocks.DEFAULT_MUSIC_STATE);\n target.setCustomState(Scratch3MusicBlocks.STATE_KEY, musicState);\n }\n return musicState;\n }\n\n /**\n * When a music-playing Target is cloned, clone the music state.\n * @param {Target} newTarget - the newly created target.\n * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.\n * @listens Runtime#event:targetWasCreated\n * @private\n */\n _onTargetCreated (newTarget, sourceTarget) {\n if (sourceTarget) {\n const musicState = sourceTarget.getCustomState(Scratch3MusicBlocks.STATE_KEY);\n if (musicState) {\n newTarget.setCustomState(Scratch3MusicBlocks.STATE_KEY, Clone.simple(musicState));\n }\n }\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: 'music',\n name: formatMessage({\n id: 'music.categoryName',\n default: 'Music',\n description: 'Label for the Music extension category'\n }),\n menuIconURI: menuIconURI,\n blockIconURI: blockIconURI,\n blocks: [\n {\n opcode: 'playDrumForBeats',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.playDrumForBeats',\n default: 'play drum [DRUM] for [BEATS] beats',\n description: 'play drum sample for a number of beats'\n }),\n arguments: {\n DRUM: {\n type: ArgumentType.NUMBER,\n menu: 'DRUM',\n defaultValue: 1\n },\n BEATS: {\n type: ArgumentType.NUMBER,\n defaultValue: 0.25\n }\n }\n },\n {\n opcode: 'midiPlayDrumForBeats',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.midiPlayDrumForBeats',\n default: 'play drum [DRUM] for [BEATS] beats',\n description: 'play drum sample for a number of beats according to a mapping of MIDI codes'\n }),\n arguments: {\n DRUM: {\n type: ArgumentType.NUMBER,\n menu: 'DRUM',\n defaultValue: 1\n },\n BEATS: {\n type: ArgumentType.NUMBER,\n defaultValue: 0.25\n }\n },\n hideFromPalette: true\n },\n {\n opcode: 'restForBeats',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.restForBeats',\n default: 'rest for [BEATS] beats',\n description: 'rest (play no sound) for a number of beats'\n }),\n arguments: {\n BEATS: {\n type: ArgumentType.NUMBER,\n defaultValue: 0.25\n }\n }\n },\n {\n opcode: 'playNoteForBeats',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.playNoteForBeats',\n default: 'play note [NOTE] for [BEATS] beats',\n description: 'play a note for a number of beats'\n }),\n arguments: {\n NOTE: {\n type: ArgumentType.NOTE,\n defaultValue: 60\n },\n BEATS: {\n type: ArgumentType.NUMBER,\n defaultValue: 0.25\n }\n }\n },\n {\n opcode: 'setInstrument',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.setInstrument',\n default: 'set instrument to [INSTRUMENT]',\n description: 'set the instrument (e.g. piano, guitar, trombone) for notes played'\n }),\n arguments: {\n INSTRUMENT: {\n type: ArgumentType.NUMBER,\n menu: 'INSTRUMENT',\n defaultValue: 1\n }\n }\n },\n {\n opcode: 'midiSetInstrument',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.midiSetInstrument',\n default: 'set instrument to [INSTRUMENT]',\n description: 'set the instrument for notes played according to a mapping of MIDI codes'\n }),\n arguments: {\n INSTRUMENT: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n },\n hideFromPalette: true\n },\n {\n opcode: 'setTempo',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.setTempo',\n default: 'set tempo to [TEMPO]',\n description: 'set tempo (speed) for notes, drums, and rests played'\n }),\n arguments: {\n TEMPO: {\n type: ArgumentType.NUMBER,\n defaultValue: 60\n }\n }\n },\n {\n opcode: 'changeTempo',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'music.changeTempo',\n default: 'change tempo by [TEMPO]',\n description: 'change tempo (speed) for notes, drums, and rests played'\n }),\n arguments: {\n TEMPO: {\n type: ArgumentType.NUMBER,\n defaultValue: 20\n }\n }\n },\n {\n opcode: 'getTempo',\n text: formatMessage({\n id: 'music.getTempo',\n default: 'tempo',\n description: 'get the current tempo (speed) for notes, drums, and rests played'\n }),\n blockType: BlockType.REPORTER\n }\n ],\n menus: {\n DRUM: {\n acceptReporters: true,\n items: this._buildMenu(this.DRUM_INFO)\n },\n INSTRUMENT: {\n acceptReporters: true,\n items: this._buildMenu(this.INSTRUMENT_INFO)\n }\n }\n };\n }\n\n /**\n * Play a drum sound for some number of beats.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n * @property {int} DRUM - the number of the drum to play.\n * @property {number} BEATS - the duration in beats of the drum sound.\n */\n playDrumForBeats (args, util) {\n this._playDrumForBeats(args.DRUM, args.BEATS, util);\n }\n\n /**\n * Play a drum sound for some number of beats according to the range of \"MIDI\" drum codes supported.\n * This block is implemented for compatibility with old Scratch projects that use the\n * 'drum:duration:elapsed:from:' block.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n */\n midiPlayDrumForBeats (args, util) {\n let drumNum = Cast.toNumber(args.DRUM);\n drumNum = Math.round(drumNum);\n const midiDescription = this.MIDI_DRUMS[drumNum - 35];\n if (midiDescription) {\n drumNum = midiDescription[0];\n } else {\n drumNum = 2; // Default instrument used in Scratch 2.0\n }\n drumNum += 1; // drumNum input to _playDrumForBeats is one-indexed\n this._playDrumForBeats(drumNum, args.BEATS, util);\n }\n\n /**\n * Internal code to play a drum sound for some number of beats.\n * @param {number} drumNum - the drum number.\n * @param {beats} beats - the duration in beats to pause after playing the sound.\n * @param {object} util - utility object provided by the runtime.\n */\n _playDrumForBeats (drumNum, beats, util) {\n if (this._stackTimerNeedsInit(util)) {\n drumNum = Cast.toNumber(drumNum);\n drumNum = Math.round(drumNum);\n drumNum -= 1; // drums are one-indexed\n drumNum = MathUtil.wrapClamp(drumNum, 0, this.DRUM_INFO.length - 1);\n beats = Cast.toNumber(beats);\n beats = this._clampBeats(beats);\n this._playDrumNum(util, drumNum);\n this._startStackTimer(util, this._beatsToSec(beats));\n } else {\n this._checkStackTimer(util);\n }\n }\n\n /**\n * Play a drum sound using its 0-indexed number.\n * @param {object} util - utility object provided by the runtime.\n * @param {number} drumNum - the number of the drum to play.\n * @private\n */\n _playDrumNum (util, drumNum) {\n if (util.runtime.audioEngine === null) return;\n if (util.target.sprite.soundBank === null) return;\n // If we're playing too many sounds, do not play the drum sound.\n if (this._concurrencyCounter > Scratch3MusicBlocks.CONCURRENCY_LIMIT) {\n return;\n }\n\n const player = this._drumPlayers[drumNum];\n\n if (typeof player === 'undefined') return;\n\n if (player.isPlaying && !player.isStarting) {\n // Take the internal player state and create a new player with it.\n // `.play` does this internally but then instructs the sound to\n // stop.\n player.take();\n }\n\n const engine = util.runtime.audioEngine;\n const context = engine.audioContext;\n const volumeGain = context.createGain();\n volumeGain.gain.setValueAtTime(util.target.volume / 100, engine.currentTime);\n volumeGain.connect(engine.getInputNode());\n\n this._concurrencyCounter++;\n player.once('stop', () => {\n this._concurrencyCounter--;\n });\n\n player.play();\n // Connect the player to the gain node.\n player.connect({getInputNode () {\n return volumeGain;\n }});\n }\n\n /**\n * Rest for some number of beats.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n * @property {number} BEATS - the duration in beats of the rest.\n */\n restForBeats (args, util) {\n if (this._stackTimerNeedsInit(util)) {\n let beats = Cast.toNumber(args.BEATS);\n beats = this._clampBeats(beats);\n this._startStackTimer(util, this._beatsToSec(beats));\n } else {\n this._checkStackTimer(util);\n }\n }\n\n /**\n * Play a note using the current musical instrument for some number of beats.\n * This function processes the arguments, and handles the timing of the block's execution.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n * @property {number} NOTE - the pitch of the note to play, interpreted as a MIDI note number.\n * @property {number} BEATS - the duration in beats of the note.\n */\n playNoteForBeats (args, util) {\n if (this._stackTimerNeedsInit(util)) {\n let note = Cast.toNumber(args.NOTE);\n note = MathUtil.clamp(note,\n Scratch3MusicBlocks.MIDI_NOTE_RANGE.min, Scratch3MusicBlocks.MIDI_NOTE_RANGE.max);\n let beats = Cast.toNumber(args.BEATS);\n beats = this._clampBeats(beats);\n // If the duration is 0, do not play the note. In Scratch 2.0, \"play drum for 0 beats\" plays the drum,\n // but \"play note for 0 beats\" is silent.\n if (beats === 0) return;\n\n const durationSec = this._beatsToSec(beats);\n\n this._playNote(util, note, durationSec);\n\n this._startStackTimer(util, durationSec);\n } else {\n this._checkStackTimer(util);\n }\n }\n\n _playNoteForPicker (noteNum, category) {\n if (category !== this.getInfo().name) return;\n const util = {\n runtime: this.runtime,\n target: this.runtime.getEditingTarget()\n };\n this._playNote(util, noteNum, 0.25);\n }\n\n /**\n * Play a note using the current instrument for a duration in seconds.\n * This function actually plays the sound, and handles the timing of the sound, including the\n * \"release\" portion of the sound, which continues briefly after the block execution has finished.\n * @param {object} util - utility object provided by the runtime.\n * @param {number} note - the pitch of the note to play, interpreted as a MIDI note number.\n * @param {number} durationSec - the duration in seconds to play the note.\n * @private\n */\n _playNote (util, note, durationSec) {\n if (util.runtime.audioEngine === null) return;\n if (util.target.sprite.soundBank === null) return;\n\n // If we're playing too many sounds, do not play the note.\n if (this._concurrencyCounter > Scratch3MusicBlocks.CONCURRENCY_LIMIT) {\n return;\n }\n\n // Determine which of the audio samples for this instrument to play\n const musicState = this._getMusicState(util.target);\n const inst = musicState.currentInstrument;\n const instrumentInfo = this.INSTRUMENT_INFO[inst];\n const sampleArray = instrumentInfo.samples;\n const sampleIndex = this._selectSampleIndexForNote(note, sampleArray);\n\n // If the audio sample has not loaded yet, bail out\n if (typeof this._instrumentPlayerArrays[inst] === 'undefined') return;\n if (typeof this._instrumentPlayerArrays[inst][sampleIndex] === 'undefined') return;\n\n // Fetch the sound player to play the note.\n const engine = util.runtime.audioEngine;\n\n if (!this._instrumentPlayerNoteArrays[inst][note]) {\n this._instrumentPlayerNoteArrays[inst][note] = this._instrumentPlayerArrays[inst][sampleIndex].take();\n }\n\n const player = this._instrumentPlayerNoteArrays[inst][note];\n\n if (player.isPlaying && !player.isStarting) {\n // Take the internal player state and create a new player with it.\n // `.play` does this internally but then instructs the sound to\n // stop.\n player.take();\n }\n\n // Set its pitch.\n const sampleNote = sampleArray[sampleIndex];\n const notePitchInterval = this._ratioForPitchInterval(note - sampleNote);\n\n // Create gain nodes for this note's volume and release, and chain them\n // to the output.\n const context = engine.audioContext;\n const volumeGain = context.createGain();\n volumeGain.gain.setValueAtTime(util.target.volume / 100, engine.currentTime);\n const releaseGain = context.createGain();\n volumeGain.connect(releaseGain);\n releaseGain.connect(engine.getInputNode());\n\n // Schedule the release of the note, ramping its gain down to zero,\n // and then stopping the sound.\n let releaseDuration = this.INSTRUMENT_INFO[inst].releaseTime;\n if (typeof releaseDuration === 'undefined') {\n releaseDuration = 0.01;\n }\n const releaseStart = context.currentTime + durationSec;\n const releaseEnd = releaseStart + releaseDuration;\n releaseGain.gain.setValueAtTime(1, releaseStart);\n releaseGain.gain.linearRampToValueAtTime(0.0001, releaseEnd);\n\n this._concurrencyCounter++;\n player.once('stop', () => {\n this._concurrencyCounter--;\n });\n\n // Start playing the note\n player.play();\n // Connect the player to the gain node.\n player.connect({getInputNode () {\n return volumeGain;\n }});\n // Set playback now after play creates the outputNode.\n player.outputNode.playbackRate.value = notePitchInterval;\n // Schedule playback to stop.\n player.outputNode.stop(releaseEnd);\n }\n\n /**\n * The samples array for each instrument is the set of pitches of the available audio samples.\n * This function selects the best one to use to play a given input note, and returns its index\n * in the samples array.\n * @param {number} note - the input note to select a sample for.\n * @param {number[]} samples - an array of the pitches of the available samples.\n * @return {index} the index of the selected sample in the samples array.\n * @private\n */\n _selectSampleIndexForNote (note, samples) {\n // Step backwards through the array of samples, i.e. in descending pitch, in order to find\n // the sample that is the closest one below (or matching) the pitch of the input note.\n for (let i = samples.length - 1; i >= 0; i--) {\n if (note >= samples[i]) {\n return i;\n }\n }\n return 0;\n }\n\n /**\n * Calcuate the frequency ratio for a given musical interval.\n * @param {number} interval - the pitch interval to convert.\n * @return {number} a ratio corresponding to the input interval.\n * @private\n */\n _ratioForPitchInterval (interval) {\n return Math.pow(2, (interval / 12));\n }\n\n /**\n * Clamp a duration in beats to the allowed min and max duration.\n * @param {number} beats - a duration in beats.\n * @return {number} - the clamped duration.\n * @private\n */\n _clampBeats (beats) {\n return MathUtil.clamp(beats, Scratch3MusicBlocks.BEAT_RANGE.min, Scratch3MusicBlocks.BEAT_RANGE.max);\n }\n\n /**\n * Convert a number of beats to a number of seconds, using the current tempo.\n * @param {number} beats - number of beats to convert to secs.\n * @return {number} seconds - number of seconds `beats` will last.\n * @private\n */\n _beatsToSec (beats) {\n return (60 / this.getTempo()) * beats;\n }\n\n /**\n * Check if the stack timer needs initialization.\n * @param {object} util - utility object provided by the runtime.\n * @return {boolean} - true if the stack timer needs to be initialized.\n * @private\n */\n _stackTimerNeedsInit (util) {\n return !util.stackFrame.timer;\n }\n\n /**\n * Start the stack timer and the yield the thread if necessary.\n * @param {object} util - utility object provided by the runtime.\n * @param {number} duration - a duration in seconds to set the timer for.\n * @private\n */\n _startStackTimer (util, duration) {\n util.stackFrame.timer = new Timer();\n util.stackFrame.timer.start();\n util.stackFrame.duration = duration;\n util.yield();\n }\n\n /**\n * Check the stack timer, and if its time is not up yet, yield the thread.\n * @param {object} util - utility object provided by the runtime.\n * @private\n */\n _checkStackTimer (util) {\n const timeElapsed = util.stackFrame.timer.timeElapsed();\n if (timeElapsed < util.stackFrame.duration * 1000) {\n util.yield();\n }\n }\n\n /**\n * Select an instrument for playing notes.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n * @property {int} INSTRUMENT - the number of the instrument to select.\n */\n setInstrument (args, util) {\n this._setInstrument(args.INSTRUMENT, util, false);\n }\n\n /**\n * Select an instrument for playing notes according to a mapping of MIDI codes to Scratch instrument numbers.\n * This block is implemented for compatibility with old Scratch projects that use the 'midiInstrument:' block.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n * @property {int} INSTRUMENT - the MIDI number of the instrument to select.\n */\n midiSetInstrument (args, util) {\n this._setInstrument(args.INSTRUMENT, util, true);\n }\n\n /**\n * Internal code to select an instrument for playing notes. If mapMidi is true, set the instrument according to\n * the MIDI to Scratch instrument mapping.\n * @param {number} instNum - the instrument number.\n * @param {object} util - utility object provided by the runtime.\n * @param {boolean} mapMidi - whether or not instNum is a MIDI instrument number.\n */\n _setInstrument (instNum, util, mapMidi) {\n const musicState = this._getMusicState(util.target);\n instNum = Cast.toNumber(instNum);\n instNum = Math.round(instNum);\n instNum -= 1; // instruments are one-indexed\n if (mapMidi) {\n instNum = (this.MIDI_INSTRUMENTS[instNum] || 0) - 1;\n }\n instNum = MathUtil.wrapClamp(instNum, 0, this.INSTRUMENT_INFO.length - 1);\n musicState.currentInstrument = instNum;\n }\n\n /**\n * Set the current tempo to a new value.\n * @param {object} args - the block arguments.\n * @property {number} TEMPO - the tempo, in beats per minute.\n */\n setTempo (args) {\n const tempo = Cast.toNumber(args.TEMPO);\n this._updateTempo(tempo);\n }\n\n /**\n * Change the current tempo by some amount.\n * @param {object} args - the block arguments.\n * @property {number} TEMPO - the amount to change the tempo, in beats per minute.\n */\n changeTempo (args) {\n const change = Cast.toNumber(args.TEMPO);\n const tempo = change + this.getTempo();\n this._updateTempo(tempo);\n }\n\n /**\n * Update the current tempo, clamping it to the min and max allowable range.\n * @param {number} tempo - the tempo to set, in beats per minute.\n * @private\n */\n _updateTempo (tempo) {\n tempo = MathUtil.clamp(tempo, Scratch3MusicBlocks.TEMPO_RANGE.min, Scratch3MusicBlocks.TEMPO_RANGE.max);\n const stage = this.runtime.getTargetForStage();\n if (stage) {\n stage.tempo = tempo;\n }\n }\n\n /**\n * Get the current tempo.\n * @return {number} - the current tempo, in beats per minute.\n */\n getTempo () {\n const stage = this.runtime.getTargetForStage();\n if (stage) {\n return stage.tempo;\n }\n return 60;\n }\n}\n\nmodule.exports = Scratch3MusicBlocks;\n","module.exports = {\n 'drums/1-snare.mp3': require('!arraybuffer-loader!./assets/drums/1-snare.mp3'),\n 'drums/2-bass-drum.mp3': require('!arraybuffer-loader!./assets/drums/2-bass-drum.mp3'),\n 'drums/3-side-stick.mp3': require('!arraybuffer-loader!./assets/drums/3-side-stick.mp3'),\n 'drums/4-crash-cymbal.mp3': require('!arraybuffer-loader!./assets/drums/4-crash-cymbal.mp3'),\n 'drums/5-open-hi-hat.mp3': require('!arraybuffer-loader!./assets/drums/5-open-hi-hat.mp3'),\n 'drums/6-closed-hi-hat.mp3': require('!arraybuffer-loader!./assets/drums/6-closed-hi-hat.mp3'),\n 'drums/7-tambourine.mp3': require('!arraybuffer-loader!./assets/drums/7-tambourine.mp3'),\n 'drums/8-hand-clap.mp3': require('!arraybuffer-loader!./assets/drums/8-hand-clap.mp3'),\n 'drums/9-claves.mp3': require('!arraybuffer-loader!./assets/drums/9-claves.mp3'),\n 'drums/10-wood-block.mp3': require('!arraybuffer-loader!./assets/drums/10-wood-block.mp3'),\n 'drums/11-cowbell.mp3': require('!arraybuffer-loader!./assets/drums/11-cowbell.mp3'),\n 'drums/12-triangle.mp3': require('!arraybuffer-loader!./assets/drums/12-triangle.mp3'),\n 'drums/13-bongo.mp3': require('!arraybuffer-loader!./assets/drums/13-bongo.mp3'),\n 'drums/14-conga.mp3': require('!arraybuffer-loader!./assets/drums/14-conga.mp3'),\n 'drums/15-cabasa.mp3': require('!arraybuffer-loader!./assets/drums/15-cabasa.mp3'),\n 'drums/16-guiro.mp3': require('!arraybuffer-loader!./assets/drums/16-guiro.mp3'),\n 'drums/17-vibraslap.mp3': require('!arraybuffer-loader!./assets/drums/17-vibraslap.mp3'),\n 'drums/18-cuica.mp3': require('!arraybuffer-loader!./assets/drums/18-cuica.mp3'),\n 'instruments/1-piano/24.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/24.mp3'),\n 'instruments/1-piano/36.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/36.mp3'),\n 'instruments/1-piano/48.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/48.mp3'),\n 'instruments/1-piano/60.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/60.mp3'),\n 'instruments/1-piano/72.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/72.mp3'),\n 'instruments/1-piano/84.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/84.mp3'),\n 'instruments/1-piano/96.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/96.mp3'),\n 'instruments/1-piano/108.mp3': require('!arraybuffer-loader!./assets/instruments/1-piano/108.mp3'),\n 'instruments/2-electric-piano/60.mp3': require('!arraybuffer-loader!./assets/instruments/2-electric-piano/60.mp3'),\n 'instruments/3-organ/60.mp3': require('!arraybuffer-loader!./assets/instruments/3-organ/60.mp3'),\n 'instruments/4-guitar/60.mp3': require('!arraybuffer-loader!./assets/instruments/4-guitar/60.mp3'),\n 'instruments/5-electric-guitar/60.mp3': require(\n '!arraybuffer-loader!./assets/instruments/5-electric-guitar/60.mp3'\n ),\n 'instruments/6-bass/36.mp3': require('!arraybuffer-loader!./assets/instruments/6-bass/36.mp3'),\n 'instruments/6-bass/48.mp3': require('!arraybuffer-loader!./assets/instruments/6-bass/48.mp3'),\n 'instruments/7-pizzicato/60.mp3': require('!arraybuffer-loader!./assets/instruments/7-pizzicato/60.mp3'),\n 'instruments/8-cello/36.mp3': require('!arraybuffer-loader!./assets/instruments/8-cello/36.mp3'),\n 'instruments/8-cello/48.mp3': require('!arraybuffer-loader!./assets/instruments/8-cello/48.mp3'),\n 'instruments/8-cello/60.mp3': require('!arraybuffer-loader!./assets/instruments/8-cello/60.mp3'),\n 'instruments/9-trombone/36.mp3': require('!arraybuffer-loader!./assets/instruments/9-trombone/36.mp3'),\n 'instruments/9-trombone/48.mp3': require('!arraybuffer-loader!./assets/instruments/9-trombone/48.mp3'),\n 'instruments/9-trombone/60.mp3': require('!arraybuffer-loader!./assets/instruments/9-trombone/60.mp3'),\n 'instruments/10-clarinet/48.mp3': require('!arraybuffer-loader!./assets/instruments/10-clarinet/48.mp3'),\n 'instruments/10-clarinet/60.mp3': require('!arraybuffer-loader!./assets/instruments/10-clarinet/60.mp3'),\n 'instruments/11-saxophone/36.mp3': require('!arraybuffer-loader!./assets/instruments/11-saxophone/36.mp3'),\n 'instruments/11-saxophone/60.mp3': require('!arraybuffer-loader!./assets/instruments/11-saxophone/60.mp3'),\n 'instruments/11-saxophone/84.mp3': require('!arraybuffer-loader!./assets/instruments/11-saxophone/84.mp3'),\n 'instruments/12-flute/60.mp3': require('!arraybuffer-loader!./assets/instruments/12-flute/60.mp3'),\n 'instruments/12-flute/72.mp3': require('!arraybuffer-loader!./assets/instruments/12-flute/72.mp3'),\n 'instruments/13-wooden-flute/60.mp3': require('!arraybuffer-loader!./assets/instruments/13-wooden-flute/60.mp3'),\n 'instruments/13-wooden-flute/72.mp3': require('!arraybuffer-loader!./assets/instruments/13-wooden-flute/72.mp3'),\n 'instruments/14-bassoon/36.mp3': require('!arraybuffer-loader!./assets/instruments/14-bassoon/36.mp3'),\n 'instruments/14-bassoon/48.mp3': require('!arraybuffer-loader!./assets/instruments/14-bassoon/48.mp3'),\n 'instruments/14-bassoon/60.mp3': require('!arraybuffer-loader!./assets/instruments/14-bassoon/60.mp3'),\n 'instruments/15-choir/48.mp3': require('!arraybuffer-loader!./assets/instruments/15-choir/48.mp3'),\n 'instruments/15-choir/60.mp3': require('!arraybuffer-loader!./assets/instruments/15-choir/60.mp3'),\n 'instruments/15-choir/72.mp3': require('!arraybuffer-loader!./assets/instruments/15-choir/72.mp3'),\n 'instruments/16-vibraphone/60.mp3': require('!arraybuffer-loader!./assets/instruments/16-vibraphone/60.mp3'),\n 'instruments/16-vibraphone/72.mp3': require('!arraybuffer-loader!./assets/instruments/16-vibraphone/72.mp3'),\n 'instruments/17-music-box/60.mp3': require('!arraybuffer-loader!./assets/instruments/17-music-box/60.mp3'),\n 'instruments/18-steel-drum/60.mp3': require('!arraybuffer-loader!./assets/instruments/18-steel-drum/60.mp3'),\n 'instruments/19-marimba/60.mp3': require('!arraybuffer-loader!./assets/instruments/19-marimba/60.mp3'),\n 'instruments/20-synth-lead/60.mp3': require('!arraybuffer-loader!./assets/instruments/20-synth-lead/60.mp3'),\n 'instruments/21-synth-pad/60.mp3': require('!arraybuffer-loader!./assets/instruments/21-synth-pad/60.mp3')\n};\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst TargetType = require('../../extension-support/target-type');\nconst Cast = require('../../util/cast');\nconst Clone = require('../../util/clone');\nconst Color = require('../../util/color');\nconst formatMessage = require('format-message');\nconst MathUtil = require('../../util/math-util');\nconst RenderedTarget = require('../../sprites/rendered-target');\nconst log = require('../../util/log');\nconst StageLayering = require('../../engine/stage-layering');\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+cGVuLWljb248L3RpdGxlPjxnIHN0cm9rZT0iIzU3NUU3NSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik04Ljc1MyAzNC42MDJsLTQuMjUgMS43OCAxLjc4My00LjIzN2MxLjIxOC0yLjg5MiAyLjkwNy01LjQyMyA1LjAzLTcuNTM4TDMxLjA2NiA0LjkzYy44NDYtLjg0MiAyLjY1LS40MSA0LjAzMi45NjcgMS4zOCAxLjM3NSAxLjgxNiAzLjE3My45NyA0LjAxNUwxNi4zMTggMjkuNTljLTIuMTIzIDIuMTE2LTQuNjY0IDMuOC03LjU2NSA1LjAxMiIgZmlsbD0iI0ZGRiIvPjxwYXRoIGQ9Ik0yOS40MSA2LjExcy00LjQ1LTIuMzc4LTguMjAyIDUuNzcyYy0xLjczNCAzLjc2Ni00LjM1IDEuNTQ2LTQuMzUgMS41NDYiLz48cGF0aCBkPSJNMzYuNDIgOC44MjVjMCAuNDYzLS4xNC44NzMtLjQzMiAxLjE2NGwtOS4zMzUgOS4zYy4yODItLjI5LjQxLS42NjguNDEtMS4xMiAwLS44NzQtLjUwNy0xLjk2My0xLjQwNi0yLjg2OC0xLjM2Mi0xLjM1OC0zLjE0Ny0xLjgtNC4wMDItLjk5TDMwLjk5IDUuMDFjLjg0NC0uODQgMi42NS0uNDEgNC4wMzUuOTYuODk4LjkwNCAxLjM5NiAxLjk4MiAxLjM5NiAyLjg1NU0xMC41MTUgMzMuNzc0Yy0uNTczLjMwMi0xLjE1Ny41Ny0xLjc2NC44M0w0LjUgMzYuMzgybDEuNzg2LTQuMjM1Yy4yNTgtLjYwNC41My0xLjE4Ni44MzMtMS43NTcuNjkuMTgzIDEuNDQ4LjYyNSAyLjEwOCAxLjI4Mi42Ni42NTggMS4xMDIgMS40MTIgMS4yODcgMi4xMDIiIGZpbGw9IiM0Qzk3RkYiLz48cGF0aCBkPSJNMzYuNDk4IDguNzQ4YzAgLjQ2NC0uMTQuODc0LS40MzMgMS4xNjVsLTE5Ljc0MiAxOS42OGMtMi4xMyAyLjExLTQuNjczIDMuNzkzLTcuNTcyIDUuMDFMNC41IDM2LjM4bC45NzQtMi4zMTYgMS45MjUtLjgwOGMyLjg5OC0xLjIxOCA1LjQ0LTIuOSA3LjU3LTUuMDFsMTkuNzQzLTE5LjY4Yy4yOTItLjI5Mi40MzItLjcwMi40MzItMS4xNjUgMC0uNjQ2LS4yNy0xLjQtLjc4LTIuMTIyLjI1LjE3Mi41LjM3Ny43MzcuNjE0Ljg5OC45MDUgMS4zOTYgMS45ODMgMS4zOTYgMi44NTYiIGZpbGw9IiM1NzVFNzUiIG9wYWNpdHk9Ii4xNSIvPjxwYXRoIGQ9Ik0xOC40NSAxMi44M2MwIC41LS40MDQuOTA1LS45MDQuOTA1cy0uOTA1LS40MDUtLjkwNS0uOTA0YzAtLjUuNDA3LS45MDMuOTA2LS45MDMuNSAwIC45MDQuNDA0LjkwNC45MDR6IiBmaWxsPSIjNTc1RTc1Ii8+PC9nPjwvc3ZnPg==';\n\n/**\n * Enum for pen color parameter values.\n * @readonly\n * @enum {string}\n */\nconst ColorParam = {\n COLOR: 'color',\n SATURATION: 'saturation',\n BRIGHTNESS: 'brightness',\n TRANSPARENCY: 'transparency'\n};\n\n/**\n * @typedef {object} PenState - the pen state associated with a particular target.\n * @property {Boolean} penDown - tracks whether the pen should draw for this target.\n * @property {number} color - the current color (hue) of the pen.\n * @property {PenAttributes} penAttributes - cached pen attributes for the renderer. This is the authoritative value for\n * diameter but not for pen color.\n */\n\n/**\n * Host for the Pen-related blocks in Scratch 3.0\n * @param {Runtime} runtime - the runtime instantiating this block package.\n * @constructor\n */\nclass Scratch3PenBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n /**\n * The ID of the renderer Drawable corresponding to the pen layer.\n * @type {int}\n * @private\n */\n this._penDrawableId = -1;\n\n /**\n * The ID of the renderer Skin corresponding to the pen layer.\n * @type {int}\n * @private\n */\n this._penSkinId = -1;\n\n this._onTargetCreated = this._onTargetCreated.bind(this);\n this._onTargetMoved = this._onTargetMoved.bind(this);\n\n runtime.on('targetWasCreated', this._onTargetCreated);\n runtime.on('RUNTIME_DISPOSED', this.clear.bind(this));\n }\n\n /**\n * The default pen state, to be used when a target has no existing pen state.\n * @type {PenState}\n */\n static get DEFAULT_PEN_STATE () {\n return {\n penDown: false,\n color: 66.66,\n saturation: 100,\n brightness: 100,\n transparency: 0,\n _shade: 50, // Used only for legacy `change shade by` blocks\n penAttributes: {\n color4f: [0, 0, 1, 1],\n diameter: 1\n }\n };\n }\n\n\n /**\n * The minimum and maximum allowed pen size.\n * The maximum is twice the diagonal of the stage, so that even an\n * off-stage sprite can fill it.\n * @type {{min: number, max: number}}\n */\n static get PEN_SIZE_RANGE () {\n return {min: 1, max: 1200};\n }\n\n /**\n * The key to load & store a target's pen-related state.\n * @type {string}\n */\n static get STATE_KEY () {\n return 'Scratch.pen';\n }\n\n /**\n * Clamp a pen size value to the range allowed by the pen.\n * @param {number} requestedSize - the requested pen size.\n * @returns {number} the clamped size.\n * @private\n */\n _clampPenSize (requestedSize) {\n return MathUtil.clamp(\n requestedSize,\n Scratch3PenBlocks.PEN_SIZE_RANGE.min,\n Scratch3PenBlocks.PEN_SIZE_RANGE.max\n );\n }\n\n /**\n * Retrieve the ID of the renderer \"Skin\" corresponding to the pen layer. If\n * the pen Skin doesn't yet exist, create it.\n * @returns {int} the Skin ID of the pen layer, or -1 on failure.\n * @private\n */\n _getPenLayerID () {\n if (this._penSkinId < 0 && this.runtime.renderer) {\n this._penSkinId = this.runtime.renderer.createPenSkin();\n this._penDrawableId = this.runtime.renderer.createDrawable(StageLayering.PEN_LAYER);\n this.runtime.renderer.updateDrawableSkinId(this._penDrawableId, this._penSkinId);\n }\n return this._penSkinId;\n }\n\n /**\n * @param {Target} target - collect pen state for this target. Probably, but not necessarily, a RenderedTarget.\n * @returns {PenState} the mutable pen state associated with that target. This will be created if necessary.\n * @private\n */\n _getPenState (target) {\n let penState = target.getCustomState(Scratch3PenBlocks.STATE_KEY);\n if (!penState) {\n penState = Clone.simple(Scratch3PenBlocks.DEFAULT_PEN_STATE);\n target.setCustomState(Scratch3PenBlocks.STATE_KEY, penState);\n }\n return penState;\n }\n\n /**\n * When a pen-using Target is cloned, clone the pen state.\n * @param {Target} newTarget - the newly created target.\n * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.\n * @listens Runtime#event:targetWasCreated\n * @private\n */\n _onTargetCreated (newTarget, sourceTarget) {\n if (sourceTarget) {\n const penState = sourceTarget.getCustomState(Scratch3PenBlocks.STATE_KEY);\n if (penState) {\n newTarget.setCustomState(Scratch3PenBlocks.STATE_KEY, Clone.simple(penState));\n if (penState.penDown) {\n newTarget.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);\n }\n }\n }\n }\n\n /**\n * Handle a target which has moved. This only fires when the pen is down.\n * @param {RenderedTarget} target - the target which has moved.\n * @param {number} oldX - the previous X position.\n * @param {number} oldY - the previous Y position.\n * @param {boolean} isForce - whether the movement was forced.\n * @private\n */\n _onTargetMoved (target, oldX, oldY, isForce) {\n // Only move the pen if the movement isn't forced (ie. dragged).\n if (!isForce) {\n const penSkinId = this._getPenLayerID();\n if (penSkinId >= 0) {\n const penState = this._getPenState(target);\n this.runtime.renderer.penLine(penSkinId, penState.penAttributes, oldX, oldY, target.x, target.y);\n this.runtime.requestRedraw();\n }\n }\n }\n\n /**\n * Wrap a color input into the range (0,100).\n * @param {number} value - the value to be wrapped.\n * @returns {number} the wrapped value.\n * @private\n */\n _wrapColor (value) {\n return MathUtil.wrapClamp(value, 0, 100);\n }\n\n /**\n * Initialize color parameters menu with localized strings\n * @returns {array} of the localized text and values for each menu element\n * @private\n */\n _initColorParam () {\n return [\n {\n text: formatMessage({\n id: 'pen.colorMenu.color',\n default: 'color',\n description: 'label for color element in color picker for pen extension'\n }),\n value: ColorParam.COLOR\n },\n {\n text: formatMessage({\n id: 'pen.colorMenu.saturation',\n default: 'saturation',\n description: 'label for saturation element in color picker for pen extension'\n }),\n value: ColorParam.SATURATION\n },\n {\n text: formatMessage({\n id: 'pen.colorMenu.brightness',\n default: 'brightness',\n description: 'label for brightness element in color picker for pen extension'\n }),\n value: ColorParam.BRIGHTNESS\n },\n {\n text: formatMessage({\n id: 'pen.colorMenu.transparency',\n default: 'transparency',\n description: 'label for transparency element in color picker for pen extension'\n }),\n value: ColorParam.TRANSPARENCY\n\n }\n ];\n }\n\n /**\n * Clamp a pen color parameter to the range (0,100).\n * @param {number} value - the value to be clamped.\n * @returns {number} the clamped value.\n * @private\n */\n _clampColorParam (value) {\n return MathUtil.clamp(value, 0, 100);\n }\n\n /**\n * Convert an alpha value to a pen transparency value.\n * Alpha ranges from 0 to 1, where 0 is transparent and 1 is opaque.\n * Transparency ranges from 0 to 100, where 0 is opaque and 100 is transparent.\n * @param {number} alpha - the input alpha value.\n * @returns {number} the transparency value.\n * @private\n */\n _alphaToTransparency (alpha) {\n return (1.0 - alpha) * 100.0;\n }\n\n /**\n * Convert a pen transparency value to an alpha value.\n * Alpha ranges from 0 to 1, where 0 is transparent and 1 is opaque.\n * Transparency ranges from 0 to 100, where 0 is opaque and 100 is transparent.\n * @param {number} transparency - the input transparency value.\n * @returns {number} the alpha value.\n * @private\n */\n _transparencyToAlpha (transparency) {\n return 1.0 - (transparency / 100.0);\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: 'pen',\n name: formatMessage({\n id: 'pen.categoryName',\n default: 'Pen',\n description: 'Label for the pen extension category'\n }),\n blockIconURI: blockIconURI,\n blocks: [\n {\n opcode: 'clear',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.clear',\n default: 'erase all',\n description: 'erase all pen trails and stamps'\n })\n },\n {\n opcode: 'stamp',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.stamp',\n default: 'stamp',\n description: 'render current costume on the background'\n }),\n filter: [TargetType.SPRITE]\n },\n {\n opcode: 'penDown',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.penDown',\n default: 'pen down',\n description: 'start leaving a trail when the sprite moves'\n }),\n filter: [TargetType.SPRITE]\n },\n {\n opcode: 'penUp',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.penUp',\n default: 'pen up',\n description: 'stop leaving a trail behind the sprite'\n }),\n filter: [TargetType.SPRITE]\n },\n {\n opcode: 'setPenColorToColor',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.setColor',\n default: 'set pen color to [COLOR]',\n description: 'set the pen color to a particular (RGB) value'\n }),\n arguments: {\n COLOR: {\n type: ArgumentType.COLOR\n }\n },\n filter: [TargetType.SPRITE]\n },\n {\n opcode: 'changePenColorParamBy',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.changeColorParam',\n default: 'change pen [COLOR_PARAM] by [VALUE]',\n description: 'change the state of a pen color parameter'\n }),\n arguments: {\n COLOR_PARAM: {\n type: ArgumentType.STRING,\n menu: 'colorParam',\n defaultValue: ColorParam.COLOR\n },\n VALUE: {\n type: ArgumentType.NUMBER,\n defaultValue: 10\n }\n },\n filter: [TargetType.SPRITE]\n },\n {\n opcode: 'setPenColorParamTo',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.setColorParam',\n default: 'set pen [COLOR_PARAM] to [VALUE]',\n description: 'set the state for a pen color parameter e.g. saturation'\n }),\n arguments: {\n COLOR_PARAM: {\n type: ArgumentType.STRING,\n menu: 'colorParam',\n defaultValue: ColorParam.COLOR\n },\n VALUE: {\n type: ArgumentType.NUMBER,\n defaultValue: 50\n }\n },\n filter: [TargetType.SPRITE]\n },\n {\n opcode: 'changePenSizeBy',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.changeSize',\n default: 'change pen size by [SIZE]',\n description: 'change the diameter of the trail left by a sprite'\n }),\n arguments: {\n SIZE: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n },\n filter: [TargetType.SPRITE]\n },\n {\n opcode: 'setPenSizeTo',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.setSize',\n default: 'set pen size to [SIZE]',\n description: 'set the diameter of a trail left by a sprite'\n }),\n arguments: {\n SIZE: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n },\n filter: [TargetType.SPRITE]\n },\n /* Legacy blocks, should not be shown in flyout */\n {\n opcode: 'setPenShadeToNumber',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.setShade',\n default: 'set pen shade to [SHADE]',\n description: 'legacy pen blocks - set pen shade'\n }),\n arguments: {\n SHADE: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n },\n hideFromPalette: true\n },\n {\n opcode: 'changePenShadeBy',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.changeShade',\n default: 'change pen shade by [SHADE]',\n description: 'legacy pen blocks - change pen shade'\n }),\n arguments: {\n SHADE: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n },\n hideFromPalette: true\n },\n {\n opcode: 'setPenHueToNumber',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.setHue',\n default: 'set pen color to [HUE]',\n description: 'legacy pen blocks - set pen color to number'\n }),\n arguments: {\n HUE: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n },\n hideFromPalette: true\n },\n {\n opcode: 'changePenHueBy',\n blockType: BlockType.COMMAND,\n text: formatMessage({\n id: 'pen.changeHue',\n default: 'change pen color by [HUE]',\n description: 'legacy pen blocks - change pen color'\n }),\n arguments: {\n HUE: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n },\n hideFromPalette: true\n }\n ],\n menus: {\n colorParam: {\n acceptReporters: true,\n items: this._initColorParam()\n }\n }\n };\n }\n\n /**\n * The pen \"clear\" block clears the pen layer's contents.\n */\n clear () {\n const penSkinId = this._getPenLayerID();\n if (penSkinId >= 0) {\n this.runtime.renderer.penClear(penSkinId);\n this.runtime.requestRedraw();\n }\n }\n\n /**\n * The pen \"stamp\" block stamps the current drawable's image onto the pen layer.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n */\n stamp (args, util) {\n const penSkinId = this._getPenLayerID();\n if (penSkinId >= 0) {\n const target = util.target;\n this.runtime.renderer.penStamp(penSkinId, target.drawableID);\n this.runtime.requestRedraw();\n }\n }\n\n /**\n * The pen \"pen down\" block causes the target to leave pen trails on future motion.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n */\n penDown (args, util) {\n const target = util.target;\n const penState = this._getPenState(target);\n\n if (!penState.penDown) {\n penState.penDown = true;\n target.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);\n }\n\n const penSkinId = this._getPenLayerID();\n if (penSkinId >= 0) {\n this.runtime.renderer.penPoint(penSkinId, penState.penAttributes, target.x, target.y);\n this.runtime.requestRedraw();\n }\n }\n\n /**\n * The pen \"pen up\" block stops the target from leaving pen trails.\n * @param {object} args - the block arguments.\n * @param {object} util - utility object provided by the runtime.\n */\n penUp (args, util) {\n const target = util.target;\n const penState = this._getPenState(target);\n\n if (penState.penDown) {\n penState.penDown = false;\n target.removeListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);\n }\n }\n\n /**\n * The pen \"set pen color to {color}\" block sets the pen to a particular RGB color.\n * The transparency is reset to 0.\n * @param {object} args - the block arguments.\n * @property {int} COLOR - the color to set, expressed as a 24-bit RGB value (0xRRGGBB).\n * @param {object} util - utility object provided by the runtime.\n */\n setPenColorToColor (args, util) {\n const penState = this._getPenState(util.target);\n const rgb = Cast.toRgbColorObject(args.COLOR);\n const hsv = Color.rgbToHsv(rgb);\n penState.color = (hsv.h / 360) * 100;\n penState.saturation = hsv.s * 100;\n penState.brightness = hsv.v * 100;\n if (rgb.hasOwnProperty('a')) {\n penState.transparency = 100 * (1 - (rgb.a / 255.0));\n } else {\n penState.transparency = 0;\n }\n\n // Set the legacy \"shade\" value the same way scratch 2 did.\n penState._shade = penState.brightness / 2;\n\n this._updatePenColor(penState);\n }\n\n /**\n * Update the cached color from the color, saturation, brightness and transparency values\n * in the provided PenState object.\n * @param {PenState} penState - the pen state to update.\n * @private\n */\n _updatePenColor (penState) {\n const rgb = Color.hsvToRgb({\n h: penState.color * 360 / 100,\n s: penState.saturation / 100,\n v: penState.brightness / 100\n });\n penState.penAttributes.color4f[0] = rgb.r / 255.0;\n penState.penAttributes.color4f[1] = rgb.g / 255.0;\n penState.penAttributes.color4f[2] = rgb.b / 255.0;\n penState.penAttributes.color4f[3] = this._transparencyToAlpha(penState.transparency);\n }\n\n /**\n * Set or change a single color parameter on the pen state, and update the pen color.\n * @param {ColorParam} param - the name of the color parameter to set or change.\n * @param {number} value - the value to set or change the param by.\n * @param {PenState} penState - the pen state to update.\n * @param {boolean} change - if true change param by value, if false set param to value.\n * @private\n */\n _setOrChangeColorParam (param, value, penState, change) {\n switch (param) {\n case ColorParam.COLOR:\n penState.color = this._wrapColor(value + (change ? penState.color : 0));\n break;\n case ColorParam.SATURATION:\n penState.saturation = this._clampColorParam(value + (change ? penState.saturation : 0));\n break;\n case ColorParam.BRIGHTNESS:\n penState.brightness = this._clampColorParam(value + (change ? penState.brightness : 0));\n break;\n case ColorParam.TRANSPARENCY:\n penState.transparency = this._clampColorParam(value + (change ? penState.transparency : 0));\n break;\n default:\n log.warn(`Tried to set or change unknown color parameter: ${param}`);\n }\n this._updatePenColor(penState);\n }\n\n /**\n * The \"change pen {ColorParam} by {number}\" block changes one of the pen's color parameters\n * by a given amound.\n * @param {object} args - the block arguments.\n * @property {ColorParam} COLOR_PARAM - the name of the selected color parameter.\n * @property {number} VALUE - the amount to change the selected parameter by.\n * @param {object} util - utility object provided by the runtime.\n */\n changePenColorParamBy (args, util) {\n const penState = this._getPenState(util.target);\n this._setOrChangeColorParam(args.COLOR_PARAM, Cast.toNumber(args.VALUE), penState, true);\n }\n\n /**\n * The \"set pen {ColorParam} to {number}\" block sets one of the pen's color parameters\n * to a given amound.\n * @param {object} args - the block arguments.\n * @property {ColorParam} COLOR_PARAM - the name of the selected color parameter.\n * @property {number} VALUE - the amount to set the selected parameter to.\n * @param {object} util - utility object provided by the runtime.\n */\n setPenColorParamTo (args, util) {\n const penState = this._getPenState(util.target);\n this._setOrChangeColorParam(args.COLOR_PARAM, Cast.toNumber(args.VALUE), penState, false);\n }\n\n /**\n * The pen \"change pen size by {number}\" block changes the pen size by the given amount.\n * @param {object} args - the block arguments.\n * @property {number} SIZE - the amount of desired size change.\n * @param {object} util - utility object provided by the runtime.\n */\n changePenSizeBy (args, util) {\n const penAttributes = this._getPenState(util.target).penAttributes;\n penAttributes.diameter = this._clampPenSize(penAttributes.diameter + Cast.toNumber(args.SIZE));\n }\n\n /**\n * The pen \"set pen size to {number}\" block sets the pen size to the given amount.\n * @param {object} args - the block arguments.\n * @property {number} SIZE - the amount of desired size change.\n * @param {object} util - utility object provided by the runtime.\n */\n setPenSizeTo (args, util) {\n const penAttributes = this._getPenState(util.target).penAttributes;\n penAttributes.diameter = this._clampPenSize(Cast.toNumber(args.SIZE));\n }\n\n /* LEGACY OPCODES */\n /**\n * Scratch 2 \"hue\" param is equivelant to twice the new \"color\" param.\n * @param {object} args - the block arguments.\n * @property {number} HUE - the amount to set the hue to.\n * @param {object} util - utility object provided by the runtime.\n */\n setPenHueToNumber (args, util) {\n const penState = this._getPenState(util.target);\n const hueValue = Cast.toNumber(args.HUE);\n const colorValue = hueValue / 2;\n this._setOrChangeColorParam(ColorParam.COLOR, colorValue, penState, false);\n this._setOrChangeColorParam(ColorParam.TRANSPARENCY, 0, penState, false);\n this._legacyUpdatePenColor(penState);\n }\n\n /**\n * Scratch 2 \"hue\" param is equivelant to twice the new \"color\" param.\n * @param {object} args - the block arguments.\n * @property {number} HUE - the amount of desired hue change.\n * @param {object} util - utility object provided by the runtime.\n */\n changePenHueBy (args, util) {\n const penState = this._getPenState(util.target);\n const hueChange = Cast.toNumber(args.HUE);\n const colorChange = hueChange / 2;\n this._setOrChangeColorParam(ColorParam.COLOR, colorChange, penState, true);\n\n this._legacyUpdatePenColor(penState);\n }\n\n /**\n * Use legacy \"set shade\" code to calculate RGB value for shade,\n * then convert back to HSV and store those components.\n * It is important to also track the given shade in penState._shade\n * because it cannot be accurately backed out of the new HSV later.\n * @param {object} args - the block arguments.\n * @property {number} SHADE - the amount to set the shade to.\n * @param {object} util - utility object provided by the runtime.\n */\n setPenShadeToNumber (args, util) {\n const penState = this._getPenState(util.target);\n let newShade = Cast.toNumber(args.SHADE);\n\n // Wrap clamp the new shade value the way scratch 2 did.\n newShade = newShade % 200;\n if (newShade < 0) newShade += 200;\n\n // And store the shade that was used to compute this new color for later use.\n penState._shade = newShade;\n\n this._legacyUpdatePenColor(penState);\n }\n\n /**\n * Because \"shade\" cannot be backed out of hsv consistently, use the previously\n * stored penState._shade to make the shade change.\n * @param {object} args - the block arguments.\n * @property {number} SHADE - the amount of desired shade change.\n * @param {object} util - utility object provided by the runtime.\n */\n changePenShadeBy (args, util) {\n const penState = this._getPenState(util.target);\n const shadeChange = Cast.toNumber(args.SHADE);\n this.setPenShadeToNumber({SHADE: penState._shade + shadeChange}, util);\n }\n\n /**\n * Update the pen state's color from its hue & shade values, Scratch 2.0 style.\n * @param {object} penState - update the HSV & RGB values in this pen state from its hue & shade values.\n * @private\n */\n _legacyUpdatePenColor (penState) {\n // Create the new color in RGB using the scratch 2 \"shade\" model\n let rgb = Color.hsvToRgb({h: penState.color * 360 / 100, s: 1, v: 1});\n const shade = (penState._shade > 100) ? 200 - penState._shade : penState._shade;\n if (shade < 50) {\n rgb = Color.mixRgb(Color.RGB_BLACK, rgb, (10 + shade) / 60);\n } else {\n rgb = Color.mixRgb(rgb, Color.RGB_WHITE, (shade - 50) / 60);\n }\n\n // Update the pen state according to new color\n const hsv = Color.rgbToHsv(rgb);\n penState.color = 100 * hsv.h / 360;\n penState.saturation = 100 * hsv.s;\n penState.brightness = 100 * hsv.v;\n\n this._updatePenColor(penState);\n }\n}\n\nmodule.exports = Scratch3PenBlocks;\n","const formatMessage = require('format-message');\nconst languageNames = require('scratch-translate-extension-languages');\n\nconst ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Cast = require('../../util/cast');\nconst MathUtil = require('../../util/math-util');\nconst Clone = require('../../util/clone');\nconst log = require('../../util/log');\nconst fetchWithTimeout = require('../../util/fetch-with-timeout');\n\n/**\n * Icon svg to be displayed in the blocks category menu, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst menuIconURI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjIgKDY3MTQ1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5FeHRlbnNpb25zL1NvZnR3YXJlL1RleHQtdG8tU3BlZWNoLU1lbnU8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZyBpZD0iRXh0ZW5zaW9ucy9Tb2Z0d2FyZS9UZXh0LXRvLVNwZWVjaC1NZW51IiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0idGV4dDJzcGVlY2giIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIuMDAwMDAwLCAyLjAwMDAwMCkiIGZpbGwtcnVsZT0ibm9uemVybyI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik01Ljc1LDguODM0NjcxNzMgQzUuNzUsOC4zMjY5NjM0NCA1LjAwMzAwNzI3LDguMDQyMjEzNzEgNC41NTYyODAxMiw4LjQ0NDE0OTk5IEwzLjIwNjI4MDEyLDkuNTI1MzU3MDIgQzIuNjk2NzMzNzgsOS45MzM0NDk2OCAyLjAzNzQ4Njc1LDEwLjE2NTg3ODggMS4zNSwxMC4xNjU4Nzg4IEwxLjE1LDEwLjE2NTg3ODggQzAuNjMyNTk2MTY1LDEwLjE2NTg3ODggMC4yNSwxMC41MTA2MDAyIDAuMjUsMTAuOTUyMDM1NSBMMC4yNSwxMy4wNjkzOTkzIEMwLjI1LDEzLjUxMDgzNDYgMC42MzI1OTYxNjUsMTMuODU1NTU2IDEuMTUsMTMuODU1NTU2IEwxLjM1LDEzLjg1NTU1NiBDMi4wNzg3Nzg0MSwxMy44NTU1NTYgMi43MjY4NjE2MSwxNC4wNjY3NjM2IDMuMjU5ODYwNDksMTQuNDk5IEw0LjU1OTIwMTQ3LDE1LjU3OTY2MDggQzUuMDEzMDkyNzYsMTUuOTU0NTM5NiA1Ljc1LDE1LjY3MzYzNDQgNS43NSwxNS4xNDE3MTI4IEw1Ljc1LDguODM0NjcxNzMgWiIgaWQ9InNwZWFrZXIiIHN0cm9rZS1vcGFjaXR5PSIwLjE1IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMC41IiBmaWxsPSIjNEQ0RDREIj48L3BhdGg+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMC43MDQ4MzEzLDggQzkuNzkwNjc0NjgsOS4xMzExNDg0NyA4LjMwNjYxODQsOS43MTQyODU3MSA3LjgzMzMzMzMzLDkuNzE0Mjg1NzEgQzcuODMzMzMzMzMsOS43MTQyODU3MSA3LjUsOS43MTQyODU3MSA3LjUsOS4zODA5NTIzOCBDNy41LDkuMDg1MjI2ODQgOC4wNjIyMDE2OCw4LjkwMTk0MTY0IDguMTg5MDYwNjcsNy41Njc1NDA1OCBDNi44ODk5Njk5MSw2LjkwNjc5MDA1IDYsNS41NTczMjY4MyA2LDQgQzYsMS43OTA4NjEgNy43OTA4NjEsNC4wNTgxMjI1MWUtMTYgMTAsMCBMMTIsMCBDMTQuMjA5MTM5LC00LjA1ODEyMjUxZS0xNiAxNiwxLjc5MDg2MSAxNiw0IEMxNiw2LjIwOTEzOSAxNC4yMDkxMzksOCAxMiw4IEwxMC43MDQ4MzEzLDggWiIgaWQ9InNwZWVjaCIgZmlsbD0iIzBFQkQ4QyI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+';\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSIwIDAgNDAgNDAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjIgKDY3MTQ1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5FeHRlbnNpb25zL1NvZnR3YXJlL1RleHQtdG8tU3BlZWNoLUJsb2NrPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGcgaWQ9IkV4dGVuc2lvbnMvU29mdHdhcmUvVGV4dC10by1TcGVlY2gtQmxvY2siIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZS1vcGFjaXR5PSIwLjE1Ij4KICAgICAgICA8ZyBpZD0idGV4dDJzcGVlY2giIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQuMDAwMDAwLCA0LjAwMDAwMCkiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIj4KICAgICAgICAgICAgPHBhdGggZD0iTTExLjUsMTcuNjY5MzQzNSBDMTEuNSwxNi42NTM5MjY5IDEwLjAwNjAxNDUsMTYuMDg0NDI3NCA5LjExMjU2MDI0LDE2Ljg4ODMgTDYuNDEyNTYwMjQsMTkuMDUwNzE0IEM1LjM5MzQ2NzU1LDE5Ljg2Njg5OTQgNC4wNzQ5NzM1MSwyMC4zMzE3NTc1IDIuNywyMC4zMzE3NTc1IEwyLjMsMjAuMzMxNzU3NSBDMS4yNjUxOTIzMywyMC4zMzE3NTc1IDAuNSwyMS4wMjEyMDAzIDAuNSwyMS45MDQwNzEgTDAuNSwyNi4xMzg3OTg2IEMwLjUsMjcuMDIxNjY5MyAxLjI2NTE5MjMzLDI3LjcxMTExMiAyLjMsMjcuNzExMTEyIEwyLjcsMjcuNzExMTEyIEM0LjE1NzU1NjgyLDI3LjcxMTExMiA1LjQ1MzcyMzIyLDI4LjEzMzUyNzEgNi41MTk3MjA5OCwyOC45OTggTDkuMTE4NDAyOTMsMzEuMTU5MzIxNiBDMTAuMDI2MTg1NSwzMS45MDkwNzkzIDExLjUsMzEuMzQ3MjY4OSAxMS41LDMwLjI4MzQyNTUgTDExLjUsMTcuNjY5MzQzNSBaIiBpZD0ic3BlYWtlciIgZmlsbD0iIzRENEQ0RCI+PC9wYXRoPgogICAgICAgICAgICA8cGF0aCBkPSJNMjEuNjQzNjA2NiwxNi41IEMxOS45NzcwMDk5LDE4LjQzNzAyMzQgMTcuMTA1MDI3NSwxOS45Mjg1NzE0IDE1LjY2NjY2NjcsMTkuOTI4NTcxNCBDMTUuNTEyNjM5NywxOS45Mjg1NzE0IDE1LjMxNjYyOTIsMTkuODk1OTAzIDE1LjEwOTcyNjUsMTkuNzkyNDUxNyBDMTQuNzM3NjAzOSwxOS42MDYzOTA0IDE0LjUsMTkuMjQ5OTg0NiAxNC41LDE4Ljc2MTkwNDggQzE0LjUsMTguNjU2ODA0MSAxNC41MTcwNTU1LDE4LjU1NDUwNzYgMTQuNTQ5NDQ2NywxOC40NTQwODQ0IEMxNC42MjU3NTQ1LDE4LjIxNzUwNjMgMTUuMTczNTcyMSwxNy40Njc1MzEgMTUuMjc3MjA3MSwxNy4yODA5ODgxIEMxNS41NDYzNTI2LDE2Ljc5NjUyNjEgMTUuNzM5MDI1LDE2LjIwNjM1NjEgMTUuODQzMjg5MSwxNS40MTYwMDM0IEMxMy4xODk3MDA1LDEzLjkyNjgzNjkgMTEuNSwxMS4xMTM5NjY4IDExLjUsOCBDMTEuNSwzLjMwNTU3OTYzIDE1LjMwNTU3OTYsLTAuNSAyMCwtMC41IEwyNCwtMC41IEMyOC42OTQ0MjA0LC0wLjUgMzIuNSwzLjMwNTU3OTYzIDMyLjUsOCBDMzIuNSwxMi42OTQ0MjA0IDI4LjY5NDQyMDQsMTYuNSAyNCwxNi41IEwyMS42NDM2MDY2LDE2LjUgWiIgaWQ9InNwZWVjaCIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+';\n\n/**\n * The url of the synthesis server.\n * @type {string}\n */\nconst SERVER_HOST = 'https://synthesis-service.scratch.mit.edu';\n\n/**\n * How long to wait in ms before timing out requests to synthesis server.\n * @type {int}\n */\nconst SERVER_TIMEOUT = 10000; // 10 seconds\n\n/**\n * Volume for playback of speech sounds, as a percentage.\n * @type {number}\n */\nconst SPEECH_VOLUME = 250;\n\n/**\n * An id for one of the voices.\n */\nconst ALTO_ID = 'ALTO';\n\n/**\n * An id for one of the voices.\n */\nconst TENOR_ID = 'TENOR';\n\n/**\n * An id for one of the voices.\n */\nconst SQUEAK_ID = 'SQUEAK';\n\n/**\n * An id for one of the voices.\n */\nconst GIANT_ID = 'GIANT';\n\n/**\n * An id for one of the voices.\n */\nconst KITTEN_ID = 'KITTEN';\n\n/**\n * Playback rate for the tenor voice, for cases where we have only a female gender voice.\n */\nconst FEMALE_TENOR_RATE = 0.89; // -2 semitones\n\n/**\n * Playback rate for the giant voice, for cases where we have only a female gender voice.\n */\nconst FEMALE_GIANT_RATE = 0.79; // -4 semitones\n\n/**\n * Language ids. The value for each language id is a valid Scratch locale.\n */\nconst ARABIC_ID = 'ar';\nconst CHINESE_ID = 'zh-cn';\nconst DANISH_ID = 'da';\nconst DUTCH_ID = 'nl';\nconst ENGLISH_ID = 'en';\nconst FRENCH_ID = 'fr';\nconst GERMAN_ID = 'de';\nconst HINDI_ID = 'hi';\nconst ICELANDIC_ID = 'is';\nconst ITALIAN_ID = 'it';\nconst JAPANESE_ID = 'ja';\nconst KOREAN_ID = 'ko';\nconst NORWEGIAN_ID = 'nb';\nconst POLISH_ID = 'pl';\nconst PORTUGUESE_BR_ID = 'pt-br';\nconst PORTUGUESE_ID = 'pt';\nconst ROMANIAN_ID = 'ro';\nconst RUSSIAN_ID = 'ru';\nconst SPANISH_ID = 'es';\nconst SPANISH_419_ID = 'es-419';\nconst SWEDISH_ID = 'sv';\nconst TURKISH_ID = 'tr';\nconst WELSH_ID = 'cy';\n\n/**\n * Class for the text2speech blocks.\n * @constructor\n */\nclass Scratch3Text2SpeechBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n /**\n * Map of soundPlayers by sound id.\n * @type {Map}\n */\n this._soundPlayers = new Map();\n\n this._stopAllSpeech = this._stopAllSpeech.bind(this);\n if (this.runtime) {\n this.runtime.on('PROJECT_STOP_ALL', this._stopAllSpeech);\n }\n\n this._onTargetCreated = this._onTargetCreated.bind(this);\n if (this.runtime) {\n runtime.on('targetWasCreated', this._onTargetCreated);\n }\n\n /**\n * A list of all Scratch locales that are supported by the extension.\n * @type {Array}\n */\n this._supportedLocales = this._getSupportedLocales();\n }\n\n /**\n * An object with info for each voice.\n */\n get VOICE_INFO () {\n return {\n [ALTO_ID]: {\n name: formatMessage({\n id: 'text2speech.alto',\n default: 'alto',\n description: 'Name for a voice with ambiguous gender.'\n }),\n gender: 'female',\n playbackRate: 1\n },\n [TENOR_ID]: {\n name: formatMessage({\n id: 'text2speech.tenor',\n default: 'tenor',\n description: 'Name for a voice with ambiguous gender.'\n }),\n gender: 'male',\n playbackRate: 1\n },\n [SQUEAK_ID]: {\n name: formatMessage({\n id: 'text2speech.squeak',\n default: 'squeak',\n description: 'Name for a funny voice with a high pitch.'\n }),\n gender: 'female',\n playbackRate: 1.19 // +3 semitones\n },\n [GIANT_ID]: {\n name: formatMessage({\n id: 'text2speech.giant',\n default: 'giant',\n description: 'Name for a funny voice with a low pitch.'\n }),\n gender: 'male',\n playbackRate: 0.84 // -3 semitones\n },\n [KITTEN_ID]: {\n name: formatMessage({\n id: 'text2speech.kitten',\n default: 'kitten',\n description: 'A baby cat.'\n }),\n gender: 'female',\n playbackRate: 1.41 // +6 semitones\n }\n };\n }\n\n /**\n * An object with information for each language.\n *\n * A note on the different sets of locales referred to in this extension:\n *\n * SCRATCH LOCALE\n * Set by the editor, and used to store the language state in the project.\n * Listed in l10n: https://github.com/LLK/scratch-l10n/blob/master/src/supported-locales.js\n * SUPPORTED LOCALE\n * A Scratch locale that has a corresponding extension locale.\n * EXTENSION LOCALE\n * A locale corresponding to one of the available spoken languages\n * in the extension. There can be multiple supported locales for a single\n * extension locale. For example, for both written versions of chinese,\n * zh-cn and zh-tw, we use a single spoken language (Mandarin). So there\n * are two supported locales, with a single extension locale.\n * SPEECH SYNTH LOCALE\n * A different locale code system, used by our speech synthesis service.\n * Each extension locale has a speech synth locale.\n */\n get LANGUAGE_INFO () {\n return {\n [ARABIC_ID]: {\n name: 'Arabic',\n locales: ['ar'],\n speechSynthLocale: 'arb',\n singleGender: true\n },\n [CHINESE_ID]: {\n name: 'Chinese (Mandarin)',\n locales: ['zh-cn', 'zh-tw'],\n speechSynthLocale: 'cmn-CN',\n singleGender: true\n },\n [DANISH_ID]: {\n name: 'Danish',\n locales: ['da'],\n speechSynthLocale: 'da-DK'\n },\n [DUTCH_ID]: {\n name: 'Dutch',\n locales: ['nl'],\n speechSynthLocale: 'nl-NL'\n },\n [ENGLISH_ID]: {\n name: 'English',\n locales: ['en'],\n speechSynthLocale: 'en-US'\n },\n [FRENCH_ID]: {\n name: 'French',\n locales: ['fr'],\n speechSynthLocale: 'fr-FR'\n },\n [GERMAN_ID]: {\n name: 'German',\n locales: ['de'],\n speechSynthLocale: 'de-DE'\n },\n [HINDI_ID]: {\n name: 'Hindi',\n locales: ['hi'],\n speechSynthLocale: 'hi-IN',\n singleGender: true\n },\n [ICELANDIC_ID]: {\n name: 'Icelandic',\n locales: ['is'],\n speechSynthLocale: 'is-IS'\n },\n [ITALIAN_ID]: {\n name: 'Italian',\n locales: ['it'],\n speechSynthLocale: 'it-IT'\n },\n [JAPANESE_ID]: {\n name: 'Japanese',\n locales: ['ja', 'ja-hira'],\n speechSynthLocale: 'ja-JP'\n },\n [KOREAN_ID]: {\n name: 'Korean',\n locales: ['ko'],\n speechSynthLocale: 'ko-KR',\n singleGender: true\n },\n [NORWEGIAN_ID]: {\n name: 'Norwegian',\n locales: ['nb', 'nn'],\n speechSynthLocale: 'nb-NO',\n singleGender: true\n },\n [POLISH_ID]: {\n name: 'Polish',\n locales: ['pl'],\n speechSynthLocale: 'pl-PL'\n },\n [PORTUGUESE_BR_ID]: {\n name: 'Portuguese (Brazilian)',\n locales: ['pt-br'],\n speechSynthLocale: 'pt-BR'\n },\n [PORTUGUESE_ID]: {\n name: 'Portuguese (European)',\n locales: ['pt'],\n speechSynthLocale: 'pt-PT'\n },\n [ROMANIAN_ID]: {\n name: 'Romanian',\n locales: ['ro'],\n speechSynthLocale: 'ro-RO',\n singleGender: true\n },\n [RUSSIAN_ID]: {\n name: 'Russian',\n locales: ['ru'],\n speechSynthLocale: 'ru-RU'\n },\n [SPANISH_ID]: {\n name: 'Spanish (European)',\n locales: ['es'],\n speechSynthLocale: 'es-ES'\n },\n [SPANISH_419_ID]: {\n name: 'Spanish (Latin American)',\n locales: ['es-419'],\n speechSynthLocale: 'es-US'\n },\n [SWEDISH_ID]: {\n name: 'Swedish',\n locales: ['sv'],\n speechSynthLocale: 'sv-SE',\n singleGender: true\n },\n [TURKISH_ID]: {\n name: 'Turkish',\n locales: ['tr'],\n speechSynthLocale: 'tr-TR',\n singleGender: true\n },\n [WELSH_ID]: {\n name: 'Welsh',\n locales: ['cy'],\n speechSynthLocale: 'cy-GB',\n singleGender: true\n }\n };\n }\n\n /**\n * The key to load & store a target's text2speech state.\n * @return {string} The key.\n */\n static get STATE_KEY () {\n return 'Scratch.text2speech';\n }\n\n /**\n * The default state, to be used when a target has no existing state.\n * @type {Text2SpeechState}\n */\n static get DEFAULT_TEXT2SPEECH_STATE () {\n return {\n voiceId: ALTO_ID\n };\n }\n\n /**\n * A default language to use for speech synthesis.\n * @type {string}\n */\n get DEFAULT_LANGUAGE () {\n return ENGLISH_ID;\n }\n\n /**\n * @param {Target} target - collect state for this target.\n * @returns {Text2SpeechState} the mutable state associated with that target. This will be created if necessary.\n * @private\n */\n _getState (target) {\n let state = target.getCustomState(Scratch3Text2SpeechBlocks.STATE_KEY);\n if (!state) {\n state = Clone.simple(Scratch3Text2SpeechBlocks.DEFAULT_TEXT2SPEECH_STATE);\n target.setCustomState(Scratch3Text2SpeechBlocks.STATE_KEY, state);\n }\n return state;\n }\n\n /**\n * When a Target is cloned, clone the state.\n * @param {Target} newTarget - the newly created target.\n * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any.\n * @listens Runtime#event:targetWasCreated\n * @private\n */\n _onTargetCreated (newTarget, sourceTarget) {\n if (sourceTarget) {\n const state = sourceTarget.getCustomState(Scratch3Text2SpeechBlocks.STATE_KEY);\n if (state) {\n newTarget.setCustomState(Scratch3Text2SpeechBlocks.STATE_KEY, Clone.simple(state));\n }\n }\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n // Only localize the default input to the \"speak\" block if we are in a\n // supported language.\n let defaultTextToSpeak = 'hello';\n if (this.isSupportedLanguage(this.getEditorLanguage())) {\n defaultTextToSpeak = formatMessage({\n id: 'text2speech.defaultTextToSpeak',\n default: 'hello',\n description: 'hello: the default text to speak'\n });\n }\n\n return {\n id: 'text2speech',\n name: formatMessage({\n id: 'text2speech.categoryName',\n default: 'Text to Speech',\n description: 'Name of the Text to Speech extension.'\n }),\n blockIconURI: blockIconURI,\n menuIconURI: menuIconURI,\n blocks: [\n {\n opcode: 'speakAndWait',\n text: formatMessage({\n id: 'text2speech.speakAndWaitBlock',\n default: 'speak [WORDS]',\n description: 'Speak some words.'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n WORDS: {\n type: ArgumentType.STRING,\n defaultValue: defaultTextToSpeak\n }\n }\n },\n {\n opcode: 'setVoice',\n text: formatMessage({\n id: 'text2speech.setVoiceBlock',\n default: 'set voice to [VOICE]',\n description: 'Set the voice for speech synthesis.'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n VOICE: {\n type: ArgumentType.STRING,\n menu: 'voices',\n defaultValue: ALTO_ID\n }\n }\n },\n {\n opcode: 'setLanguage',\n text: formatMessage({\n id: 'text2speech.setLanguageBlock',\n default: 'set language to [LANGUAGE]',\n description: 'Set the language for speech synthesis.'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n LANGUAGE: {\n type: ArgumentType.STRING,\n menu: 'languages',\n defaultValue: this.getCurrentLanguage()\n }\n }\n }\n ],\n menus: {\n voices: {\n acceptReporters: true,\n items: this.getVoiceMenu()\n },\n languages: {\n acceptReporters: true,\n items: this.getLanguageMenu()\n }\n }\n };\n }\n\n /**\n * Get the language code currently set in the editor, or fall back to the\n * browser locale.\n * @return {string} a Scratch locale code.\n */\n getEditorLanguage () {\n const locale = formatMessage.setup().locale ||\n navigator.language || navigator.userLanguage || this.DEFAULT_LANGUAGE;\n return locale.toLowerCase();\n }\n\n /**\n * Get the language code currently set for the extension.\n * @returns {string} a Scratch locale code.\n */\n getCurrentLanguage () {\n const stage = this.runtime.getTargetForStage();\n if (!stage) return this.DEFAULT_LANGUAGE;\n // If no language has been set, set it to the editor locale (or default).\n if (!stage.textToSpeechLanguage) {\n this.setCurrentLanguage(this.getEditorLanguage());\n }\n return stage.textToSpeechLanguage;\n }\n\n /**\n * Set the language code for the extension.\n * It is stored in the stage so it can be saved and loaded with the project.\n * @param {string} locale a locale code.\n */\n setCurrentLanguage (locale) {\n const stage = this.runtime.getTargetForStage();\n if (!stage) return;\n\n if (this.isSupportedLanguage(locale)) {\n stage.textToSpeechLanguage = this._getExtensionLocaleForSupportedLocale(locale);\n }\n\n // Support language names dropped onto the menu via reporter block\n // such as a variable containing a language name (in any language),\n // or the translate extension's language reporter.\n const localeForDroppedName = languageNames.nameMap[locale.toLowerCase()];\n if (localeForDroppedName && this.isSupportedLanguage(localeForDroppedName)) {\n stage.textToSpeechLanguage =\n this._getExtensionLocaleForSupportedLocale(localeForDroppedName);\n }\n\n // If the language is null, set it to the default language.\n // This can occur e.g. if the extension was loaded with the editor\n // set to a language that is not in the list.\n if (!stage.textToSpeechLanguage) {\n stage.textToSpeechLanguage = this.DEFAULT_LANGUAGE;\n }\n }\n\n /**\n * Get the extension locale for a supported locale, or null.\n * @param {string} locale a locale code.\n * @returns {?string} a locale supported by the extension.\n */\n _getExtensionLocaleForSupportedLocale (locale) {\n for (const lang in this.LANGUAGE_INFO) {\n if (this.LANGUAGE_INFO[lang].locales.includes(locale)) {\n return lang;\n }\n }\n log.error(`cannot find extension locale for locale ${locale}`);\n }\n\n /**\n * Get the locale code used by the speech synthesis server corresponding to\n * the current language code set for the extension.\n * @returns {string} a speech synthesis locale.\n */\n _getSpeechSynthLocale () {\n let speechSynthLocale = this.LANGUAGE_INFO[this.DEFAULT_LANGUAGE].speechSynthLocale;\n if (this.LANGUAGE_INFO[this.getCurrentLanguage()]) {\n speechSynthLocale = this.LANGUAGE_INFO[this.getCurrentLanguage()].speechSynthLocale;\n }\n return speechSynthLocale;\n }\n\n /**\n * Get an array of the locales supported by this extension.\n * @returns {Array} An array of locale strings.\n */\n _getSupportedLocales () {\n return Object.keys(this.LANGUAGE_INFO).reduce((acc, lang) =>\n acc.concat(this.LANGUAGE_INFO[lang].locales), []);\n }\n\n /**\n * Check if a Scratch language code is in the list of supported languages for the\n * speech synthesis service.\n * @param {string} languageCode the language code to check.\n * @returns {boolean} true if the language code is supported.\n */\n isSupportedLanguage (languageCode) {\n return this._supportedLocales.includes(languageCode);\n }\n\n /**\n * Get the menu of voices for the \"set voice\" block.\n * @return {array} the text and value for each menu item.\n */\n getVoiceMenu () {\n return Object.keys(this.VOICE_INFO).map(voiceId => ({\n text: this.VOICE_INFO[voiceId].name,\n value: voiceId\n }));\n }\n\n /**\n * Get the localized menu of languages for the \"set language\" block.\n * For each language:\n * if there is a custom translated spoken language name, use that;\n * otherwise use the translation in the languageNames menuMap;\n * otherwise fall back to the untranslated name in LANGUAGE_INFO.\n * @return {array} the text and value for each menu item.\n */\n getLanguageMenu () {\n const editorLanguage = this.getEditorLanguage();\n // Get the array of localized language names\n const localizedNameMap = {};\n let nameArray = languageNames.menuMap[editorLanguage];\n if (nameArray) {\n // Also get any localized names of spoken languages\n let spokenNameArray = [];\n if (languageNames.spokenLanguages) {\n spokenNameArray = languageNames.spokenLanguages[editorLanguage];\n nameArray = nameArray.concat(spokenNameArray);\n }\n // Create a map of language code to localized name\n // The localized spoken language names have been concatenated onto\n // the end of the name array, so the result of the forEach below is\n // when there is both a written language name (e.g. 'Chinese\n // (simplified)') and a spoken language name (e.g. 'Chinese\n // (Mandarin)', we always use the spoken version.\n nameArray.forEach(lang => {\n localizedNameMap[lang.code] = lang.name;\n });\n }\n\n return Object.keys(this.LANGUAGE_INFO).map(key => {\n let name = this.LANGUAGE_INFO[key].name;\n const localizedName = localizedNameMap[key];\n if (localizedName) {\n name = localizedName;\n }\n // Uppercase the first character of the name\n name = name.charAt(0).toUpperCase() + name.slice(1);\n return {\n text: name,\n value: key\n };\n });\n }\n\n /**\n * Set the voice for speech synthesis for this sprite.\n * @param {object} args Block arguments\n * @param {object} util Utility object provided by the runtime.\n */\n setVoice (args, util) {\n const state = this._getState(util.target);\n\n let voice = args.VOICE;\n\n // If the arg is a dropped number, treat it as a voice index\n let voiceNum = parseInt(voice, 10);\n if (!isNaN(voiceNum)) {\n voiceNum -= 1; // Treat dropped args as one-indexed\n voiceNum = MathUtil.wrapClamp(voiceNum, 0, Object.keys(this.VOICE_INFO).length - 1);\n voice = Object.keys(this.VOICE_INFO)[voiceNum];\n }\n\n // Only set the voice if the arg is a valid voice id.\n if (Object.keys(this.VOICE_INFO).includes(voice)) {\n state.voiceId = voice;\n }\n }\n\n /**\n * Set the language for speech synthesis.\n * @param {object} args Block arguments\n */\n setLanguage (args) {\n this.setCurrentLanguage(args.LANGUAGE);\n }\n\n /**\n * Stop all currently playing speech sounds.\n */\n _stopAllSpeech () {\n this._soundPlayers.forEach(player => {\n player.stop();\n });\n }\n\n /**\n * Convert the provided text into a sound file and then play the file.\n * @param {object} args Block arguments\n * @param {object} util Utility object provided by the runtime.\n * @return {Promise} A promise that resolves after playing the sound\n */\n speakAndWait (args, util) {\n // Cast input to string\n let words = Cast.toString(args.WORDS);\n let locale = this._getSpeechSynthLocale();\n\n const state = this._getState(util.target);\n\n let gender = this.VOICE_INFO[state.voiceId].gender;\n let playbackRate = this.VOICE_INFO[state.voiceId].playbackRate;\n\n // Special case for voices where the synthesis service only provides a\n // single gender voice. In that case, always request the female voice,\n // and set special playback rates for the tenor and giant voices.\n if (this.LANGUAGE_INFO[this.getCurrentLanguage()].singleGender) {\n gender = 'female';\n if (state.voiceId === TENOR_ID) {\n playbackRate = FEMALE_TENOR_RATE;\n }\n if (state.voiceId === GIANT_ID) {\n playbackRate = FEMALE_GIANT_RATE;\n }\n }\n\n if (state.voiceId === KITTEN_ID) {\n words = words.replace(/\\S+/g, 'meow');\n locale = this.LANGUAGE_INFO[this.DEFAULT_LANGUAGE].speechSynthLocale;\n }\n\n // Build up URL\n let path = `${SERVER_HOST}/synth`;\n path += `?locale=${locale}`;\n path += `&gender=${gender}`;\n path += `&text=${encodeURIComponent(words.substring(0, 128))}`;\n\n // Perform HTTP request to get audio file\n return fetchWithTimeout(path, {}, SERVER_TIMEOUT)\n .then(res => {\n if (res.status !== 200) {\n throw new Error(`HTTP ${res.status} error reaching translation service`);\n }\n\n return res.arrayBuffer();\n })\n .then(buffer => {\n // Play the sound\n const sound = {\n data: {\n buffer\n }\n };\n return this.runtime.audioEngine.decodeSoundPlayer(sound);\n })\n .then(soundPlayer => {\n this._soundPlayers.set(soundPlayer.id, soundPlayer);\n\n soundPlayer.setPlaybackRate(playbackRate);\n\n // Increase the volume\n const engine = this.runtime.audioEngine;\n const chain = engine.createEffectChain();\n chain.set('volume', SPEECH_VOLUME);\n soundPlayer.connect(chain);\n\n soundPlayer.play();\n return new Promise(resolve => {\n soundPlayer.on('stop', () => {\n this._soundPlayers.delete(soundPlayer.id);\n resolve();\n });\n });\n })\n .catch(err => {\n log.warn(err);\n });\n }\n}\nmodule.exports = Scratch3Text2SpeechBlocks;\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Cast = require('../../util/cast');\nconst log = require('../../util/log');\nconst fetchWithTimeout = require('../../util/fetch-with-timeout');\nconst languageNames = require('scratch-translate-extension-languages');\nconst formatMessage = require('format-message');\n\n/**\n * Icon svg to be displayed in the blocks category menu, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst menuIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAACXBIWXMAABYlAAAWJQFJUiTwAAAGAklEQVRYhe1YbUxTVxh+rh02o0KtkOEgKA4U4yeRWCdgxDoxCnH6h22iqSz76aasZlnijzkTBlvS4TJ/LGaJsmiyESe4hAVJvMJGxwQhLKECcRWkpWNZERs6Ctb2Lm97C/fe3n6Jyfzhk5y09z3nPPe57znnPe85DMdxeJ6x6LlW90LgM8BLchR1dXUZeXl5b3Ect+ppXsEwzHBfX98PVVVVY0GbmjW2AdgpaFYP4JxTZ+iLyCVdJFeuXNmdn59fn56enrFkyRIsWhSfk30+H1wuF+x2+1hPT4++oqLiJi/wEoA8AJslXSqdOsOlmARWV1dnlpeXd2ZnZ2fEK0xOqMViGWtoaNh++vRpa9CuZo1ZAJokQlc5dYYROR6RCq1WW56WlhZV3H0H8O9sZIHEQVzEKbTzQooBPBCYz4TlET4oFIosGtZoOHUN+Ph61GYgLuIU2tSscSmAYwAeCcx6NWs8o2aNxVKOkEUi9R55qv428Ng7b3viA/6eAs7dmrctVgD6bYBKGZ6LB4mrk7F/whcmokApfh8BWu6G2mc8ADsktuWmAbtzozGiLUJdu9QQVSC98JUkYNgBfPsboH4Z+GhPoK62FZiaAU7sCrTZmB5VHM3BPjVrrARwUVL1B4CD0vYxLVV68YFNQIICcLrn7SROtTjwEbGIE4iksFIpEVfs1BkeSdvGFUsObAz8Gm8CNTcC/49q42EIEbkLwKfhxCGWIRZC/zrQ/ifgcAWMK5YB+zc8nUBeZFuUORmfQIp/PsHGM/04YMta5oPT6cTs7Cw8Ho+oj9vtzmloaCgPZQtApVI96ejo6K2trR3lOM4nrRftJCzLfq3T6Y7LCfvuNtDL7wepfKgkTz6ZdeHdzePYlq30xz2lUintHhH0UbQ12my2+oKCguMcx7mE7aOHmWHgsxvzzzQP3ysMxMfzt2bxKmNHyZblSE5OjktYEImJidBoNFCr1frOzs5khmHe4Thubp8SCVQoFBwNUUJCwpyNwsfyZGBDOvB2fuCZQAH56KYJKJUpTy1OCOJYvXr1ocbGxjIAPwarRKvYZrNdn5iYEHV8LRW4cBj4oHheXBDT09PPRFwQxKXRaIQpmVjgkSNHfrFardcmJydjIqSMRehtOfzjmMTZmm/8hf5HAnF5vV7RVicSyHGcR6vVHh4YGPjKYrFMkTelq5JAH0B1MzMzUT+iu6cfdwfv+wv9jxchgZomaEFBwcmcnJxVY2NjXQqFQlQ/Pj6O/v7+s2az+U2Hw9Ec7X3tHXfm/v/c2hG3wLCruLm5+VBGRoY2mJGQJ0nc4ODgqZKSkjqKWSzL7olEPjJqx4PRv5CaqvE/OxyTflvWitj3xbBbnUql2kRxjYTRcA4MDHR1d3frguJiIW//NeC9/SVF2LplvcgWK8J6sKWl5UuVSrXO4/HYHj58+FNZWVkLx3HT8Rz0u3vN/t8Ho3aRaH3FgYULrKmpodT8jeBzvDcQ3T1m/5D6RXX0zNmn3TP+uq356xcmkE/NTwLoc+oMTXGpA3CnN7Bi99Hw5s8PL4mlulgFys5BXlwbn4I3qlnjsXgFBr22f+8OrFub7S/79u4Q1cWCEA8KxAmPhRfVrBFy51cK1nJnj+/rvwix0eqVswu5pJDzoPTMKhSZJzQolUoLZSLPCsRFnEI6OYE7I7xPdGYoKiq6YLVaByllWiiIg7iIM5rAYBouBB2yq5w6g+iATWGnqampZGhoqItiJSUP4YrcR9CQUh31JQ7iIk5hm7AXmPxdip5/dNIUCnduYBgm8fLly9tzc3NLwzlSqVTuW7NmzVphQkubwL179+xdXV3HKisrTVJxiJKwnuGHVM2XNjVrPCh3h8IT3+SLLKqrq+tKS0uvrly5UksJKvjsJSkpKd3r9TrkxCGSBxHwIoWXc7zAIOiIOOLUGULOsNHAMIzSZDJ9npmZeSIlJcWfTdPQm0ym8zqd7n257hGPnXxYyePv8py8mVb40ji1+UGZUmFh4Yetra1bzGbzteHh4SlKQNxu961wff7XS3Sau/w0c4VLQF7c8i8IAP4DcHKth/4Ur7MAAAAASUVORK5CYII=';\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAACXBIWXMAABYlAAAWJQFJUiTwAAAN+UlEQVR4Ae1ce2xT1xn/Tkhq4hqHJKRLDAlQGI+GUfFc14HaLmxuGd0ab93GgK6Vmm01y9BUsaU0RfyRFTakaRHq3So6jVapWEUxa9dRuU8x6IAGCoO6wa1KXiSQOE9jkjivO/2u7yWOuff6XvvekFb5SUdx7ON7v/vz9zrnO+cwnudpAokjZYK75DBBYJKYIDBJTBCYJCYITBJfOgIZYzbGWA5jLJ8xNm/z5s334a/4P1omYyzNsPt9WdIYxhiUId/j8azPz89fY7VaF6ampjqi+4TD4Qvd3d0f7t+/fx/HcTVE1M7z/EBS99VDoCikTWzWMdTgYSLqIaIQGs/zwzKyOaqrq1+aOnVqUUZGBqWnp5PFYqG0tDQaHh6mvr4+GhwcpFAoJLSenp4P9+7dW8Zx3Fme5zsTFSxVa0eovtPpnL1ly5YfzJgxw2WxWBYketNEAO25dOmSp7Ky8iBjrFbmoW12u70oOzubMjMzR32QkpJCVqtVeG2322lgYIACgcDK0tLS1+6+++4yxpiH5/krCQkGDYzXiCi3qqrKXVNTE7x48SLf0dHB9/X18WMF3Av3xL0hA2SBTNFyE9E8v9/PX758WbNUuCa+4/F4ymKvp7XF1UDGWK7H43m0sLBwZ05Ozg2/7lgApoiGe3d2dk5ZsWLFcx6Px84Y2xelOcHW1taXiOiRcDgsaJ2gljab0GDKscD1Jk2ahHd3ejwePOtenufb9TySKoEw26qqKhfIy83NFdT/ZiP6oauqqoKMsf2iObeuXr26vKKiwpOVlTXFbrfb8/LyFubk5KyBu1H68aVnGh4e3uZ2u08yxo7pCixKqgrX4XQ6l8JkoOrjDZAJskFGyBolt0UMcGhgJ19yP2rm3dDQwJ85c+akXlNWI9B++PDh38PvjFdANsgIWVUfksheUVHxfZDY3d0t+zT9/f2CP3S73feJWYZFC4FqJmxDtJ06daphRnuiluhrDqJbLcZcD7JBRiJ6Dj5Qrg9jDHfLhlm3tbUdslgsj8i5IvhI+MpNmzaVLlq0yCN+9wMiCvA8H1KSQY1AK3yHFP6ThfcTouePEaVNItrzI6LbpiR/TcgmplOyQmLE4XQ6C3fv3v0y+iEQIbggjZELKiB2+vTpxXl5ecUIRD6fr/nQoUNuxtjbPM/3yN1DLREWPsNNjUBNC9HgMFEKI2q5asglo2VTeo7sioqKP6anpwtBZObMmZSfny9LHokRG5+jn8PhoFtuucVRXFzMQdmVZPiyTyakIblGBqE3/QKZIBLDQafTmavUT/NIRA3XwkSlB4jaFD3FCPqHiJ5+Xb3PNBvRnoeN85UYyiUCmDHg9XoVn8wQDTxRR9RxjQjxLl4DgfH64Fq4pgEY6OjoONTe3q6bRPTH9zBmFsfgsjBEA4vmE/kuE30eiPzfN0B0WYyJGelEWQpx6FJXhNC0FKIcG9Fk0TUVZEWuaQBad+zYUbF79+6FjY2NC+DfpBGKGkBeY2MjBYPBC2VlZU9g1sZUAoFf3zvyGia9/u8RbRrmiSofvrG/1IeEaEn0/E+NkmQEmLVhjPmIaAMisRYSo8nbunXrBq/X6+N5PqzU35QgAt8177bI655+ovPNN/Z50xeJyIyIlswwQ4oI8PAgAWSAFJCjZM56ySMzo/DP7iJKTSEaGibi/jP6M2jfwbNEQzzRpBSin68yS4oItJCYCHlkJoEYcWSKvu9KkOjwxyOf7T9F1DsQ0b6CTGOS6niQIxEJNSVBHhnpA+Xwm28RPfOvSAL9t+NEy2cShcJEr5+P+EcQ+PT9ZkowGiBF8olIsMPhcBFmrnt7e4Voi4Chhzwym0BoYWFexAcODBGVvUbU3RshbxIjWrdobLQvGhKJXq+3xO12z1q3bt2aN9544x2O45A4Neshj8wmENjmJHr8ZaJQ/0iizcRk+fFvmn13eYgk1TLGWjiOwxQWKY1148H0oRwiMohiUYky8Oz3zL5zfIA0qSV6DdM18NWPiF4+FXnNogh84h9EW9cQ3TV7pC+ceVTVTNPooa5OGLLMYkgmE0fcqp8STCPwYhvRn94jauqKBBHJbOED4Q8xAtn1FtH8rxA9WUSUNthJiQy5Zs2aRX6/35usvBqqfrJQrAujmu/3+/3z5s3TJQgCxsEzRP9riuSAUsBYXhCJysCWVyP+EHkgiA0Hr9D9Xw3S/QuJJlvShIlSca4vASr0A5MG0Piuri68vnr69OmyjRs3aip1GqaByPNePRuZCMDwTSIuPY1oy32jTfWFDUQvniB67RzRta4rtDwvSN8tJBrnVT9ZGEIgpupf+C/RgGh90CpMEKwtJFq/XH5aCiOVb0zvpH+fipD3Baj6yUKNQIEOqLcWU4IPx9ANGue6k+iBQvX5PPi6lHD7dc0bD+RJgCxDQ0O0bNmyXU6n8wRjrFsxsKhUshznzp2r0VrSbAny/OcB7eU7VMdQBfuiV/3U8sAQohIcqxZgRHH7NO2/MlIVEitr4xVRVT+bkoiqBCKkIyp1dia8eEkRiHokVtbGK+JV/VQJhM17vd5ahPRAIICZCkMfU8r3xipVSQQaqn7qURjRB0u/ENIRleBYb0aaEQ+Btk4qfXLnqF7bn/oF3bFgjun3jjsWRh7kcrn2VVdXb25pablaW1uLXOl6xWo84LD36A1SHDl6akwk05QHgkTkQwjpsQsstSa/V65cGeUGMIbFMMwIyJFVfdpHj2zopVut6YbcQwmaZ2Ngzl6v9+zatWv/sHjx4u9UVlYKAzMUoOMB5HV1dV09cODAQ/NFOJ1OpxEPAKJ6evtueB/vnTrtM+IWqtA1nYXAwvM81GgAi3CUFi5KQKBobm4WyDt48OCm8vLy93me/xQNSmjEAxw5NqJ91vTJQpNQ/dE4I5AikwxWjuPWZGVlFWM9shLgI+vr6zHDcoHjuAdF8gwN5dd6eulUFEkrlhUKTQI+Q4AxE4lMqOasWrXqafg9uRREquiDvKamppe2bdv2kz179pwwmjyS8X3Lly4SWjSqT38s/2WDkMhkggUBJHbsCuIQndH6+/ubjxw58ju32/0eXKCeCUo9iDbfadMyr2sfzFjyi4ffOkZrnatNoi8xAoX1JjabrRhDnehZZBDn9/v3uFyuV+ItTEwWdQ3NVN9w+fpVViwdMV0QeeTYaeF1W1un0HdWgcNoEQQkYsJd27dvfxa+7dKlS9TQ0NBcV1d36OTJk5sLCwtXuVwujuf5WjPJIxnzvWf18uuvY834TZk80Sjo1kBxdPLp4sWLizBnKr4NewnqLQkmg1gCy575s+LVkOo8UWKOHAlV5cSAgGmadnG/WWCsyZPL/ZSAvmaNTHRroLho2xFdlGaMXRSDxZiQGJvf3bHgdtl+n1y4OOo70WZuFHQRCPKwaHvXrl1/sVqtK7EsYs6cOdsee+yxd8vLy3+Lir/ZJCKvi879Zhbk0fanfinb91dP7hSCCEXlhDnTjJ0M0WzCEnlYZ5eZmbly7ty5woLs2bNnYzxchPfxuaihpiE2r7tnlbJWRUdmMikn1ERgNHl2u33UIkUM5fA/3h8LEpHXRWPFskWKfWNNNva7RkDLZkNF8iTgf7yPFaDoh9VPZpnzvVEaZ7VOVjVJ5H4/fOjbRoswGnG2SIG8pSgu1dXV8UNDQ6pFGHyOfugv7mFT3C5FRHNRVBrLbbN6AdkgI2RNZK+cLvIkyJCYonB9XVW/mwHIBhkhayJVuWxEWyWzVYJkzjabbcGOHTvKUbBT6Kqr6nczANkgo9o2BzVWbEhVMGWllbzrF01JIXwPU16IMwrdTK36JQuxbHEVMiZEoNPpFKaaE62axSPd7KpfMoAskAmyQUa12STFKOz1egODg4PN9fX1Dqxb0TJ1LwG/HuYEg8Hgu5i9Ueo3Hqt+kB3k+Xy+p8QVWqrmoZbGtGOrJ3YrNjc3O6StonhAOTKx4h21D0xvYTYa6+0wOlHb5UMjBat9WMiDtShdXV1TvkjL21TPjcEpQJiB5jhOWM28ZMkSV0FBQTG2gsoBJU/MQp8/f/5tt9v9gZ5F20Yeq4LJXlhNPEjaRjELLEWz1eaYNR57AlWwYTs88iJsj5cDFgxhWz2218fbhq90ToN4zgF+obk4ykRvk7b2azn+JGabf754b9m0S3ceqPCAuTiYAQc0KAGCR53tIgklHQKh6RyCZFr0+QhNTU1x81f0OX78uMe0c2Ni0I7jknDiTzAYxNEiN3SA6VgsFmGV57lz50oDgcA7fr9f2ErQ0dFxlTH2ERE1mVUnwVwlY+z9lpaWB0tKSv4aDocXwOUo+VOkW6FQqJjjOI94gpG+FfsJmFk2TvqB6iudgCGZBzJ5aCsaRif4ztGjR19M9NfWKadl3bp1d+J+uG9bW5uiNra2tkojjpm675OgcLkSiXqGYjBvcWw5z2wC+RGf6uA4bqPP52v67LPPZImMGvPqliuhNdKxqUdvb+8UrJFRW6VAN2Epm+gmmhlj/ySiDzwez4/nz59f2tnZ6ZCOhEJqhvQFlUa1nFUJCS8ylxYcud3umpKSkl2hUGilJFRqaipNnjz5+hEjyLOwoa+7u1tKrk2t2MnIKmygYYzhBI5XqqqqHpgzZ84au93+dRwqgRQGlUaxzqMLSR/AKJ4Gme12uxeuX7/+0YyMjJWxORxGND09PTWNjY3vuFyu/UTUaFYQ0SizRcwOpIU0Q2JVUf8Pa6C/AZGZYuqSL+VkYh6H/3OQS46F7xvLNnGSeZKYOMU3SUwQmCQmCEwSEwQmiQkCkwER/R+aET3lwEIlXgAAAABJRU5ErkJggg==';\n\n/**\n * The url of the translate server.\n * @type {string}\n */\nconst serverURL = 'https://translate-service.scratch.mit.edu/';\n\n/**\n * How long to wait in ms before timing out requests to translate server.\n * @type {int}\n */\nconst serverTimeoutMs = 10000; // 10 seconds (chosen arbitrarily).\n\n/**\n * Class for the translate block in Scratch 3.0.\n * @constructor\n */\nclass Scratch3TranslateBlocks {\n constructor () {\n /**\n * Language code of the viewer, based on their locale.\n * @type {string}\n * @private\n */\n this._viewerLanguageCode = this.getViewerLanguageCode();\n\n /**\n * List of supported language name and language code pairs, for use in the block menu.\n * Filled in by getInfo so it is updated when the interface language changes.\n * @type {Array.>}\n * @private\n */\n this._supportedLanguages = [];\n\n /**\n * A randomly selected language code, for use as the default value in the language menu.\n * Properly filled in getInfo so it is updated when the interface languages changes.\n * @type {string}\n * @private\n */\n this._randomLanguageCode = 'en';\n\n\n /**\n * The result from the most recent translation.\n * @type {string}\n * @private\n */\n this._translateResult = '';\n\n /**\n * The language of the text most recently translated.\n * @type {string}\n * @private\n */\n this._lastLangTranslated = '';\n\n /**\n * The text most recently translated.\n * @type {string}\n * @private\n */\n this._lastTextTranslated = '';\n }\n\n /**\n * The key to load & store a target's translate state.\n * @return {string} The key.\n */\n static get STATE_KEY () {\n return 'Scratch.translate';\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n this._supportedLanguages = this._getSupportedLanguages(this.getViewerLanguageCode());\n this._randomLanguageCode = this._supportedLanguages[\n Math.floor(Math.random() * this._supportedLanguages.length)].value;\n\n return {\n id: 'translate',\n name: formatMessage({\n id: 'translate.categoryName',\n default: 'Translate',\n description: 'Name of extension that adds translate blocks'\n }),\n blockIconURI: blockIconURI,\n menuIconURI: menuIconURI,\n blocks: [\n {\n opcode: 'getTranslate',\n text: formatMessage({\n id: 'translate.translateBlock',\n default: 'translate [WORDS] to [LANGUAGE]',\n description: 'translate some text to a different language'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n WORDS: {\n type: ArgumentType.STRING,\n defaultValue: formatMessage({\n id: 'translate.defaultTextToTranslate',\n default: 'hello',\n description: 'hello: the default text to translate'\n })\n },\n LANGUAGE: {\n type: ArgumentType.STRING,\n menu: 'languages',\n defaultValue: this._randomLanguageCode\n }\n }\n },\n {\n opcode: 'getViewerLanguage',\n text: formatMessage({\n id: 'translate.viewerLanguage',\n default: 'language',\n description: 'the languge of the project viewer'\n }),\n blockType: BlockType.REPORTER,\n arguments: {}\n }\n ],\n menus: {\n languages: {\n acceptReporters: true,\n items: this._supportedLanguages\n }\n }\n };\n }\n\n /**\n * Computes a list of language code and name pairs for the given language.\n * @param {string} code The language code to get the list of language pairs\n * @return {Array.>} An array of languge name and\n * language code pairs.\n * @private\n */\n _getSupportedLanguages (code) {\n return languageNames.menuMap[code].map(entry => {\n const obj = {text: entry.name, value: entry.code};\n return obj;\n });\n }\n /**\n * Get the human readable language value for the reporter block.\n * @return {string} the language name of the project viewer.\n */\n getViewerLanguage () {\n this._viewerLanguageCode = this.getViewerLanguageCode();\n const names = languageNames.menuMap[this._viewerLanguageCode];\n let langNameObj = names.find(obj => obj.code === this._viewerLanguageCode);\n\n // If we don't have a name entry yet, try looking it up via the Google langauge\n // code instead of Scratch's (e.g. for es-419 we look up es to get espanol)\n if (!langNameObj && languageNames.scratchToGoogleMap[this._viewerLanguageCode]) {\n const lookupCode = languageNames.scratchToGoogleMap[this._viewerLanguageCode];\n langNameObj = names.find(obj => obj.code === lookupCode);\n }\n\n let langName = this._viewerLanguageCode;\n if (langNameObj) {\n langName = langNameObj.name;\n }\n return langName;\n }\n\n /**\n * Get the viewer's language code.\n * @return {string} the language code.\n */\n getViewerLanguageCode () {\n const locale = formatMessage.setup().locale;\n const viewerLanguages = [locale].concat(navigator.languages);\n const languageKeys = Object.keys(languageNames.menuMap);\n // Return the first entry in viewerLanguages that matches\n // one of the available language keys.\n const languageCode = viewerLanguages.reduce((acc, lang) => {\n if (acc) {\n return acc;\n }\n if (languageKeys.indexOf(lang.toLowerCase()) > -1) {\n return lang;\n }\n return acc;\n }, '') || 'en';\n\n return languageCode.toLowerCase();\n }\n\n /**\n * Get a language code from a block argument. The arg can be a language code\n * or a language name, written in any language.\n * @param {object} arg A block argument.\n * @return {string} A language code.\n */\n getLanguageCodeFromArg (arg) {\n const languageArg = Cast.toString(arg).toLowerCase();\n // Check if the arg matches a language code in the menu.\n if (languageNames.menuMap.hasOwnProperty(languageArg)) {\n return languageArg;\n }\n // Check for a dropped-in language name, and convert to a language code.\n if (languageNames.nameMap.hasOwnProperty(languageArg)) {\n return languageNames.nameMap[languageArg];\n }\n\n // There are some languages we launched in the language menu that Scratch did not\n // end up launching in. In order to keep projects that may have had that menu item\n // working, check for those language codes and let them through.\n // Examples: 'ab', 'hi'.\n if (languageNames.previouslySupported.indexOf(languageArg) !== -1) {\n return languageArg;\n }\n // Default to English.\n return 'en';\n }\n\n /**\n * Translates the text in the translate block to the language specified in the menu.\n * @param {object} args - the block arguments.\n * @return {Promise} - a promise that resolves after the response from the translate server.\n */\n getTranslate (args) {\n // If the text contains only digits 0-9 and nothing else, return it without\n // making a request.\n if (/^\\d+$/.test(args.WORDS)) return Promise.resolve(args.WORDS);\n\n // Don't remake the request if we already have the value.\n if (this._lastTextTranslated === args.WORDS &&\n this._lastLangTranslated === args.LANGUAGE) {\n return this._translateResult;\n }\n\n const lang = this.getLanguageCodeFromArg(args.LANGUAGE);\n\n let urlBase = `${serverURL}translate?language=`;\n urlBase += lang;\n urlBase += '&text=';\n urlBase += encodeURIComponent(args.WORDS);\n\n const tempThis = this;\n const translatePromise = fetchWithTimeout(urlBase, {}, serverTimeoutMs)\n .then(response => response.text())\n .then(responseText => {\n const translated = JSON.parse(responseText).result;\n tempThis._translateResult = translated;\n // Cache what we just translated so we don't keep making the\n // same call over and over.\n tempThis._lastTextTranslated = args.WORDS;\n tempThis._lastLangTranslated = args.LANGUAGE;\n return translated;\n })\n .catch(err => {\n log.warn(`error fetching translate result! ${err}`);\n return '';\n });\n return translatePromise;\n }\n}\nmodule.exports = Scratch3TranslateBlocks;\n","const Runtime = require('../../engine/runtime');\n\nconst ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Clone = require('../../util/clone');\nconst Cast = require('../../util/cast');\nconst formatMessage = require('format-message');\nconst Video = require('../../io/video');\n\nconst VideoMotion = require('./library');\n\n/**\n * Icon svg to be displayed in the blocks category menu, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst menuIconURI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjIgKDY3MTQ1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5FeHRlbnNpb25zL1NvZnR3YXJlL1ZpZGVvLVNlbnNpbmctTWVudTwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSJFeHRlbnNpb25zL1NvZnR3YXJlL1ZpZGVvLVNlbnNpbmctTWVudSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9InZpZGVvLW1vdGlvbiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsIDUuMDAwMDAwKSIgZmlsbC1ydWxlPSJub256ZXJvIj4KICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC1Db3B5IiBmaWxsPSIjMEVCRDhDIiBvcGFjaXR5PSIwLjI1IiBjeD0iMTYiIGN5PSI4IiByPSIyIj48L2NpcmNsZT4KICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC1Db3B5IiBmaWxsPSIjMEVCRDhDIiBvcGFjaXR5PSIwLjUiIGN4PSIxNiIgY3k9IjYiIHI9IjIiPjwvY2lyY2xlPgogICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLUNvcHkiIGZpbGw9IiMwRUJEOEMiIG9wYWNpdHk9IjAuNzUiIGN4PSIxNiIgY3k9IjQiIHI9IjIiPjwvY2lyY2xlPgogICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsIiBmaWxsPSIjMEVCRDhDIiBjeD0iMTYiIGN5PSIyIiByPSIyIj48L2NpcmNsZT4KICAgICAgICAgICAgPHBhdGggZD0iTTExLjMzNTk3MzksMi4yMDk3ODgyNSBMOC4yNSw0LjIwOTk1NjQ5IEw4LjI1LDMuMDUgQzguMjUsMi4wNDQ4ODIyNyA3LjQ2ODU5MDMxLDEuMjUgNi41LDEuMjUgTDIuMDUsMS4yNSBDMS4wMzgwNzExOSwxLjI1IDAuMjUsMi4wMzgwNzExOSAwLjI1LDMuMDUgTDAuMjUsNyBDMC4yNSw3Ljk2MzY5OTM3IDEuMDQyMjQ5MTksOC43NTU5NDg1NiAyLjA1LDguOCBMNi41LDguOCBDNy40NTA4MzAwOSw4LjggOC4yNSw3Ljk3MzI3MjUgOC4yNSw3IEw4LjI1LDUuODU4NDUyNDEgTDguNjI4NjIzOTQsNi4wODU2MjY3NyBMMTEuNDI2Nzc2Nyw3Ljc3MzIyMzMgQzExLjQzNjg5NDMsNy43ODMzNDA5MSAxMS40NzU3NjU1LDcuOCAxMS41LDcuOCBDMTEuNjMzNDkzMiw3LjggMTEuNzUsNy42OTEyNjAzNCAxMS43NSw3LjU1IEwxMS43NSwyLjQgQzExLjc1LDIuNDE4MzgyNjkgMTEuNzIxOTAyOSwyLjM1MjgyMjgyIDExLjY4NTYyNjgsMi4yNzg2MjM5NCBDMTEuNjEyOTUyOCwyLjE1NzUwMDY5IDExLjQ3MDc5NjgsMi4xMjkwNjk1IDExLjMzNTk3MzksMi4yMDk3ODgyNSBaIiBpZD0idmlkZW9fMzdfIiBzdHJva2Utb3BhY2l0eT0iMC4xNSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAuNSIgZmlsbD0iIzRENEQ0RCI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+';\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst blockIconURI = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSIwIDAgNDAgNDAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjIgKDY3MTQ1KSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5FeHRlbnNpb25zL1NvZnR3YXJlL1ZpZGVvLVNlbnNpbmctQmxvY2s8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZyBpZD0iRXh0ZW5zaW9ucy9Tb2Z0d2FyZS9WaWRlby1TZW5zaW5nLUJsb2NrIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2Utb3BhY2l0eT0iMC4xNSI+CiAgICAgICAgPGcgaWQ9InZpZGVvLW1vdGlvbiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsIDEwLjAwMDAwMCkiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIj4KICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC1Db3B5IiBmaWxsPSIjRkZGRkZGIiBvcGFjaXR5PSIwLjI1IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGN4PSIzMiIgY3k9IjE2IiByPSI0LjUiPjwvY2lyY2xlPgogICAgICAgICAgICA8Y2lyY2xlIGlkPSJPdmFsLUNvcHkiIGZpbGw9IiNGRkZGRkYiIG9wYWNpdHk9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjeD0iMzIiIGN5PSIxMiIgcj0iNC41Ij48L2NpcmNsZT4KICAgICAgICAgICAgPGNpcmNsZSBpZD0iT3ZhbC1Db3B5IiBmaWxsPSIjRkZGRkZGIiBvcGFjaXR5PSIwLjc1IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGN4PSIzMiIgY3k9IjgiIHI9IjQuNSI+PC9jaXJjbGU+CiAgICAgICAgICAgIDxjaXJjbGUgaWQ9Ik92YWwiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY3g9IjMyIiBjeT0iNCIgcj0iNC41Ij48L2NpcmNsZT4KICAgICAgICAgICAgPHBhdGggZD0iTTIyLjY3MTk0NzcsNC40MTk1NzY0OSBMMTYuNSw4LjQxOTkxMjk4IEwxNi41LDYuMSBDMTYuNSw0LjA4OTc2NDU0IDE0LjkzNzE4MDYsMi41IDEzLDIuNSBMNC4xLDIuNSBDMi4wNzYxNDIzNywyLjUgMC41LDQuMDc2MTQyMzcgMC41LDYuMSBMMC41LDE0IEMwLjUsMTUuOTI3Mzk4NyAyLjA4NDQ5ODM5LDE3LjUxMTg5NzEgNC4xLDE3LjYgTDEzLDE3LjYgQzE0LjkwMTY2MDIsMTcuNiAxNi41LDE1Ljk0NjU0NSAxNi41LDE0IEwxNi41LDExLjcxNjkwNDggTDIyLjc1NzI0NzksMTUuNDcxMjUzNSBMMjIuODUzNTUzNCwxNS41NDY0NDY2IEMyMi44NzM3ODg2LDE1LjU2NjY4MTggMjIuOTUxNTMxLDE1LjYgMjMsMTUuNiBDMjMuMjY2OTg2NSwxNS42IDIzLjUsMTUuMzgyNTIwNyAyMy41LDE1LjEgTDIzLjUsNC44IEMyMy41LDQuODM2NzY1MzggMjMuNDQzODA1OCw0LjcwNTY0NTYzIDIzLjM3MTI1MzUsNC41NTcyNDc4OCBDMjMuMjI1OTA1Niw0LjMxNTAwMTM5IDIyLjk0MTU5MzcsNC4yNTgxMzg5OSAyMi42NzE5NDc3LDQuNDE5NTc2NDkgWiIgaWQ9InZpZGVvXzM3XyIgZmlsbD0iIzRENEQ0RCI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+';\n\n/**\n * Sensor attribute video sensor block should report.\n * @readonly\n * @enum {string}\n */\nconst SensingAttribute = {\n /** The amount of motion. */\n MOTION: 'motion',\n\n /** The direction of the motion. */\n DIRECTION: 'direction'\n};\n\n/**\n * Subject video sensor block should report for.\n * @readonly\n * @enum {string}\n */\nconst SensingSubject = {\n /** The sensor traits of the whole stage. */\n STAGE: 'Stage',\n\n /** The senosr traits of the area overlapped by this sprite. */\n SPRITE: 'this sprite'\n};\n\n/**\n * States the video sensing activity can be set to.\n * @readonly\n * @enum {string}\n */\nconst VideoState = {\n /** Video turned off. */\n OFF: 'off',\n\n /** Video turned on with default y axis mirroring. */\n ON: 'on',\n\n /** Video turned on without default y axis mirroring. */\n ON_FLIPPED: 'on-flipped'\n};\n\n/**\n * Class for the motion-related blocks in Scratch 3.0\n * @param {Runtime} runtime - the runtime instantiating this block package.\n * @constructor\n */\nclass Scratch3VideoSensingBlocks {\n constructor (runtime) {\n /**\n * The runtime instantiating this block package.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n /**\n * The motion detection algoritm used to power the motion amount and\n * direction values.\n * @type {VideoMotion}\n */\n this.detect = new VideoMotion();\n\n /**\n * The last millisecond epoch timestamp that the video stream was\n * analyzed.\n * @type {number}\n */\n this._lastUpdate = null;\n\n /**\n * A flag to determine if this extension has been installed in a project.\n * It is set to false the first time getInfo is run.\n * @type {boolean}\n */\n this.firstInstall = true;\n\n if (this.runtime.ioDevices) {\n // Configure the video device with values from globally stored locations.\n this.runtime.on(Runtime.PROJECT_LOADED, this.updateVideoDisplay.bind(this));\n\n // Clear target motion state values when the project starts.\n this.runtime.on(Runtime.PROJECT_RUN_START, this.reset.bind(this));\n\n // Kick off looping the analysis logic.\n this._loop();\n }\n }\n\n /**\n * After analyzing a frame the amount of milliseconds until another frame\n * is analyzed.\n * @type {number}\n */\n static get INTERVAL () {\n return 33;\n }\n\n /**\n * Dimensions the video stream is analyzed at after its rendered to the\n * sample canvas.\n * @type {Array.}\n */\n static get DIMENSIONS () {\n return [480, 360];\n }\n\n /**\n * The key to load & store a target's motion-related state.\n * @type {string}\n */\n static get STATE_KEY () {\n return 'Scratch.videoSensing';\n }\n\n /**\n * The default motion-related state, to be used when a target has no existing motion state.\n * @type {MotionState}\n */\n static get DEFAULT_MOTION_STATE () {\n return {\n motionFrameNumber: 0,\n motionAmount: 0,\n motionDirection: 0\n };\n }\n\n /**\n * The transparency setting of the video preview stored in a value\n * accessible by any object connected to the virtual machine.\n * @type {number}\n */\n get globalVideoTransparency () {\n const stage = this.runtime.getTargetForStage();\n if (stage) {\n return stage.videoTransparency;\n }\n return 50;\n }\n\n set globalVideoTransparency (transparency) {\n const stage = this.runtime.getTargetForStage();\n if (stage) {\n stage.videoTransparency = transparency;\n }\n return transparency;\n }\n\n /**\n * The video state of the video preview stored in a value accessible by any\n * object connected to the virtual machine.\n * @type {number}\n */\n get globalVideoState () {\n const stage = this.runtime.getTargetForStage();\n if (stage) {\n return stage.videoState;\n }\n // Though the default value for the stage is normally 'on', we need to default\n // to 'off' here to prevent the video device from briefly activating\n // while waiting for stage targets to be installed that say it should be off\n return VideoState.OFF;\n }\n\n set globalVideoState (state) {\n const stage = this.runtime.getTargetForStage();\n if (stage) {\n stage.videoState = state;\n }\n return state;\n }\n\n /**\n * Get the latest values for video transparency and state,\n * and set the video device to use them.\n */\n updateVideoDisplay () {\n this.setVideoTransparency({\n TRANSPARENCY: this.globalVideoTransparency\n });\n this.videoToggle({\n VIDEO_STATE: this.globalVideoState\n });\n }\n\n /**\n * Reset the extension's data motion detection data. This will clear out\n * for example old frames, so the first analyzed frame will not be compared\n * against a frame from before reset was called.\n */\n reset () {\n this.detect.reset();\n\n const targets = this.runtime.targets;\n for (let i = 0; i < targets.length; i++) {\n const state = targets[i].getCustomState(Scratch3VideoSensingBlocks.STATE_KEY);\n if (state) {\n state.motionAmount = 0;\n state.motionDirection = 0;\n }\n }\n }\n\n /**\n * Occasionally step a loop to sample the video, stamp it to the preview\n * skin, and add a TypedArray copy of the canvas's pixel data.\n * @private\n */\n _loop () {\n setTimeout(this._loop.bind(this), Math.max(this.runtime.currentStepTime, Scratch3VideoSensingBlocks.INTERVAL));\n\n // Add frame to detector\n const time = Date.now();\n if (this._lastUpdate === null) {\n this._lastUpdate = time;\n }\n const offset = time - this._lastUpdate;\n if (offset > Scratch3VideoSensingBlocks.INTERVAL) {\n const frame = this.runtime.ioDevices.video.getFrame({\n format: Video.FORMAT_IMAGE_DATA,\n dimensions: Scratch3VideoSensingBlocks.DIMENSIONS\n });\n if (frame) {\n this._lastUpdate = time;\n this.detect.addFrame(frame.data);\n }\n }\n }\n\n /**\n * Create data for a menu in scratch-blocks format, consisting of an array\n * of objects with text and value properties. The text is a translated\n * string, and the value is one-indexed.\n * @param {object[]} info - An array of info objects each having a name\n * property.\n * @return {array} - An array of objects with text and value properties.\n * @private\n */\n _buildMenu (info) {\n return info.map((entry, index) => {\n const obj = {};\n obj.text = entry.name;\n obj.value = entry.value || String(index + 1);\n return obj;\n });\n }\n\n /**\n * @param {Target} target - collect motion state for this target.\n * @returns {MotionState} the mutable motion state associated with that\n * target. This will be created if necessary.\n * @private\n */\n _getMotionState (target) {\n let motionState = target.getCustomState(Scratch3VideoSensingBlocks.STATE_KEY);\n if (!motionState) {\n motionState = Clone.simple(Scratch3VideoSensingBlocks.DEFAULT_MOTION_STATE);\n target.setCustomState(Scratch3VideoSensingBlocks.STATE_KEY, motionState);\n }\n return motionState;\n }\n\n static get SensingAttribute () {\n return SensingAttribute;\n }\n\n /**\n * An array of choices of whether a reporter should return the frame's\n * motion amount or direction.\n * @type {object[]}\n * @param {string} name - the translatable name to display in sensor\n * attribute menu\n * @param {string} value - the serializable value of the attribute\n */\n get ATTRIBUTE_INFO () {\n return [\n {\n name: formatMessage({\n id: 'videoSensing.motion',\n default: 'motion',\n description: 'Attribute for the \"video [ATTRIBUTE] on [SUBJECT]\" block'\n }),\n value: SensingAttribute.MOTION\n },\n {\n name: formatMessage({\n id: 'videoSensing.direction',\n default: 'direction',\n description: 'Attribute for the \"video [ATTRIBUTE] on [SUBJECT]\" block'\n }),\n value: SensingAttribute.DIRECTION\n }\n ];\n }\n\n static get SensingSubject () {\n return SensingSubject;\n }\n\n /**\n * An array of info about the subject choices.\n * @type {object[]}\n * @param {string} name - the translatable name to display in the subject menu\n * @param {string} value - the serializable value of the subject\n */\n get SUBJECT_INFO () {\n return [\n {\n name: formatMessage({\n id: 'videoSensing.sprite',\n default: 'sprite',\n description: 'Subject for the \"video [ATTRIBUTE] on [SUBJECT]\" block'\n }),\n value: SensingSubject.SPRITE\n },\n {\n name: formatMessage({\n id: 'videoSensing.stage',\n default: 'stage',\n description: 'Subject for the \"video [ATTRIBUTE] on [SUBJECT]\" block'\n }),\n value: SensingSubject.STAGE\n }\n ];\n }\n\n /**\n * States the video sensing activity can be set to.\n * @readonly\n * @enum {string}\n */\n static get VideoState () {\n return VideoState;\n }\n\n /**\n * An array of info on video state options for the \"turn video [STATE]\" block.\n * @type {object[]}\n * @param {string} name - the translatable name to display in the video state menu\n * @param {string} value - the serializable value stored in the block\n */\n get VIDEO_STATE_INFO () {\n return [\n {\n name: formatMessage({\n id: 'videoSensing.off',\n default: 'off',\n description: 'Option for the \"turn video [STATE]\" block'\n }),\n value: VideoState.OFF\n },\n {\n name: formatMessage({\n id: 'videoSensing.on',\n default: 'on',\n description: 'Option for the \"turn video [STATE]\" block'\n }),\n value: VideoState.ON\n },\n {\n name: formatMessage({\n id: 'videoSensing.onFlipped',\n default: 'on flipped',\n description: 'Option for the \"turn video [STATE]\" block that causes the video to be flipped' +\n ' horizontally (reversed as in a mirror)'\n }),\n value: VideoState.ON_FLIPPED\n }\n ];\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n // Set the video display properties to defaults the first time\n // getInfo is run. This turns on the video device when it is\n // first added to a project, and is overwritten by a PROJECT_LOADED\n // event listener that later calls updateVideoDisplay\n if (this.firstInstall) {\n this.globalVideoState = VideoState.ON;\n this.globalVideoTransparency = 50;\n this.updateVideoDisplay();\n this.firstInstall = false;\n }\n\n // Return extension definition\n return {\n id: 'videoSensing',\n name: formatMessage({\n id: 'videoSensing.categoryName',\n default: 'Video Sensing',\n description: 'Label for the video sensing extension category'\n }),\n blockIconURI: blockIconURI,\n menuIconURI: menuIconURI,\n blocks: [\n {\n // @todo this hat needs to be set itself to restart existing\n // threads like Scratch 2's behaviour.\n opcode: 'whenMotionGreaterThan',\n text: formatMessage({\n id: 'videoSensing.whenMotionGreaterThan',\n default: 'when video motion > [REFERENCE]',\n description: 'Event that triggers when the amount of motion is greater than [REFERENCE]'\n }),\n blockType: BlockType.HAT,\n arguments: {\n REFERENCE: {\n type: ArgumentType.NUMBER,\n defaultValue: 10\n }\n }\n },\n {\n opcode: 'videoOn',\n blockType: BlockType.REPORTER,\n text: formatMessage({\n id: 'videoSensing.videoOn',\n default: 'video [ATTRIBUTE] on [SUBJECT]',\n description: 'Reporter that returns the amount of [ATTRIBUTE] for the selected [SUBJECT]'\n }),\n arguments: {\n ATTRIBUTE: {\n type: ArgumentType.NUMBER,\n menu: 'ATTRIBUTE',\n defaultValue: SensingAttribute.MOTION\n },\n SUBJECT: {\n type: ArgumentType.NUMBER,\n menu: 'SUBJECT',\n defaultValue: SensingSubject.SPRITE\n }\n }\n },\n {\n opcode: 'videoToggle',\n text: formatMessage({\n id: 'videoSensing.videoToggle',\n default: 'turn video [VIDEO_STATE]',\n description: 'Controls display of the video preview layer'\n }),\n arguments: {\n VIDEO_STATE: {\n type: ArgumentType.NUMBER,\n menu: 'VIDEO_STATE',\n defaultValue: VideoState.ON\n }\n }\n },\n {\n opcode: 'setVideoTransparency',\n text: formatMessage({\n id: 'videoSensing.setVideoTransparency',\n default: 'set video transparency to [TRANSPARENCY]',\n description: 'Controls transparency of the video preview layer'\n }),\n arguments: {\n TRANSPARENCY: {\n type: ArgumentType.NUMBER,\n defaultValue: 50\n }\n }\n }\n ],\n menus: {\n ATTRIBUTE: {\n acceptReporters: true,\n items: this._buildMenu(this.ATTRIBUTE_INFO)\n },\n SUBJECT: {\n acceptReporters: true,\n items: this._buildMenu(this.SUBJECT_INFO)\n },\n VIDEO_STATE: {\n acceptReporters: true,\n items: this._buildMenu(this.VIDEO_STATE_INFO)\n }\n }\n };\n }\n\n /**\n * Analyze a part of the frame that a target overlaps.\n * @param {Target} target - a target to determine where to analyze\n * @returns {MotionState} the motion state for the given target\n */\n _analyzeLocalMotion (target) {\n const drawable = this.runtime.renderer._allDrawables[target.drawableID];\n const state = this._getMotionState(target);\n this.detect.getLocalMotion(drawable, state);\n return state;\n }\n\n /**\n * A scratch reporter block handle that analyzes the last two frames and\n * depending on the arguments, returns the motion or direction for the\n * whole stage or just the target sprite.\n * @param {object} args - the block arguments\n * @param {BlockUtility} util - the block utility\n * @returns {number} the motion amount or direction of the stage or sprite\n */\n videoOn (args, util) {\n this.detect.analyzeFrame();\n\n let state = this.detect;\n if (args.SUBJECT === SensingSubject.SPRITE) {\n state = this._analyzeLocalMotion(util.target);\n }\n\n if (args.ATTRIBUTE === SensingAttribute.MOTION) {\n return state.motionAmount;\n }\n return state.motionDirection;\n }\n\n /**\n * A scratch hat block edge handle that analyzes the last two frames where\n * the target sprite overlaps and if it has more motion than the given\n * reference value.\n * @param {object} args - the block arguments\n * @param {BlockUtility} util - the block utility\n * @returns {boolean} true if the sprite overlaps more motion than the\n * reference\n */\n whenMotionGreaterThan (args, util) {\n this.detect.analyzeFrame();\n const state = this._analyzeLocalMotion(util.target);\n return state.motionAmount > Number(args.REFERENCE);\n }\n\n /**\n * A scratch command block handle that configures the video state from\n * passed arguments.\n * @param {object} args - the block arguments\n * @param {VideoState} args.VIDEO_STATE - the video state to set the device to\n */\n videoToggle (args) {\n const state = args.VIDEO_STATE;\n this.globalVideoState = state;\n if (state === VideoState.OFF) {\n this.runtime.ioDevices.video.disableVideo();\n } else {\n this.runtime.ioDevices.video.enableVideo();\n // Mirror if state is ON. Do not mirror if state is ON_FLIPPED.\n this.runtime.ioDevices.video.mirror = state === VideoState.ON;\n }\n }\n\n /**\n * A scratch command block handle that configures the video preview's\n * transparency from passed arguments.\n * @param {object} args - the block arguments\n * @param {number} args.TRANSPARENCY - the transparency to set the video\n * preview to\n */\n setVideoTransparency (args) {\n const transparency = Cast.toNumber(args.TRANSPARENCY);\n this.globalVideoTransparency = transparency;\n this.runtime.ioDevices.video.setPreviewGhost(transparency);\n }\n}\n\nmodule.exports = Scratch3VideoSensingBlocks;\n","/**\n * @file library.js\n *\n * Tony Hwang and John Maloney, January 2011\n * Michael \"Z\" Goddard, March 2018\n *\n * Video motion sensing primitives.\n */\n\nconst {motionVector, scratchAtan2} = require('./math');\n\n/**\n * The width of the intended resolution to analyze for motion.\n * @type {number}\n */\nconst WIDTH = 480;\n\n/**\n * The height of the intended resolution to analyze for motion.\n * @type {number}\n */\nconst HEIGHT = 360;\n\n/**\n * A constant value to scale the magnitude of the x and y components called u\n * and v. This creates the motionAmount value.\n *\n * Old note: chosen empirically to give a range of roughly 0-100\n *\n * @type {number}\n */\nconst AMOUNT_SCALE = 100;\n\n/**\n * A constant value to scale the magnitude of the x and y components called u\n * and v in the local motion derivative. This creates the motionAmount value on\n * a target's motion state.\n *\n * Old note: note 2e-4 * activePixelNum is an experimentally tuned threshold\n * for my logitech Pro 9000 webcam - TTH\n *\n * @type {number}\n */\nconst LOCAL_AMOUNT_SCALE = AMOUNT_SCALE * 2e-4;\n\n/**\n * The motion amount must be higher than the THRESHOLD to calculate a new\n * direction value.\n * @type {number}\n */\nconst THRESHOLD = 10;\n\n/**\n * The size of the radius of the window of summarized values when considering\n * the motion inside the full resolution of the sample.\n * @type {number}\n */\nconst WINSIZE = 8;\n\n/**\n * A ceiling for the motionAmount stored to a local target's motion state. The\n * motionAmount is not allowed to be larger than LOCAL_MAX_AMOUNT.\n * @type {number}\n */\nconst LOCAL_MAX_AMOUNT = 100;\n\n/**\n * The motion amount for a target's local motion must be higher than the\n * LOCAL_THRESHOLD to calculate a new direction value.\n * @type {number}\n */\nconst LOCAL_THRESHOLD = THRESHOLD / 3;\n\n/**\n * Store the necessary image pixel data to compares frames of a video and\n * detect an amount and direction of motion in the full sample or in a\n * specified area.\n * @constructor\n */\nclass VideoMotion {\n constructor () {\n /**\n * The number of frames that have been added from a source.\n * @type {number}\n */\n this.frameNumber = 0;\n\n /**\n * The frameNumber last analyzed.\n * @type {number}\n */\n this.lastAnalyzedFrame = 0;\n\n /**\n * The amount of motion detected in the current frame.\n * @type {number}\n */\n this.motionAmount = 0;\n\n /**\n * The direction the motion detected in the frame is general moving in.\n * @type {number}\n */\n this.motionDirection = 0;\n\n /**\n * A copy of the current frame's pixel values. A index of the array is\n * represented in RGBA. The lowest byte is red. The next is green. The\n * next is blue. And the last is the alpha value of that pixel.\n * @type {Uint32Array}\n */\n this.curr = null;\n\n /**\n * A copy of the last frame's pixel values.\n * @type {Uint32Array}\n */\n this.prev = null;\n\n /**\n * A buffer for holding one component of a pixel's full value twice.\n * One for the current value. And one for the last value.\n * @type {number}\n */\n this._arrays = new ArrayBuffer(WIDTH * HEIGHT * 2 * 1);\n\n /**\n * A clamped uint8 view of _arrays. One component of each index of the\n * curr member is copied into this array.\n * @type {number}\n */\n this._curr = new Uint8ClampedArray(this._arrays, WIDTH * HEIGHT * 0 * 1, WIDTH * HEIGHT);\n\n /**\n * A clamped uint8 view of _arrays. One component of each index of the\n * prev member is copied into this array.\n * @type {number}\n */\n this._prev = new Uint8ClampedArray(this._arrays, WIDTH * HEIGHT * 1 * 1, WIDTH * HEIGHT);\n }\n\n /**\n * Reset internal state so future frame analysis does not consider values\n * from before this method was called.\n */\n reset () {\n this.frameNumber = 0;\n this.lastAnalyzedFrame = 0;\n this.motionAmount = this.motionDirection = 0;\n this.prev = this.curr = null;\n }\n\n /**\n * Add a frame to be next analyzed. The passed array represent a pixel with\n * each index in the RGBA format.\n * @param {Uint32Array} source - a source frame of pixels to copy\n */\n addFrame (source) {\n this.frameNumber++;\n\n // Swap curr to prev.\n this.prev = this.curr;\n // Create a clone of the array so any modifications made to the source\n // array do not affect the work done in here.\n this.curr = new Uint32Array(source.buffer.slice(0));\n\n // Swap _prev and _curr. Copy one of the color components of the new\n // array into _curr overwriting what was the old _prev data.\n const _tmp = this._prev;\n this._prev = this._curr;\n this._curr = _tmp;\n for (let i = 0; i < this.curr.length; i++) {\n this._curr[i] = this.curr[i] & 0xff;\n }\n }\n\n /**\n * Analyze the current frame against the previous frame determining the\n * amount of motion and direction of the motion.\n */\n analyzeFrame () {\n if (!this.curr || !this.prev) {\n this.motionAmount = this.motionDirection = -1;\n // Don't have two frames to analyze yet\n return;\n }\n\n // Return early if new data has not been received.\n if (this.lastAnalyzedFrame === this.frameNumber) {\n return;\n }\n this.lastAnalyzedFrame = this.frameNumber;\n\n const {\n _curr: curr,\n _prev: prev\n } = this;\n\n const winStep = (WINSIZE * 2) + 1;\n const wmax = WIDTH - WINSIZE - 1;\n const hmax = HEIGHT - WINSIZE - 1;\n\n // Accumulate 2d motion vectors from groups of pixels and average it\n // later.\n let uu = 0;\n let vv = 0;\n let n = 0;\n\n // Iterate over groups of cells building up the components to determine\n // a motion vector for each cell instead of the whole frame to avoid\n // integer overflows.\n for (let i = WINSIZE + 1; i < hmax; i += winStep) {\n for (let j = WINSIZE + 1; j < wmax; j += winStep) {\n let A2 = 0;\n let A1B2 = 0;\n let B1 = 0;\n let C1 = 0;\n let C2 = 0;\n\n // This is a performance critical math region.\n let address = ((i - WINSIZE) * WIDTH) + j - WINSIZE;\n let nextAddress = address + winStep;\n const maxAddress = ((i + WINSIZE) * WIDTH) + j + WINSIZE;\n for (; address <= maxAddress; address += WIDTH - winStep, nextAddress += WIDTH) {\n for (; address <= nextAddress; address += 1) {\n // The difference in color between the last frame and\n // the current frame.\n const gradT = ((prev[address]) - (curr[address]));\n // The difference between the pixel to the left and the\n // pixel to the right.\n const gradX = ((curr[address - 1]) - (curr[address + 1]));\n // The difference between the pixel above and the pixel\n // below.\n const gradY = ((curr[address - WIDTH]) - (curr[address + WIDTH]));\n\n // Add the combined values of this pixel to previously\n // considered pixels.\n A2 += gradX * gradX;\n A1B2 += gradX * gradY;\n B1 += gradY * gradY;\n C2 += gradX * gradT;\n C1 += gradY * gradT;\n }\n }\n\n // Use the accumalated values from the for loop to determine a\n // motion direction.\n const {u, v} = motionVector(A2, A1B2, B1, C2, C1);\n\n // If u and v are within negative winStep to positive winStep,\n // add them to a sum that will later be averaged.\n if (-winStep < u && u < winStep && -winStep < v && v < winStep) {\n uu += u;\n vv += v;\n n++;\n }\n }\n }\n\n // Average the summed vector values of all of the motion groups.\n uu /= n;\n vv /= n;\n\n // Scale the magnitude of the averaged UV vector.\n this.motionAmount = Math.round(AMOUNT_SCALE * Math.hypot(uu, vv));\n if (this.motionAmount > THRESHOLD) {\n // Scratch direction\n this.motionDirection = scratchAtan2(vv, uu);\n }\n }\n\n /**\n * Build motion amount and direction values based on stored current and\n * previous frame that overlaps a given drawable.\n * @param {Drawable} drawable - touchable and bounded drawable to build motion for\n * @param {MotionState} state - state to store built values to\n */\n getLocalMotion (drawable, state) {\n if (!this.curr || !this.prev) {\n state.motionAmount = state.motionDirection = -1;\n // Don't have two frames to analyze yet\n return;\n }\n\n // Skip if the current frame has already been considered for this state.\n if (state.motionFrameNumber !== this.frameNumber) {\n const {\n _prev: prev,\n _curr: curr\n } = this;\n\n // The public APIs for Renderer#isTouching manage keeping the matrix and\n // silhouette up-to-date, which is needed for drawable#isTouching to work (used below)\n drawable.updateCPURenderAttributes();\n\n // Restrict the region the amount and direction are built from to\n // the area of the current frame overlapped by the given drawable's\n // bounding box.\n const boundingRect = drawable.getFastBounds();\n // Transform the bounding box from scratch space to a space from 0,\n // 0 to WIDTH, HEIGHT.\n const xmin = Math.max(Math.floor(boundingRect.left + (WIDTH / 2)), 1);\n const xmax = Math.min(Math.floor(boundingRect.right + (WIDTH / 2)), WIDTH - 1);\n const ymin = Math.max(Math.floor((HEIGHT / 2) - boundingRect.top), 1);\n const ymax = Math.min(Math.floor((HEIGHT / 2) - boundingRect.bottom), HEIGHT - 1);\n\n let A2 = 0;\n let A1B2 = 0;\n let B1 = 0;\n let C1 = 0;\n let C2 = 0;\n let scaleFactor = 0;\n\n const position = [0, 0, 0];\n\n // This is a performance critical math region.\n for (let i = ymin; i < ymax; i++) {\n for (let j = xmin; j < xmax; j++) {\n // i and j are in a coordinate planning ranging from 0 to\n // HEIGHT and 0 to WIDTH. Transform that into Scratch's\n // range of HEIGHT / 2 to -HEIGHT / 2 and -WIDTH / 2 to\n // WIDTH / 2;\n position[0] = j - (WIDTH / 2);\n position[1] = (HEIGHT / 2) - i;\n // Consider only pixels in the drawable that can touch the\n // edge or other drawables. Empty space in the current skin\n // is skipped.\n if (drawable.isTouching(position)) {\n const address = (i * WIDTH) + j;\n // The difference in color between the last frame and\n // the current frame.\n const gradT = ((prev[address]) - (curr[address]));\n // The difference between the pixel to the left and the\n // pixel to the right.\n const gradX = ((curr[address - 1]) - (curr[address + 1]));\n // The difference between the pixel above and the pixel\n // below.\n const gradY = ((curr[address - WIDTH]) - (curr[address + WIDTH]));\n\n // Add the combined values of this pixel to previously\n // considered pixels.\n A2 += gradX * gradX;\n A1B2 += gradX * gradY;\n B1 += gradY * gradY;\n C2 += gradX * gradT;\n C1 += gradY * gradT;\n scaleFactor++;\n }\n }\n }\n\n // Use the accumalated values from the for loop to determine a\n // motion direction.\n let {u, v} = motionVector(A2, A1B2, B1, C2, C1);\n\n let activePixelNum = 0;\n if (scaleFactor) {\n // Store the area of the sprite in pixels\n activePixelNum = scaleFactor;\n\n scaleFactor /= (2 * WINSIZE * 2 * WINSIZE);\n u = u / scaleFactor;\n v = v / scaleFactor;\n }\n\n // Scale the magnitude of the averaged UV vector and the number of\n // overlapping drawable pixels.\n state.motionAmount = Math.round(LOCAL_AMOUNT_SCALE * activePixelNum * Math.hypot(u, v));\n if (state.motionAmount > LOCAL_MAX_AMOUNT) {\n // Clip all magnitudes greater than 100.\n state.motionAmount = LOCAL_MAX_AMOUNT;\n }\n if (state.motionAmount > LOCAL_THRESHOLD) {\n // Scratch direction.\n state.motionDirection = scratchAtan2(v, u);\n }\n\n // Skip future calls on this state until a new frame is added.\n state.motionFrameNumber = this.frameNumber;\n }\n }\n}\n\nmodule.exports = VideoMotion;\n","/**\n * A constant value helping to transform a value in radians to degrees.\n * @type {number}\n */\nconst TO_DEGREE = 180 / Math.PI;\n\n/**\n * A object reused to save on memory allocation returning u and v vector from\n * motionVector.\n * @type {UV}\n */\nconst _motionVectorOut = {u: 0, v: 0};\n\n/**\n * Determine a motion vector combinations of the color component difference on\n * the x axis, y axis, and temporal axis.\n * @param {number} A2 - a sum of x axis squared\n * @param {number} A1B2 - a sum of x axis times y axis\n * @param {number} B1 - a sum of y axis squared\n * @param {number} C2 - a sum of x axis times temporal axis\n * @param {number} C1 - a sum of y axis times temporal axis\n * @param {UV} out - optional object to store return UV info in\n * @returns {UV} a uv vector representing the motion for the given input\n */\nconst motionVector = function (A2, A1B2, B1, C2, C1, out = _motionVectorOut) {\n // Compare sums of X * Y and sums of X squared and Y squared.\n const delta = ((A1B2 * A1B2) - (A2 * B1));\n if (delta) {\n // System is not singular - solving by Kramer method.\n const deltaX = -((C1 * A1B2) - (C2 * B1));\n const deltaY = -((A1B2 * C2) - (A2 * C1));\n const Idelta = 8 / delta;\n out.u = deltaX * Idelta;\n out.v = deltaY * Idelta;\n } else {\n // Singular system - find optical flow in gradient direction.\n const Norm = ((A1B2 + A2) * (A1B2 + A2)) + ((B1 + A1B2) * (B1 + A1B2));\n if (Norm) {\n const IGradNorm = 8 / Norm;\n const temp = -(C1 + C2) * IGradNorm;\n out.u = (A1B2 + A2) * temp;\n out.v = (B1 + A1B2) * temp;\n } else {\n out.u = 0;\n out.v = 0;\n }\n }\n return out;\n};\n\n/**\n * Translate an angle in degrees with the range -180 to 180 rotated to\n * Scratch's reference angle.\n * @param {number} degrees - angle in range -180 to 180\n * @returns {number} angle from Scratch's reference angle\n */\nconst scratchDegrees = function (degrees) {\n return ((degrees + 270) % 360) - 180;\n};\n\n/**\n * Get the angle of the y and x component of a 2d vector in degrees in\n * Scratch's coordinate plane.\n * @param {number} y - the y component of a 2d vector\n * @param {number} x - the x component of a 2d vector\n * @returns {number} angle in degrees in Scratch's coordinate plane\n */\nconst scratchAtan2 = function (y, x) {\n return scratchDegrees(Math.atan2(y, x) * TO_DEGREE);\n};\n\nmodule.exports = {\n motionVector,\n scratchDegrees,\n scratchAtan2\n};\n","const ArgumentType = require('../../extension-support/argument-type');\nconst BlockType = require('../../extension-support/block-type');\nconst Cast = require('../../util/cast');\nconst formatMessage = require('format-message');\nconst color = require('../../util/color');\nconst BLE = require('../../io/ble');\nconst Base64Util = require('../../util/base64-util');\nconst MathUtil = require('../../util/math-util');\nconst RateLimiter = require('../../util/rateLimiter.js');\nconst log = require('../../util/log');\n\n/**\n * Icon svg to be displayed at the left edge of each extension block, encoded as a data URI.\n * @type {string}\n */\n// eslint-disable-next-line max-len\nconst iconURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAACXBIWXMAABYlAAAWJQFJUiTwAAAF8klEQVR4Ae2cbWxTVRjH/7ctbVc2tyEMNpWBk0VIkLcEjSAQgglTE5HEaKqJi1E/mbCP/dJA0kQbvzgTQ0Ki2T7V6AeYGoEPLJmGKPiyzZDwEpYJCHSbQIcbdLvres1zOa13Xbvdu2eTDp9fst329Lnn5XfPPfece7tphmFAmDkuccdDBDIRgUxEIBMRyEQEMhGBTEQgExHIRAQyEYFMRCATEchEBDIRgUxEIBMRyEQEMhGBTEQgExHIxMPNIByNVQBoBUDb7kgo2KTS9wBoUmFNkVCwW6U3A1gP4JJKHwxHY/S+WcW2RkLBVhV7AMAOAIMAGlWstbyOSCh4QMU2Uoy1PBVL+a7IqZu1vOZIKNg20/azBarGvKxebw9HY22RULADwBFLTBcATQnZl4lVEimN4ssteXQrQfstebQpmW1q30xshyqvxRLbofYnYW9ZYgeV8C5LLOWlzbTxM3ouHI7GPgSwWx3Z0syBSBku6IYnlTbM+uQenJQaMnKHDaqAFnDrcCFbl3G1defEjas0a4N/Vz10OybyvapfrSX1sjpo+WIz0ME7QL3djgtHPTAcjb2mepw/b2ZaGh5NL5RnofR8R99dIC5fHusK5JsrCUpm7TSx21XvbcwTNwnbAsPR2GcA3qaG+H0LsHlDPZ7fca/ujZ+cRW9/Em5vCXzlNVhQUjFpf/3OTSRvXkKJz43Xt1bh1S1LUeq/5+njQ9/iVmLIfL1ieRU2b1iFtavztXNu6TrTi8PfnYI67WdPoOp5przV9Y8iuHdb9rOW9uumPI+vDIElddBckztPOqVn5X36Xj1WVQeynx1sOWbK83jc2PviM/dFXIYNax9H55leXLoyYHsfWwI14JCRRx7x5ckBU1oheYQ+1G9u39lVM0Hej7+cR7w/Yb7e9+5LqChfaLvixcK088BwNNZkAOV02ubK6+odwt3RcfOULSSPGEveG48bNj08If3kqXPmdtO6unkpDzYn0u/TLxrzcumJJ80Ut79sygzoFF6/siw75mUYupOEpmnY0/A0pw33FTsCa+hX5oJhZXgkZb5zub2O20CnL7EwkPeCPm+wI7CEBvi5wuOZ36tJW7X3uGXJXAgxk8P4eNpRPEvgskqfuR0Z/BNGejxvDM3/5gs0pboWv+motqybCc+tqUCzz43kaBJ/X+2eMjZ3ClNsjIzo5ioknXZ2b4AlkKYltLJoaY9jOJm/B0KJbtg4c4F/XOmH3+dF9dLKbBo1OD6QQGV56YQ55ODtO0jcHkZ1VSX8/n9nB9S7RkZ1rFy+NG8ZR9s70TeQQKDEh7vJUdt1Y9/OopXFB2/WcbMpyOexE9mlFS21aLlHMmKHfzBl0QT/hV2bzM9oLXv0xG8YGR0zpdLEn6RT2k+/XjDzoLX2G3u3TZBLUyral/Z5qCyAK1f/sl2/or+IWNel1Eji3MWrpjyCZHWqdNrSe6ieSHFERl4mP+q5GehgHGvvRGal5XI5uzU47f3A/R99YTgdF2wXrmkolr9ToZ5NvTjT4yOhoC2T057CJM/r9WDxoqmXa07R9THcuDVcMO8bt4ag6ynULKvkFjWBTLl0ugZKvNlyqLeSQKfYGgOpgXt2b5zVhlzrS+Dr451YvKg0b95txztxvS8xZ+VuXFuLJ5+oNgV+9c3PuHDxGs6cu+w4v//9RJo6x5bN9UgbBo4cPY1U6j+cSD8orFvzGFYuX4KxsRQGbth6FCICc9m5dY05HtN46AQRqPB5PWjY+ZT5RnMwkxGBFh5ZVmle9Z3MrGbjwfqccrC1vajrV7QCaVCfS6qrJj96nQlFK5CujPRT7MgYyEQEMhGBTGwJpAW4kJ9pBbo0zbx70X7y7AOv8HxP3LyB4YTpb2cZBt2iqL3QEwf9zDbX+waLca439QMeC7a+YBmOxugLiM/OTt2yaOoMoO+H6LOcNwf6xusrthsh/7mIh1yFmYhAJiKQiQhkIgKZiEAmIpCJCGQiApmIQCYikIkIZCICmYhAJiKQiQhkIgKZiEAmIpCJCGQiAjkA+AeOwQKMcWZqHgAAAABJRU5ErkJggg==';\n\n/**\n * A list of WeDo 2.0 BLE service UUIDs.\n * @enum\n */\nconst BLEService = {\n DEVICE_SERVICE: '00001523-1212-efde-1523-785feabcd123',\n IO_SERVICE: '00004f0e-1212-efde-1523-785feabcd123'\n};\n\n/**\n * A list of WeDo 2.0 BLE characteristic UUIDs.\n *\n * Characteristics on DEVICE_SERVICE:\n * - ATTACHED_IO\n *\n * Characteristics on IO_SERVICE:\n * - INPUT_VALUES\n * - INPUT_COMMAND\n * - OUTPUT_COMMAND\n *\n * @enum\n */\nconst BLECharacteristic = {\n ATTACHED_IO: '00001527-1212-efde-1523-785feabcd123',\n LOW_VOLTAGE_ALERT: '00001528-1212-efde-1523-785feabcd123',\n INPUT_VALUES: '00001560-1212-efde-1523-785feabcd123',\n INPUT_COMMAND: '00001563-1212-efde-1523-785feabcd123',\n OUTPUT_COMMAND: '00001565-1212-efde-1523-785feabcd123'\n};\n\n/**\n * A time interval to wait (in milliseconds) in between battery check calls.\n * @type {number}\n */\nconst BLEBatteryCheckInterval = 5000;\n\n/**\n * A time interval to wait (in milliseconds) while a block that sends a BLE message is running.\n * @type {number}\n */\nconst BLESendInterval = 100;\n\n/**\n * A maximum number of BLE message sends per second, to be enforced by the rate limiter.\n * @type {number}\n */\nconst BLESendRateMax = 20;\n\n/**\n * Enum for WeDo 2.0 sensor and output types.\n * @readonly\n * @enum {number}\n */\nconst WeDo2Device = {\n MOTOR: 1,\n PIEZO: 22,\n LED: 23,\n TILT: 34,\n DISTANCE: 35\n};\n\n/**\n * Enum for connection/port ids assigned to internal WeDo 2.0 output devices.\n * @readonly\n * @enum {number}\n */\n// TODO: Check for these more accurately at startup?\nconst WeDo2ConnectID = {\n LED: 6,\n PIEZO: 5\n};\n\n/**\n * Enum for ids for various output commands on the WeDo 2.0.\n * @readonly\n * @enum {number}\n */\nconst WeDo2Command = {\n MOTOR_POWER: 1,\n PLAY_TONE: 2,\n STOP_TONE: 3,\n WRITE_RGB: 4,\n SET_VOLUME: 255\n};\n\n/**\n * Enum for modes for input sensors on the WeDo 2.0.\n * @enum {number}\n */\nconst WeDo2Mode = {\n TILT: 0, // angle\n DISTANCE: 0, // detect\n LED: 1 // RGB\n};\n\n/**\n * Enum for units for input sensors on the WeDo 2.0.\n *\n * 0 = raw\n * 1 = percent\n *\n * @enum {number}\n */\nconst WeDo2Unit = {\n TILT: 0,\n DISTANCE: 1,\n LED: 0\n};\n\n/**\n * Manage power, direction, and timers for one WeDo 2.0 motor.\n */\nclass WeDo2Motor {\n /**\n * Construct a WeDo 2.0 Motor instance.\n * @param {WeDo2} parent - the WeDo 2.0 peripheral which owns this motor.\n * @param {int} index - the zero-based index of this motor on its parent peripheral.\n */\n constructor (parent, index) {\n /**\n * The WeDo 2.0 peripheral which owns this motor.\n * @type {WeDo2}\n * @private\n */\n this._parent = parent;\n\n /**\n * The zero-based index of this motor on its parent peripheral.\n * @type {int}\n * @private\n */\n this._index = index;\n\n /**\n * This motor's current direction: 1 for \"this way\" or -1 for \"that way\"\n * @type {number}\n * @private\n */\n this._direction = 1;\n\n /**\n * This motor's current power level, in the range [0,100].\n * @type {number}\n * @private\n */\n this._power = 100;\n\n /**\n * Is this motor currently moving?\n * @type {boolean}\n * @private\n */\n this._isOn = false;\n\n /**\n * If the motor has been turned on or is actively braking for a specific duration, this is the timeout ID for\n * the end-of-action handler. Cancel this when changing plans.\n * @type {Object}\n * @private\n */\n this._pendingTimeoutId = null;\n\n /**\n * The starting time for the pending timeout.\n * @type {Object}\n * @private\n */\n this._pendingTimeoutStartTime = null;\n\n /**\n * The delay/duration of the pending timeout.\n * @type {Object}\n * @private\n */\n this._pendingTimeoutDelay = null;\n\n this.startBraking = this.startBraking.bind(this);\n this.turnOff = this.turnOff.bind(this);\n }\n\n /**\n * @return {number} - the duration of active braking after a call to startBraking(). Afterward, turn the motor off.\n * @constructor\n */\n static get BRAKE_TIME_MS () {\n return 1000;\n }\n\n /**\n * @return {int} - this motor's current direction: 1 for \"this way\" or -1 for \"that way\"\n */\n get direction () {\n return this._direction;\n }\n\n /**\n * @param {int} value - this motor's new direction: 1 for \"this way\" or -1 for \"that way\"\n */\n set direction (value) {\n if (value < 0) {\n this._direction = -1;\n } else {\n this._direction = 1;\n }\n }\n\n /**\n * @return {int} - this motor's current power level, in the range [0,100].\n */\n get power () {\n return this._power;\n }\n\n /**\n * @param {int} value - this motor's new power level, in the range [0,100].\n */\n set power (value) {\n const p = Math.max(0, Math.min(value, 100));\n // Lego Wedo 2.0 hub only turns motors at power range [30 - 100], so\n // map value from [0 - 100] to [30 - 100].\n if (p === 0) {\n this._power = 0;\n } else {\n const delta = 100 / p;\n this._power = 30 + (70 / delta);\n }\n }\n\n /**\n * @return {boolean} - true if this motor is currently moving, false if this motor is off or braking.\n */\n get isOn () {\n return this._isOn;\n }\n\n /**\n * @return {boolean} - time, in milliseconds, of when the pending timeout began.\n */\n get pendingTimeoutStartTime () {\n return this._pendingTimeoutStartTime;\n }\n\n /**\n * @return {boolean} - delay, in milliseconds, of the pending timeout.\n */\n get pendingTimeoutDelay () {\n return this._pendingTimeoutDelay;\n }\n\n /**\n * Turn this motor on indefinitely.\n */\n turnOn () {\n if (this._power === 0) return;\n\n const cmd = this._parent.generateOutputCommand(\n this._index + 1,\n WeDo2Command.MOTOR_POWER,\n [this._power * this._direction] // power in range 0-100\n );\n\n this._parent.send(BLECharacteristic.OUTPUT_COMMAND, cmd);\n\n this._isOn = true;\n this._clearTimeout();\n }\n\n /**\n * Turn this motor on for a specific duration.\n * @param {number} milliseconds - run the motor for this long.\n */\n turnOnFor (milliseconds) {\n if (this._power === 0) return;\n\n milliseconds = Math.max(0, milliseconds);\n this.turnOn();\n this._setNewTimeout(this.startBraking, milliseconds);\n }\n\n /**\n * Start active braking on this motor. After a short time, the motor will turn off.\n */\n startBraking () {\n if (this._power === 0) return;\n\n const cmd = this._parent.generateOutputCommand(\n this._index + 1,\n WeDo2Command.MOTOR_POWER,\n [127] // 127 = break\n );\n\n this._parent.send(BLECharacteristic.OUTPUT_COMMAND, cmd);\n\n this._isOn = false;\n this._setNewTimeout(this.turnOff, WeDo2Motor.BRAKE_TIME_MS);\n }\n\n /**\n * Turn this motor off.\n * @param {boolean} [useLimiter=true] - if true, use the rate limiter\n */\n turnOff (useLimiter = true) {\n if (this._power === 0) return;\n\n const cmd = this._parent.generateOutputCommand(\n this._index + 1,\n WeDo2Command.MOTOR_POWER,\n [0] // 0 = stop\n );\n\n this._parent.send(BLECharacteristic.OUTPUT_COMMAND, cmd, useLimiter);\n\n this._isOn = false;\n }\n\n /**\n * Clear the motor action timeout, if any. Safe to call even when there is no pending timeout.\n * @private\n */\n _clearTimeout () {\n if (this._pendingTimeoutId !== null) {\n clearTimeout(this._pendingTimeoutId);\n this._pendingTimeoutId = null;\n this._pendingTimeoutStartTime = null;\n this._pendingTimeoutDelay = null;\n }\n }\n\n /**\n * Set a new motor action timeout, after clearing an existing one if necessary.\n * @param {Function} callback - to be called at the end of the timeout.\n * @param {int} delay - wait this many milliseconds before calling the callback.\n * @private\n */\n _setNewTimeout (callback, delay) {\n this._clearTimeout();\n const timeoutID = setTimeout(() => {\n if (this._pendingTimeoutId === timeoutID) {\n this._pendingTimeoutId = null;\n this._pendingTimeoutStartTime = null;\n this._pendingTimeoutDelay = null;\n }\n callback();\n }, delay);\n this._pendingTimeoutId = timeoutID;\n this._pendingTimeoutStartTime = Date.now();\n this._pendingTimeoutDelay = delay;\n }\n}\n\n/**\n * Manage communication with a WeDo 2.0 peripheral over a Bluetooth Low Energy client socket.\n */\nclass WeDo2 {\n\n constructor (runtime, extensionId) {\n\n /**\n * The Scratch 3.0 runtime used to trigger the green flag button.\n * @type {Runtime}\n * @private\n */\n this._runtime = runtime;\n this._runtime.on('PROJECT_STOP_ALL', this.stopAll.bind(this));\n\n /**\n * The id of the extension this peripheral belongs to.\n */\n this._extensionId = extensionId;\n\n /**\n * A list of the ids of the motors or sensors in ports 1 and 2.\n * @type {string[]}\n * @private\n */\n this._ports = ['none', 'none'];\n\n /**\n * The motors which this WeDo 2.0 could possibly have.\n * @type {WeDo2Motor[]}\n * @private\n */\n this._motors = [null, null];\n\n /**\n * The most recently received value for each sensor.\n * @type {Object.}\n * @private\n */\n this._sensors = {\n tiltX: 0,\n tiltY: 0,\n distance: 0\n };\n\n /**\n * The Bluetooth connection socket for reading/writing peripheral data.\n * @type {BLE}\n * @private\n */\n this._ble = null;\n this._runtime.registerPeripheralExtension(extensionId, this);\n\n /**\n * A rate limiter utility, to help limit the rate at which we send BLE messages\n * over the socket to Scratch Link to a maximum number of sends per second.\n * @type {RateLimiter}\n * @private\n */\n this._rateLimiter = new RateLimiter(BLESendRateMax);\n\n /**\n * An interval id for the battery check interval.\n * @type {number}\n * @private\n */\n this._batteryLevelIntervalId = null;\n\n this.reset = this.reset.bind(this);\n this._onConnect = this._onConnect.bind(this);\n this._onMessage = this._onMessage.bind(this);\n this._checkBatteryLevel = this._checkBatteryLevel.bind(this);\n }\n\n /**\n * @return {number} - the latest value received for the tilt sensor's tilt about the X axis.\n */\n get tiltX () {\n return this._sensors.tiltX;\n }\n\n /**\n * @return {number} - the latest value received for the tilt sensor's tilt about the Y axis.\n */\n get tiltY () {\n return this._sensors.tiltY;\n }\n\n /**\n * @return {number} - the latest value received from the distance sensor.\n */\n get distance () {\n return this._sensors.distance;\n }\n\n /**\n * Access a particular motor on this peripheral.\n * @param {int} index - the zero-based index of the desired motor.\n * @return {WeDo2Motor} - the WeDo2Motor instance, if any, at that index.\n */\n motor (index) {\n return this._motors[index];\n }\n\n /**\n * Stop all the motors that are currently running.\n */\n stopAllMotors () {\n this._motors.forEach(motor => {\n if (motor) {\n // Send the motor off command without using the rate limiter.\n // This allows the stop button to stop motors even if we are\n // otherwise flooded with commands.\n motor.turnOff(false);\n }\n });\n }\n\n /**\n * Set the WeDo 2.0 peripheral's LED to a specific color.\n * @param {int} inputRGB - a 24-bit RGB color in 0xRRGGBB format.\n * @return {Promise} - a promise of the completion of the set led send operation.\n */\n setLED (inputRGB) {\n const rgb = [\n (inputRGB >> 16) & 0x000000FF,\n (inputRGB >> 8) & 0x000000FF,\n (inputRGB) & 0x000000FF\n ];\n\n const cmd = this.generateOutputCommand(\n WeDo2ConnectID.LED,\n WeDo2Command.WRITE_RGB,\n rgb\n );\n\n return this.send(BLECharacteristic.OUTPUT_COMMAND, cmd);\n }\n\n /**\n * Sets the input mode of the LED to RGB.\n * @return {Promise} - a promise returned by the send operation.\n */\n setLEDMode () {\n const cmd = this.generateInputCommand(\n WeDo2ConnectID.LED,\n WeDo2Device.LED,\n WeDo2Mode.LED,\n 0,\n WeDo2Unit.LED,\n false\n );\n\n return this.send(BLECharacteristic.INPUT_COMMAND, cmd);\n }\n\n /**\n * Switch off the LED on the WeDo 2.0.\n * @return {Promise} - a promise of the completion of the stop led send operation.\n */\n stopLED () {\n const cmd = this.generateOutputCommand(\n WeDo2ConnectID.LED,\n WeDo2Command.WRITE_RGB,\n [0, 0, 0]\n );\n\n return this.send(BLECharacteristic.OUTPUT_COMMAND, cmd);\n }\n\n /**\n * Play a tone from the WeDo 2.0 peripheral for a specific amount of time.\n * @param {int} tone - the pitch of the tone, in Hz.\n * @param {int} milliseconds - the duration of the note, in milliseconds.\n * @return {Promise} - a promise of the completion of the play tone send operation.\n */\n playTone (tone, milliseconds) {\n const cmd = this.generateOutputCommand(\n WeDo2ConnectID.PIEZO,\n WeDo2Command.PLAY_TONE,\n [\n tone,\n tone >> 8,\n milliseconds,\n milliseconds >> 8\n ]\n );\n\n return this.send(BLECharacteristic.OUTPUT_COMMAND, cmd);\n }\n\n /**\n * Stop the tone playing from the WeDo 2.0 peripheral, if any.\n * @return {Promise} - a promise that the command sent.\n */\n stopTone () {\n const cmd = this.generateOutputCommand(\n WeDo2ConnectID.PIEZO,\n WeDo2Command.STOP_TONE\n );\n\n // Send this command without using the rate limiter, because it is\n // only triggered by the stop button.\n return this.send(BLECharacteristic.OUTPUT_COMMAND, cmd, false);\n }\n\n /**\n * Stop the tone playing and motors on the WeDo 2.0 peripheral.\n */\n stopAll () {\n if (!this.isConnected()) return;\n this.stopTone();\n this.stopAllMotors();\n }\n\n /**\n * Called by the runtime when user wants to scan for a WeDo 2.0 peripheral.\n */\n scan () {\n if (this._ble) {\n this._ble.disconnect();\n }\n this._ble = new BLE(this._runtime, this._extensionId, {\n filters: [{\n services: [BLEService.DEVICE_SERVICE]\n }],\n optionalServices: [BLEService.IO_SERVICE]\n }, this._onConnect, this.reset);\n }\n\n /**\n * Called by the runtime when user wants to connect to a certain WeDo 2.0 peripheral.\n * @param {number} id - the id of the peripheral to connect to.\n */\n connect (id) {\n if (this._ble) {\n this._ble.connectPeripheral(id);\n }\n }\n\n /**\n * Disconnects from the current BLE socket.\n */\n disconnect () {\n if (this._ble) {\n this._ble.disconnect();\n }\n\n this.reset();\n }\n\n /**\n * Reset all the state and timeout/interval ids.\n */\n reset () {\n this._ports = ['none', 'none'];\n this._motors = [null, null];\n this._sensors = {\n tiltX: 0,\n tiltY: 0,\n distance: 0\n };\n\n if (this._batteryLevelIntervalId) {\n window.clearInterval(this._batteryLevelIntervalId);\n this._batteryLevelIntervalId = null;\n }\n }\n\n /**\n * Called by the runtime to detect whether the WeDo 2.0 peripheral is connected.\n * @return {boolean} - the connected state.\n */\n isConnected () {\n let connected = false;\n if (this._ble) {\n connected = this._ble.isConnected();\n }\n return connected;\n }\n\n /**\n * Write a message to the WeDo 2.0 peripheral BLE socket.\n * @param {number} uuid - the UUID of the characteristic to write to\n * @param {Array} message - the message to write.\n * @param {boolean} [useLimiter=true] - if true, use the rate limiter\n * @return {Promise} - a promise result of the write operation\n */\n send (uuid, message, useLimiter = true) {\n if (!this.isConnected()) return Promise.resolve();\n\n if (useLimiter) {\n if (!this._rateLimiter.okayToSend()) return Promise.resolve();\n }\n\n return this._ble.write(\n BLEService.IO_SERVICE,\n uuid,\n Base64Util.uint8ArrayToBase64(message),\n 'base64'\n );\n }\n\n /**\n * Generate a WeDo 2.0 'Output Command' in the byte array format\n * (CONNECT ID, COMMAND ID, NUMBER OF BYTES, VALUES ...).\n *\n * This sends a command to the WeDo 2.0 to actuate the specified outputs.\n *\n * @param {number} connectID - the port (Connect ID) to send a command to.\n * @param {number} commandID - the id of the byte command.\n * @param {array} values - the list of values to write to the command.\n * @return {array} - a generated output command.\n */\n generateOutputCommand (connectID, commandID, values = null) {\n let command = [connectID, commandID];\n if (values) {\n command = command.concat(\n values.length\n ).concat(\n values\n );\n }\n return command;\n }\n\n /**\n * Generate a WeDo 2.0 'Input Command' in the byte array format\n * (COMMAND ID, COMMAND TYPE, CONNECT ID, TYPE ID, MODE, DELTA INTERVAL (4 BYTES),\n * UNIT, NOTIFICATIONS ENABLED).\n *\n * This sends a command to the WeDo 2.0 that sets that input format\n * of the specified inputs and sets value change notifications.\n *\n * @param {number} connectID - the port (Connect ID) to send a command to.\n * @param {number} type - the type of input sensor.\n * @param {number} mode - the mode of the input sensor.\n * @param {number} delta - the delta change needed to trigger notification.\n * @param {array} units - the unit of the input sensor value.\n * @param {boolean} enableNotifications - whether to enable notifications.\n * @return {array} - a generated input command.\n */\n generateInputCommand (connectID, type, mode, delta, units, enableNotifications) {\n const command = [\n 1, // Command ID = 1 = \"Sensor Format\"\n 2, // Command Type = 2 = \"Write\"\n connectID,\n type,\n mode,\n delta,\n 0, // Delta Interval Byte 2\n 0, // Delta Interval Byte 3\n 0, // Delta Interval Byte 4\n units,\n enableNotifications ? 1 : 0\n ];\n\n return command;\n }\n\n /**\n * Sets LED mode and initial color and starts reading data from peripheral after BLE has connected.\n * @private\n */\n _onConnect () {\n this.setLEDMode();\n this.setLED(0x0000FF);\n this._ble.startNotifications(\n BLEService.DEVICE_SERVICE,\n BLECharacteristic.ATTACHED_IO,\n this._onMessage\n );\n this._batteryLevelIntervalId = window.setInterval(this._checkBatteryLevel, BLEBatteryCheckInterval);\n }\n\n /**\n * Process the sensor data from the incoming BLE characteristic.\n * @param {object} base64 - the incoming BLE data.\n * @private\n */\n _onMessage (base64) {\n const data = Base64Util.base64ToUint8Array(base64);\n // log.info(data);\n\n /**\n * If first byte of data is '1' or '2', then either clear the\n * sensor present in ports 1 or 2 or set their format.\n *\n * If first byte of data is anything else, read incoming sensor value.\n */\n switch (data[0]) {\n case 1:\n case 2: {\n const connectID = data[0];\n if (data[1] === 0) {\n // clear sensor or motor\n this._clearPort(connectID);\n } else {\n // register sensor or motor\n this._registerSensorOrMotor(connectID, data[3]);\n }\n break;\n }\n default: {\n // read incoming sensor value\n const connectID = data[1];\n const type = this._ports[connectID - 1];\n if (type === WeDo2Device.DISTANCE) {\n this._sensors.distance = data[2];\n }\n if (type === WeDo2Device.TILT) {\n this._sensors.tiltX = data[2];\n this._sensors.tiltY = data[3];\n }\n break;\n }\n }\n }\n\n /**\n * Check the battery level on the WeDo 2.0. If the WeDo 2.0 has disconnected\n * for some reason, the BLE socket will get an error back and automatically\n * close the socket.\n */\n _checkBatteryLevel () {\n this._ble.read(\n BLEService.DEVICE_SERVICE,\n BLECharacteristic.LOW_VOLTAGE_ALERT,\n false\n );\n }\n\n /**\n * Register a new sensor or motor connected at a port. Store the type of\n * sensor or motor internally, and then register for notifications on input\n * values if it is a sensor.\n * @param {number} connectID - the port to register a sensor or motor on.\n * @param {number} type - the type ID of the sensor or motor\n * @private\n */\n _registerSensorOrMotor (connectID, type) {\n // Record which port is connected to what type of device\n this._ports[connectID - 1] = type;\n\n // Record motor port\n if (type === WeDo2Device.MOTOR) {\n this._motors[connectID - 1] = new WeDo2Motor(this, connectID - 1);\n } else {\n // Set input format for tilt or distance sensor\n const typeString = type === WeDo2Device.DISTANCE ? 'DISTANCE' : 'TILT';\n const cmd = this.generateInputCommand(\n connectID,\n type,\n WeDo2Mode[typeString],\n 1,\n WeDo2Unit[typeString],\n true\n );\n\n this.send(BLECharacteristic.INPUT_COMMAND, cmd);\n this._ble.startNotifications(\n BLEService.IO_SERVICE,\n BLECharacteristic.INPUT_VALUES,\n this._onMessage\n );\n }\n }\n\n /**\n * Clear the sensor or motor present at port 1 or 2.\n * @param {number} connectID - the port to clear.\n * @private\n */\n _clearPort (connectID) {\n const type = this._ports[connectID - 1];\n if (type === WeDo2Device.TILT) {\n this._sensors.tiltX = this._sensors.tiltY = 0;\n }\n if (type === WeDo2Device.DISTANCE) {\n this._sensors.distance = 0;\n }\n this._ports[connectID - 1] = 'none';\n this._motors[connectID - 1] = null;\n }\n}\n\n/**\n * Enum for motor specification.\n * @readonly\n * @enum {string}\n */\nconst WeDo2MotorLabel = {\n DEFAULT: 'motor',\n A: 'motor A',\n B: 'motor B',\n ALL: 'all motors'\n};\n\n/**\n * Enum for motor direction specification.\n * @readonly\n * @enum {string}\n */\nconst WeDo2MotorDirection = {\n FORWARD: 'this way',\n BACKWARD: 'that way',\n REVERSE: 'reverse'\n};\n\n/**\n * Enum for tilt sensor direction.\n * @readonly\n * @enum {string}\n */\nconst WeDo2TiltDirection = {\n UP: 'up',\n DOWN: 'down',\n LEFT: 'left',\n RIGHT: 'right',\n ANY: 'any'\n};\n\n/**\n * Scratch 3.0 blocks to interact with a LEGO WeDo 2.0 peripheral.\n */\nclass Scratch3WeDo2Blocks {\n\n /**\n * @return {string} - the ID of this extension.\n */\n static get EXTENSION_ID () {\n return 'wedo2';\n }\n\n /**\n * @return {number} - the tilt sensor counts as \"tilted\" if its tilt angle meets or exceeds this threshold.\n */\n static get TILT_THRESHOLD () {\n return 15;\n }\n\n /**\n * Construct a set of WeDo 2.0 blocks.\n * @param {Runtime} runtime - the Scratch 3.0 runtime.\n */\n constructor (runtime) {\n /**\n * The Scratch 3.0 runtime.\n * @type {Runtime}\n */\n this.runtime = runtime;\n\n // Create a new WeDo 2.0 peripheral instance\n this._peripheral = new WeDo2(this.runtime, Scratch3WeDo2Blocks.EXTENSION_ID);\n }\n\n /**\n * @returns {object} metadata for this extension and its blocks.\n */\n getInfo () {\n return {\n id: Scratch3WeDo2Blocks.EXTENSION_ID,\n name: 'WeDo 2.0',\n blockIconURI: iconURI,\n showStatusButton: true,\n blocks: [\n {\n opcode: 'motorOnFor',\n text: formatMessage({\n id: 'wedo2.motorOnFor',\n default: 'turn [MOTOR_ID] on for [DURATION] seconds',\n description: 'turn a motor on for some time'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: WeDo2MotorLabel.DEFAULT\n },\n DURATION: {\n type: ArgumentType.NUMBER,\n defaultValue: 1\n }\n }\n },\n {\n opcode: 'motorOn',\n text: formatMessage({\n id: 'wedo2.motorOn',\n default: 'turn [MOTOR_ID] on',\n description: 'turn a motor on indefinitely'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: WeDo2MotorLabel.DEFAULT\n }\n }\n },\n {\n opcode: 'motorOff',\n text: formatMessage({\n id: 'wedo2.motorOff',\n default: 'turn [MOTOR_ID] off',\n description: 'turn a motor off'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: WeDo2MotorLabel.DEFAULT\n }\n }\n },\n {\n opcode: 'startMotorPower',\n text: formatMessage({\n id: 'wedo2.startMotorPower',\n default: 'set [MOTOR_ID] power to [POWER]',\n description: 'set the motor\\'s power and turn it on'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: WeDo2MotorLabel.DEFAULT\n },\n POWER: {\n type: ArgumentType.NUMBER,\n defaultValue: 100\n }\n }\n },\n {\n opcode: 'setMotorDirection',\n text: formatMessage({\n id: 'wedo2.setMotorDirection',\n default: 'set [MOTOR_ID] direction to [MOTOR_DIRECTION]',\n description: 'set the motor\\'s turn direction'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n MOTOR_ID: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_ID',\n defaultValue: WeDo2MotorLabel.DEFAULT\n },\n MOTOR_DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'MOTOR_DIRECTION',\n defaultValue: WeDo2MotorDirection.FORWARD\n }\n }\n },\n {\n opcode: 'setLightHue',\n text: formatMessage({\n id: 'wedo2.setLightHue',\n default: 'set light color to [HUE]',\n description: 'set the LED color'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n HUE: {\n type: ArgumentType.NUMBER,\n defaultValue: 50\n }\n }\n },\n {\n opcode: 'playNoteFor',\n text: formatMessage({\n id: 'wedo2.playNoteFor',\n default: 'play note [NOTE] for [DURATION] seconds',\n description: 'play a certain note for some time'\n }),\n blockType: BlockType.COMMAND,\n arguments: {\n NOTE: {\n type: ArgumentType.NUMBER, // TODO: ArgumentType.MIDI_NOTE?\n defaultValue: 60\n },\n DURATION: {\n type: ArgumentType.NUMBER,\n defaultValue: 0.5\n }\n },\n hideFromPalette: true\n },\n {\n opcode: 'whenDistance',\n text: formatMessage({\n id: 'wedo2.whenDistance',\n default: 'when distance [OP] [REFERENCE]',\n description: 'check for when distance is < or > than reference'\n }),\n blockType: BlockType.HAT,\n arguments: {\n OP: {\n type: ArgumentType.STRING,\n menu: 'OP',\n defaultValue: '<'\n },\n REFERENCE: {\n type: ArgumentType.NUMBER,\n defaultValue: 50\n }\n }\n },\n {\n opcode: 'whenTilted',\n text: formatMessage({\n id: 'wedo2.whenTilted',\n default: 'when tilted [TILT_DIRECTION_ANY]',\n description: 'check when tilted in a certain direction'\n }),\n func: 'isTilted',\n blockType: BlockType.HAT,\n arguments: {\n TILT_DIRECTION_ANY: {\n type: ArgumentType.STRING,\n menu: 'TILT_DIRECTION_ANY',\n defaultValue: WeDo2TiltDirection.ANY\n }\n }\n },\n {\n opcode: 'getDistance',\n text: formatMessage({\n id: 'wedo2.getDistance',\n default: 'distance',\n description: 'the value returned by the distance sensor'\n }),\n blockType: BlockType.REPORTER\n },\n {\n opcode: 'isTilted',\n text: formatMessage({\n id: 'wedo2.isTilted',\n default: 'tilted [TILT_DIRECTION_ANY]?',\n description: 'whether the tilt sensor is tilted'\n }),\n blockType: BlockType.BOOLEAN,\n arguments: {\n TILT_DIRECTION_ANY: {\n type: ArgumentType.STRING,\n menu: 'TILT_DIRECTION_ANY',\n defaultValue: WeDo2TiltDirection.ANY\n }\n }\n },\n {\n opcode: 'getTiltAngle',\n text: formatMessage({\n id: 'wedo2.getTiltAngle',\n default: 'tilt angle [TILT_DIRECTION]',\n description: 'the angle returned by the tilt sensor'\n }),\n blockType: BlockType.REPORTER,\n arguments: {\n TILT_DIRECTION: {\n type: ArgumentType.STRING,\n menu: 'TILT_DIRECTION',\n defaultValue: WeDo2TiltDirection.UP\n }\n }\n }\n ],\n menus: {\n MOTOR_ID: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'wedo2.motorId.default',\n default: 'motor',\n description: 'label for motor element in motor menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2MotorLabel.DEFAULT\n },\n {\n text: formatMessage({\n id: 'wedo2.motorId.a',\n default: 'motor A',\n description: 'label for motor A element in motor menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2MotorLabel.A\n },\n {\n text: formatMessage({\n id: 'wedo2.motorId.b',\n default: 'motor B',\n description: 'label for motor B element in motor menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2MotorLabel.B\n },\n {\n text: formatMessage({\n id: 'wedo2.motorId.all',\n default: 'all motors',\n description: 'label for all motors element in motor menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2MotorLabel.ALL\n }\n ]\n },\n MOTOR_DIRECTION: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'wedo2.motorDirection.forward',\n default: 'this way',\n description:\n 'label for forward element in motor direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2MotorDirection.FORWARD\n },\n {\n text: formatMessage({\n id: 'wedo2.motorDirection.backward',\n default: 'that way',\n description:\n 'label for backward element in motor direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2MotorDirection.BACKWARD\n },\n {\n text: formatMessage({\n id: 'wedo2.motorDirection.reverse',\n default: 'reverse',\n description:\n 'label for reverse element in motor direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2MotorDirection.REVERSE\n }\n ]\n },\n TILT_DIRECTION: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.up',\n default: 'up',\n description: 'label for up element in tilt direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2TiltDirection.UP\n },\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.down',\n default: 'down',\n description: 'label for down element in tilt direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2TiltDirection.DOWN\n },\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.left',\n default: 'left',\n description: 'label for left element in tilt direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2TiltDirection.LEFT\n },\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.right',\n default: 'right',\n description: 'label for right element in tilt direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2TiltDirection.RIGHT\n }\n ]\n },\n TILT_DIRECTION_ANY: {\n acceptReporters: true,\n items: [\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.up',\n default: 'up'\n }),\n value: WeDo2TiltDirection.UP\n },\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.down',\n default: 'down'\n }),\n value: WeDo2TiltDirection.DOWN\n },\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.left',\n default: 'left'\n }),\n value: WeDo2TiltDirection.LEFT\n },\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.right',\n default: 'right'\n }),\n value: WeDo2TiltDirection.RIGHT\n },\n {\n text: formatMessage({\n id: 'wedo2.tiltDirection.any',\n default: 'any',\n description: 'label for any element in tilt direction menu for LEGO WeDo 2 extension'\n }),\n value: WeDo2TiltDirection.ANY\n }\n ]\n },\n OP: {\n acceptReporters: true,\n items: ['<', '>']\n }\n }\n };\n }\n\n /**\n * Turn specified motor(s) on for a specified duration.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to activate.\n * @property {int} DURATION - the amount of time to run the motors.\n * @return {Promise} - a promise which will resolve at the end of the duration.\n */\n motorOnFor (args) {\n // TODO: cast args.MOTOR_ID?\n let durationMS = Cast.toNumber(args.DURATION) * 1000;\n durationMS = MathUtil.clamp(durationMS, 0, 15000);\n return new Promise(resolve => {\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.turnOnFor(durationMS);\n }\n });\n\n // Run for some time even when no motor is connected\n setTimeout(resolve, durationMS);\n });\n }\n\n /**\n * Turn specified motor(s) on indefinitely.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to activate.\n * @return {Promise} - a Promise that resolves after some delay.\n */\n motorOn (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.turnOn();\n }\n });\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BLESendInterval);\n });\n }\n\n /**\n * Turn specified motor(s) off.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to deactivate.\n * @return {Promise} - a Promise that resolves after some delay.\n */\n motorOff (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.turnOff();\n }\n });\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BLESendInterval);\n });\n }\n\n /**\n * Turn specified motor(s) off.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to be affected.\n * @property {int} POWER - the new power level for the motor(s).\n * @return {Promise} - a Promise that resolves after some delay.\n */\n startMotorPower (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n motor.power = MathUtil.clamp(Cast.toNumber(args.POWER), 0, 100);\n motor.turnOn();\n }\n });\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BLESendInterval);\n });\n }\n\n /**\n * Set the direction of rotation for specified motor(s).\n * If the direction is 'reverse' the motor(s) will be reversed individually.\n * @param {object} args - the block's arguments.\n * @property {MotorID} MOTOR_ID - the motor(s) to be affected.\n * @property {MotorDirection} MOTOR_DIRECTION - the new direction for the motor(s).\n * @return {Promise} - a Promise that resolves after some delay.\n */\n setMotorDirection (args) {\n // TODO: cast args.MOTOR_ID?\n this._forEachMotor(args.MOTOR_ID, motorIndex => {\n const motor = this._peripheral.motor(motorIndex);\n if (motor) {\n switch (args.MOTOR_DIRECTION) {\n case WeDo2MotorDirection.FORWARD:\n motor.direction = 1;\n break;\n case WeDo2MotorDirection.BACKWARD:\n motor.direction = -1;\n break;\n case WeDo2MotorDirection.REVERSE:\n motor.direction = -motor.direction;\n break;\n default:\n log.warn(`Unknown motor direction in setMotorDirection: ${args.DIRECTION}`);\n break;\n }\n // keep the motor on if it's running, and update the pending timeout if needed\n if (motor.isOn) {\n if (motor.pendingTimeoutDelay) {\n motor.turnOnFor(motor.pendingTimeoutStartTime + motor.pendingTimeoutDelay - Date.now());\n } else {\n motor.turnOn();\n }\n }\n }\n });\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BLESendInterval);\n });\n }\n\n /**\n * Set the LED's hue.\n * @param {object} args - the block's arguments.\n * @property {number} HUE - the hue to set, in the range [0,100].\n * @return {Promise} - a Promise that resolves after some delay.\n */\n setLightHue (args) {\n // Convert from [0,100] to [0,360]\n let inputHue = Cast.toNumber(args.HUE);\n inputHue = MathUtil.wrapClamp(inputHue, 0, 100);\n const hue = inputHue * 360 / 100;\n\n const rgbObject = color.hsvToRgb({h: hue, s: 1, v: 1});\n\n const rgbDecimal = color.rgbToDecimal(rgbObject);\n\n this._peripheral.setLED(rgbDecimal);\n\n return new Promise(resolve => {\n window.setTimeout(() => {\n resolve();\n }, BLESendInterval);\n });\n }\n\n /**\n * Make the WeDo 2.0 peripheral play a MIDI note for the specified duration.\n * @param {object} args - the block's arguments.\n * @property {number} NOTE - the MIDI note to play.\n * @property {number} DURATION - the duration of the note, in seconds.\n * @return {Promise} - a promise which will resolve at the end of the duration.\n */\n playNoteFor (args) {\n let durationMS = Cast.toNumber(args.DURATION) * 1000;\n durationMS = MathUtil.clamp(durationMS, 0, 3000);\n const note = MathUtil.clamp(Cast.toNumber(args.NOTE), 25, 125); // valid WeDo 2.0 sounds\n if (durationMS === 0) return; // WeDo 2.0 plays duration '0' forever\n return new Promise(resolve => {\n const tone = this._noteToTone(note);\n this._peripheral.playTone(tone, durationMS);\n\n // Run for some time even when no piezo is connected\n setTimeout(resolve, durationMS);\n });\n }\n\n /**\n * Compare the distance sensor's value to a reference.\n * @param {object} args - the block's arguments.\n * @property {string} OP - the comparison operation: '<' or '>'.\n * @property {number} REFERENCE - the value to compare against.\n * @return {boolean} - the result of the comparison, or false on error.\n */\n whenDistance (args) {\n switch (args.OP) {\n case '<':\n return this._peripheral.distance < Cast.toNumber(args.REFERENCE);\n case '>':\n return this._peripheral.distance > Cast.toNumber(args.REFERENCE);\n default:\n log.warn(`Unknown comparison operator in whenDistance: ${args.OP}`);\n return false;\n }\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} TILT_DIRECTION_ANY - the tilt direction to test (up, down, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n */\n whenTilted (args) {\n return this._isTilted(args.TILT_DIRECTION_ANY);\n }\n\n /**\n * @return {number} - the distance sensor's value, scaled to the [0,100] range.\n */\n getDistance () {\n return this._peripheral.distance;\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} TILT_DIRECTION_ANY - the tilt direction to test (up, down, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n */\n isTilted (args) {\n return this._isTilted(args.TILT_DIRECTION_ANY);\n }\n\n /**\n * @param {object} args - the block's arguments.\n * @property {TiltDirection} TILT_DIRECTION - the direction (up, down, left, right) to check.\n * @return {number} - the tilt sensor's angle in the specified direction.\n * Note that getTiltAngle(up) = -getTiltAngle(down) and getTiltAngle(left) = -getTiltAngle(right).\n */\n getTiltAngle (args) {\n return this._getTiltAngle(args.TILT_DIRECTION);\n }\n\n /**\n * Test whether the tilt sensor is currently tilted.\n * @param {TiltDirection} direction - the tilt direction to test (up, down, left, right, or any).\n * @return {boolean} - true if the tilt sensor is tilted past a threshold in the specified direction.\n * @private\n */\n _isTilted (direction) {\n switch (direction) {\n case WeDo2TiltDirection.ANY:\n return this._getTiltAngle(WeDo2TiltDirection.UP) >= Scratch3WeDo2Blocks.TILT_THRESHOLD ||\n this._getTiltAngle(WeDo2TiltDirection.DOWN) >= Scratch3WeDo2Blocks.TILT_THRESHOLD ||\n this._getTiltAngle(WeDo2TiltDirection.LEFT) >= Scratch3WeDo2Blocks.TILT_THRESHOLD ||\n this._getTiltAngle(WeDo2TiltDirection.RIGHT) >= Scratch3WeDo2Blocks.TILT_THRESHOLD;\n default:\n return this._getTiltAngle(direction) >= Scratch3WeDo2Blocks.TILT_THRESHOLD;\n }\n }\n\n /**\n * @param {TiltDirection} direction - the direction (up, down, left, right) to check.\n * @return {number} - the tilt sensor's angle in the specified direction.\n * Note that getTiltAngle(up) = -getTiltAngle(down) and getTiltAngle(left) = -getTiltAngle(right).\n * @private\n */\n _getTiltAngle (direction) {\n switch (direction) {\n case WeDo2TiltDirection.UP:\n return this._peripheral.tiltY > 45 ? 256 - this._peripheral.tiltY : -this._peripheral.tiltY;\n case WeDo2TiltDirection.DOWN:\n return this._peripheral.tiltY > 45 ? this._peripheral.tiltY - 256 : this._peripheral.tiltY;\n case WeDo2TiltDirection.LEFT:\n return this._peripheral.tiltX > 45 ? 256 - this._peripheral.tiltX : -this._peripheral.tiltX;\n case WeDo2TiltDirection.RIGHT:\n return this._peripheral.tiltX > 45 ? this._peripheral.tiltX - 256 : this._peripheral.tiltX;\n default:\n log.warn(`Unknown tilt direction in _getTiltAngle: ${direction}`);\n }\n }\n\n /**\n * Call a callback for each motor indexed by the provided motor ID.\n * @param {MotorID} motorID - the ID specifier.\n * @param {Function} callback - the function to call with the numeric motor index for each motor.\n * @private\n */\n _forEachMotor (motorID, callback) {\n let motors;\n switch (motorID) {\n case WeDo2MotorLabel.A:\n motors = [0];\n break;\n case WeDo2MotorLabel.B:\n motors = [1];\n break;\n case WeDo2MotorLabel.ALL:\n case WeDo2MotorLabel.DEFAULT:\n motors = [0, 1];\n break;\n default:\n log.warn(`Invalid motor ID: ${motorID}`);\n motors = [];\n break;\n }\n for (const index of motors) {\n callback(index);\n }\n }\n\n /**\n * @param {number} midiNote - the MIDI note value to convert.\n * @return {number} - the frequency, in Hz, corresponding to that MIDI note value.\n * @private\n */\n _noteToTone (midiNote) {\n // Note that MIDI note 69 is A4, 440 Hz\n return 440 * Math.pow(2, (midiNote - 69) / 12);\n }\n}\n\nmodule.exports = Scratch3WeDo2Blocks;\n","const StringUtil = require('../util/string-util');\nconst log = require('../util/log');\n\nconst loadVector_ = function (costume, runtime, rotationCenter, optVersion) {\n return new Promise(resolve => {\n let svgString = costume.asset.decodeText();\n // SVG Renderer load fixes \"quirks\" associated with Scratch 2 projects\n if (optVersion && optVersion === 2 && !runtime.v2SvgAdapter) {\n log.error('No V2 SVG adapter present; SVGs may not render correctly.');\n } else if (optVersion && optVersion === 2 && runtime.v2SvgAdapter) {\n runtime.v2SvgAdapter.loadString(svgString, true /* fromVersion2 */);\n svgString = runtime.v2SvgAdapter.toString();\n // Put back into storage\n const storage = runtime.storage;\n costume.asset.encodeTextData(svgString, storage.DataFormat.SVG, true);\n costume.assetId = costume.asset.assetId;\n costume.md5 = `${costume.assetId}.${costume.dataFormat}`;\n }\n // createSVGSkin does the right thing if rotationCenter isn't provided, so it's okay if it's\n // undefined here\n costume.skinId = runtime.renderer.createSVGSkin(svgString, rotationCenter);\n costume.size = runtime.renderer.getSkinSize(costume.skinId);\n // Now we should have a rotationCenter even if we didn't before\n if (!rotationCenter) {\n rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);\n costume.rotationCenterX = rotationCenter[0];\n costume.rotationCenterY = rotationCenter[1];\n costume.bitmapResolution = 1;\n }\n\n resolve(costume);\n });\n};\n\nconst canvasPool = (function () {\n /**\n * A pool of canvas objects that can be reused to reduce memory\n * allocations. And time spent in those allocations and the later garbage\n * collection.\n */\n class CanvasPool {\n constructor () {\n this.pool = [];\n this.clearSoon = null;\n }\n\n /**\n * After a short wait period clear the pool to let the VM collect\n * garbage.\n */\n clear () {\n if (!this.clearSoon) {\n this.clearSoon = new Promise(resolve => setTimeout(resolve, 1000))\n .then(() => {\n this.pool.length = 0;\n this.clearSoon = null;\n });\n }\n }\n\n /**\n * Return a canvas. Create the canvas if the pool is empty.\n * @returns {HTMLCanvasElement} A canvas element.\n */\n create () {\n return this.pool.pop() || document.createElement('canvas');\n }\n\n /**\n * Release the canvas to be reused.\n * @param {HTMLCanvasElement} canvas A canvas element.\n */\n release (canvas) {\n this.clear();\n this.pool.push(canvas);\n }\n }\n\n return new CanvasPool();\n}());\n\n/**\n * Return a promise to fetch a bitmap from storage and return it as a canvas\n * If the costume has bitmapResolution 1, it will be converted to bitmapResolution 2 here (the standard for Scratch 3)\n * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function\n * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information.\n * @param {!object} costume - the Scratch costume object.\n * @param {!Runtime} runtime - Scratch runtime, used to access the v2BitmapAdapter\n * @param {?object} rotationCenter - optionally passed in coordinates for the center of rotation for the image. If\n * none is given, the rotation center of the costume will be set to the middle of the costume later on.\n * @property {number} costume.bitmapResolution - the resolution scale for a bitmap costume.\n * @returns {?Promise} - a promise which will resolve to an object {canvas, rotationCenter, assetMatchesBase},\n * or reject on error.\n * assetMatchesBase is true if the asset matches the base layer; false if it required adjustment\n */\nconst fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) {\n if (!costume || !costume.asset) {\n return Promise.reject('Costume load failed. Assets were missing.');\n }\n if (!runtime.v2BitmapAdapter) {\n return Promise.reject('No V2 Bitmap adapter present.');\n }\n\n return Promise.all([costume.asset, costume.textLayerAsset].map(asset => {\n if (!asset) {\n return null;\n }\n\n if (typeof createImageBitmap !== 'undefined') {\n return createImageBitmap(\n new Blob([asset.data], {type: asset.assetType.contentType})\n );\n }\n\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.onload = function () {\n resolve(image);\n image.onload = null;\n image.onerror = null;\n };\n image.onerror = function () {\n reject('Costume load failed. Asset could not be read.');\n image.onload = null;\n image.onerror = null;\n };\n image.src = asset.encodeDataURI();\n });\n }))\n .then(([baseImageElement, textImageElement]) => {\n const mergeCanvas = canvasPool.create();\n\n const scale = costume.bitmapResolution === 1 ? 2 : 1;\n mergeCanvas.width = baseImageElement.width;\n mergeCanvas.height = baseImageElement.height;\n\n const ctx = mergeCanvas.getContext('2d');\n ctx.drawImage(baseImageElement, 0, 0);\n if (textImageElement) {\n ctx.drawImage(textImageElement, 0, 0);\n }\n // Track the canvas we merged the bitmaps onto separately from the\n // canvas that we receive from resize if scale is not 1. We know\n // resize treats mergeCanvas as read only data. We don't know when\n // resize may use or modify the canvas. So we'll only release the\n // mergeCanvas back into the canvas pool. Reusing the canvas from\n // resize may cause errors.\n let canvas = mergeCanvas;\n if (scale !== 1) {\n canvas = runtime.v2BitmapAdapter.resize(mergeCanvas, canvas.width * scale, canvas.height * scale);\n }\n\n // By scaling, we've converted it to bitmap resolution 2\n if (rotationCenter) {\n rotationCenter[0] = rotationCenter[0] * scale;\n rotationCenter[1] = rotationCenter[1] * scale;\n costume.rotationCenterX = rotationCenter[0];\n costume.rotationCenterY = rotationCenter[1];\n }\n costume.bitmapResolution = 2;\n\n // Clean up the costume object\n delete costume.textLayerMD5;\n delete costume.textLayerAsset;\n\n return {\n canvas,\n mergeCanvas,\n rotationCenter,\n // True if the asset matches the base layer; false if it required adjustment\n assetMatchesBase: scale === 1 && !textImageElement\n };\n })\n .catch(() => {\n // Clean up the text layer properties if it fails to load\n delete costume.textLayerMD5;\n delete costume.textLayerAsset;\n });\n};\n\nconst loadBitmap_ = function (costume, runtime, _rotationCenter) {\n return fetchBitmapCanvas_(costume, runtime, _rotationCenter)\n .then(fetched => {\n const updateCostumeAsset = function (dataURI) {\n if (!runtime.v2BitmapAdapter) {\n // TODO: This might be a bad practice since the returned\n // promise isn't acted on. If this is something we should be\n // creating a rejected promise for we should also catch it\n // somewhere and act on that error (like logging).\n //\n // Return a rejection to stop executing updateCostumeAsset.\n return Promise.reject('No V2 Bitmap adapter present.');\n }\n\n const storage = runtime.storage;\n costume.asset = storage.createAsset(\n storage.AssetType.ImageBitmap,\n storage.DataFormat.PNG,\n runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI),\n null,\n true // generate md5\n );\n costume.dataFormat = storage.DataFormat.PNG;\n costume.assetId = costume.asset.assetId;\n costume.md5 = `${costume.assetId}.${costume.dataFormat}`;\n };\n\n if (!fetched.assetMatchesBase) {\n updateCostumeAsset(fetched.canvas.toDataURL());\n }\n\n return fetched;\n })\n .then(({canvas, mergeCanvas, rotationCenter}) => {\n // createBitmapSkin does the right thing if costume.rotationCenter is undefined.\n // That will be the case if you upload a bitmap asset or create one by taking a photo.\n let center;\n if (rotationCenter) {\n // fetchBitmapCanvas will ensure that the costume's bitmap resolution is 2 and its rotation center is\n // scaled to match, so it's okay to always divide by 2.\n center = [\n rotationCenter[0] / 2,\n rotationCenter[1] / 2\n ];\n }\n\n // TODO: costume.bitmapResolution will always be 2 at this point because of fetchBitmapCanvas_, so we don't\n // need to pass it in here.\n costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, center);\n canvasPool.release(mergeCanvas);\n const renderSize = runtime.renderer.getSkinSize(costume.skinId);\n costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2\n\n if (!rotationCenter) {\n rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);\n // Actual rotation center, since all bitmaps are resolution 2\n costume.rotationCenterX = rotationCenter[0] * 2;\n costume.rotationCenterY = rotationCenter[1] * 2;\n costume.bitmapResolution = 2;\n }\n return costume;\n });\n};\n\n/**\n * Initialize a costume from an asset asynchronously.\n * Do not call this unless there is a renderer attached.\n * @param {!object} costume - the Scratch costume object.\n * @property {int} skinId - the ID of the costume's render skin, once installed.\n * @property {number} rotationCenterX - the X component of the costume's origin.\n * @property {number} rotationCenterY - the Y component of the costume's origin.\n * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.\n * @property {!Asset} costume.asset - the asset of the costume loaded from storage.\n * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.\n * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set\n * to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.\n * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.\n */\nconst loadCostumeFromAsset = function (costume, runtime, optVersion) {\n costume.assetId = costume.asset.assetId;\n const renderer = runtime.renderer;\n if (!renderer) {\n log.error('No rendering module present; cannot load costume: ', costume.name);\n return Promise.resolve(costume);\n }\n const AssetType = runtime.storage.AssetType;\n let rotationCenter;\n // Use provided rotation center and resolution if they are defined. Bitmap resolution\n // should only ever be 1 or 2.\n if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) &&\n typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) {\n rotationCenter = [costume.rotationCenterX, costume.rotationCenterY];\n }\n if (costume.asset.assetType.runtimeFormat === AssetType.ImageVector.runtimeFormat) {\n return loadVector_(costume, runtime, rotationCenter, optVersion)\n .catch(error => {\n log.warn(`Error loading vector image: ${error.name}: ${error.message}`);\n // Use default asset if original fails to load\n costume.assetId = runtime.storage.defaultAssetId.ImageVector;\n costume.asset = runtime.storage.get(costume.assetId);\n costume.md5 = `${costume.assetId}.${AssetType.ImageVector.runtimeFormat}`;\n return loadVector_(costume, runtime);\n });\n }\n return loadBitmap_(costume, runtime, rotationCenter, optVersion);\n};\n\n/**\n * Load a costume's asset into memory asynchronously.\n * Do not call this unless there is a renderer attached.\n * @param {!string} md5ext - the MD5 and extension of the costume to be loaded.\n * @param {!object} costume - the Scratch costume object.\n * @property {int} skinId - the ID of the costume's render skin, once installed.\n * @property {number} rotationCenterX - the X component of the costume's origin.\n * @property {number} rotationCenterY - the Y component of the costume's origin.\n * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.\n * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.\n * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set\n * to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.\n * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.\n */\nconst loadCostume = function (md5ext, costume, runtime, optVersion) {\n const idParts = StringUtil.splitFirst(md5ext, '.');\n const md5 = idParts[0];\n const ext = idParts[1].toLowerCase();\n costume.dataFormat = ext;\n\n if (costume.asset) {\n // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or file\n return loadCostumeFromAsset(costume, runtime, optVersion);\n }\n\n // Need to load the costume from storage. The server should have a reference to this md5.\n if (!runtime.storage) {\n log.error('No storage module present; cannot load costume asset: ', md5ext);\n return Promise.resolve(costume);\n }\n\n if (!runtime.storage.defaultAssetId) {\n log.error(`No default assets found`);\n return Promise.resolve(costume);\n }\n\n const AssetType = runtime.storage.AssetType;\n const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap;\n\n const costumePromise = runtime.storage.load(assetType, md5, ext);\n if (!costumePromise) {\n log.error(`Couldn't fetch costume asset: ${md5ext}`);\n return;\n }\n\n let textLayerPromise;\n if (costume.textLayerMD5) {\n textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png');\n } else {\n textLayerPromise = Promise.resolve(null);\n }\n\n return Promise.all([costumePromise, textLayerPromise]).then(assetArray => {\n costume.asset = assetArray[0];\n if (assetArray[1]) {\n costume.textLayerAsset = assetArray[1];\n }\n return loadCostumeFromAsset(costume, runtime, optVersion);\n });\n};\n\nmodule.exports = {\n loadCostume,\n loadCostumeFromAsset\n};\n","const StringUtil = require('../util/string-util');\nconst log = require('../util/log');\n\n/**\n * Initialize a sound from an asset asynchronously.\n * @param {!object} sound - the Scratch sound object.\n * @property {string} md5 - the MD5 and extension of the sound to be loaded.\n * @property {Buffer} data - sound data will be written here once loaded.\n * @param {!Asset} soundAsset - the asset loaded from storage.\n * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.\n * @param {SoundBank} soundBank - Scratch Audio SoundBank to add sounds to.\n * @returns {!Promise} - a promise which will resolve to the sound when ready.\n */\nconst loadSoundFromAsset = function (sound, soundAsset, runtime, soundBank) {\n sound.assetId = soundAsset.assetId;\n if (!runtime.audioEngine) {\n log.error('No audio engine present; cannot load sound asset: ', sound.md5);\n return Promise.resolve(sound);\n }\n return runtime.audioEngine.decodeSoundPlayer(Object.assign(\n {},\n sound,\n {data: soundAsset.data}\n )).then(soundPlayer => {\n sound.soundId = soundPlayer.id;\n // Set the sound sample rate and sample count based on the\n // the audio buffer from the audio engine since the sound\n // gets resampled by the audio engine\n const soundBuffer = soundPlayer.buffer;\n sound.rate = soundBuffer.sampleRate;\n sound.sampleCount = soundBuffer.length;\n\n if (soundBank !== null) {\n soundBank.addSoundPlayer(soundPlayer);\n }\n\n return sound;\n });\n};\n\n/**\n * Load a sound's asset into memory asynchronously.\n * @param {!object} sound - the Scratch sound object.\n * @property {string} md5 - the MD5 and extension of the sound to be loaded.\n * @property {Buffer} data - sound data will be written here once loaded.\n * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.\n * @param {SoundBank} soundBank - Scratch Audio SoundBank to add sounds to.\n * @returns {!Promise} - a promise which will resolve to the sound when ready.\n */\nconst loadSound = function (sound, runtime, soundBank) {\n if (!runtime.storage) {\n log.error('No storage module present; cannot load sound asset: ', sound.md5);\n return Promise.resolve(sound);\n }\n const idParts = StringUtil.splitFirst(sound.md5, '.');\n const md5 = idParts[0];\n const ext = idParts[1].toLowerCase();\n sound.dataFormat = ext;\n return (\n (sound.asset && Promise.resolve(sound.asset)) ||\n runtime.storage.load(runtime.storage.AssetType.Sound, md5, ext)\n ).then(soundAsset => {\n sound.asset = soundAsset;\n return loadSoundFromAsset(sound, soundAsset, runtime, soundBank);\n });\n};\n\nmodule.exports = {\n loadSound,\n loadSoundFromAsset\n};\n","const VirtualMachine = require('./virtual-machine');\n\nmodule.exports = VirtualMachine;\n","const JSONRPC = require('../util/jsonrpc');\n\nclass BLE extends JSONRPC {\n\n /**\n * A BLE peripheral socket object. It handles connecting, over web sockets, to\n * BLE peripherals, and reading and writing data to them.\n * @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.\n * @param {string} extensionId - the id of the extension using this socket.\n * @param {object} peripheralOptions - the list of options for peripheral discovery.\n * @param {object} connectCallback - a callback for connection.\n * @param {object} resetCallback - a callback for resetting extension state.\n */\n constructor (runtime, extensionId, peripheralOptions, connectCallback, resetCallback = null) {\n super();\n\n this._socket = runtime.getScratchLinkSocket('BLE');\n this._socket.setOnOpen(this.requestPeripheral.bind(this));\n this._socket.setOnClose(this.handleDisconnectError.bind(this));\n this._socket.setOnError(this._handleRequestError.bind(this));\n this._socket.setHandleMessage(this._handleMessage.bind(this));\n\n this._sendMessage = this._socket.sendMessage.bind(this._socket);\n\n this._availablePeripherals = {};\n this._connectCallback = connectCallback;\n this._connected = false;\n this._characteristicDidChangeCallback = null;\n this._resetCallback = resetCallback;\n this._discoverTimeoutID = null;\n this._extensionId = extensionId;\n this._peripheralOptions = peripheralOptions;\n this._runtime = runtime;\n\n this._socket.open();\n }\n\n /**\n * Request connection to the peripheral.\n * If the web socket is not yet open, request when the socket promise resolves.\n */\n requestPeripheral () {\n this._availablePeripherals = {};\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);\n this.sendRemoteRequest('discover', this._peripheralOptions)\n .catch(e => {\n this._handleRequestError(e);\n });\n }\n\n /**\n * Try connecting to the input peripheral id, and then call the connect\n * callback if connection is successful.\n * @param {number} id - the id of the peripheral to connect to\n */\n connectPeripheral (id) {\n this.sendRemoteRequest('connect', {peripheralId: id})\n .then(() => {\n this._connected = true;\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);\n this._connectCallback();\n })\n .catch(e => {\n this._handleRequestError(e);\n });\n }\n\n /**\n * Close the websocket.\n */\n disconnect () {\n if (this._connected) {\n this._connected = false;\n }\n\n if (this._socket.isOpen()) {\n this._socket.close();\n }\n\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n\n // Sets connection status icon to orange\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);\n }\n\n /**\n * @return {bool} whether the peripheral is connected.\n */\n isConnected () {\n return this._connected;\n }\n\n /**\n * Start receiving notifications from the specified ble service.\n * @param {number} serviceId - the ble service to read.\n * @param {number} characteristicId - the ble characteristic to get notifications from.\n * @param {object} onCharacteristicChanged - callback for characteristic change notifications.\n * @return {Promise} - a promise from the remote startNotifications request.\n */\n startNotifications (serviceId, characteristicId, onCharacteristicChanged = null) {\n const params = {\n serviceId,\n characteristicId\n };\n this._characteristicDidChangeCallback = onCharacteristicChanged;\n return this.sendRemoteRequest('startNotifications', params)\n .catch(e => {\n this.handleDisconnectError(e);\n });\n }\n\n /**\n * Read from the specified ble service.\n * @param {number} serviceId - the ble service to read.\n * @param {number} characteristicId - the ble characteristic to read.\n * @param {boolean} optStartNotifications - whether to start receiving characteristic change notifications.\n * @param {object} onCharacteristicChanged - callback for characteristic change notifications.\n * @return {Promise} - a promise from the remote read request.\n */\n read (serviceId, characteristicId, optStartNotifications = false, onCharacteristicChanged = null) {\n const params = {\n serviceId,\n characteristicId\n };\n if (optStartNotifications) {\n params.startNotifications = true;\n }\n if (onCharacteristicChanged) {\n this._characteristicDidChangeCallback = onCharacteristicChanged;\n }\n return this.sendRemoteRequest('read', params)\n .catch(e => {\n this.handleDisconnectError(e);\n });\n }\n\n /**\n * Write data to the specified ble service.\n * @param {number} serviceId - the ble service to write.\n * @param {number} characteristicId - the ble characteristic to write.\n * @param {string} message - the message to send.\n * @param {string} encoding - the message encoding type.\n * @param {boolean} withResponse - if true, resolve after peripheral's response.\n * @return {Promise} - a promise from the remote send request.\n */\n write (serviceId, characteristicId, message, encoding = null, withResponse = null) {\n const params = {serviceId, characteristicId, message};\n if (encoding) {\n params.encoding = encoding;\n }\n if (withResponse !== null) {\n params.withResponse = withResponse;\n }\n return this.sendRemoteRequest('write', params)\n .catch(e => {\n this.handleDisconnectError(e);\n });\n }\n\n /**\n * Handle a received call from the socket.\n * @param {string} method - a received method label.\n * @param {object} params - a received list of parameters.\n * @return {object} - optional return value.\n */\n didReceiveCall (method, params) {\n switch (method) {\n case 'didDiscoverPeripheral':\n this._availablePeripherals[params.peripheralId] = params;\n this._runtime.emit(\n this._runtime.constructor.PERIPHERAL_LIST_UPDATE,\n this._availablePeripherals\n );\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n break;\n case 'userDidPickPeripheral':\n this._availablePeripherals[params.peripheralId] = params;\n this._runtime.emit(\n this._runtime.constructor.USER_PICKED_PERIPHERAL,\n this._availablePeripherals\n );\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n break;\n case 'userDidNotPickPeripheral':\n this._runtime.emit(\n this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT\n );\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n break;\n case 'characteristicDidChange':\n if (this._characteristicDidChangeCallback) {\n this._characteristicDidChangeCallback(params.message);\n }\n break;\n case 'ping':\n return 42;\n }\n }\n\n /**\n * Handle an error resulting from losing connection to a peripheral.\n *\n * This could be due to:\n * - battery depletion\n * - going out of bluetooth range\n * - being powered down\n *\n * Disconnect the socket, and if the extension using this socket has a\n * reset callback, call it. Finally, emit an error to the runtime.\n */\n handleDisconnectError (/* e */) {\n // log.error(`BLE error: ${JSON.stringify(e)}`);\n\n if (!this._connected) return;\n\n this.disconnect();\n\n if (this._resetCallback) {\n this._resetCallback();\n }\n\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {\n message: `Scratch lost connection to`,\n extensionId: this._extensionId\n });\n }\n\n _handleRequestError (/* e */) {\n // log.error(`BLE error: ${JSON.stringify(e)}`);\n\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {\n message: `Scratch lost connection to`,\n extensionId: this._extensionId\n });\n }\n\n _handleDiscoverTimeout () {\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT);\n }\n}\n\nmodule.exports = BLE;\n","const JSONRPC = require('../util/jsonrpc');\n\nclass BT extends JSONRPC {\n\n /**\n * A BT peripheral socket object. It handles connecting, over web sockets, to\n * BT peripherals, and reading and writing data to them.\n * @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.\n * @param {string} extensionId - the id of the extension using this socket.\n * @param {object} peripheralOptions - the list of options for peripheral discovery.\n * @param {object} connectCallback - a callback for connection.\n * @param {object} resetCallback - a callback for resetting extension state.\n * @param {object} messageCallback - a callback for message sending.\n */\n constructor (runtime, extensionId, peripheralOptions, connectCallback, resetCallback = null, messageCallback) {\n super();\n\n this._socket = runtime.getScratchLinkSocket('BT');\n this._socket.setOnOpen(this.requestPeripheral.bind(this));\n this._socket.setOnError(this._handleRequestError.bind(this));\n this._socket.setOnClose(this.handleDisconnectError.bind(this));\n this._socket.setHandleMessage(this._handleMessage.bind(this));\n\n this._sendMessage = this._socket.sendMessage.bind(this._socket);\n\n this._availablePeripherals = {};\n this._connectCallback = connectCallback;\n this._connected = false;\n this._characteristicDidChangeCallback = null;\n this._resetCallback = resetCallback;\n this._discoverTimeoutID = null;\n this._extensionId = extensionId;\n this._peripheralOptions = peripheralOptions;\n this._messageCallback = messageCallback;\n this._runtime = runtime;\n\n this._socket.open();\n }\n\n /**\n * Request connection to the peripheral.\n * If the web socket is not yet open, request when the socket promise resolves.\n */\n requestPeripheral () {\n this._availablePeripherals = {};\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);\n this.sendRemoteRequest('discover', this._peripheralOptions)\n .catch(\n e => this._handleRequestError(e)\n );\n }\n\n /**\n * Try connecting to the input peripheral id, and then call the connect\n * callback if connection is successful.\n * @param {number} id - the id of the peripheral to connect to\n * @param {string} pin - an optional pin for pairing\n */\n connectPeripheral (id, pin = null) {\n const params = {peripheralId: id};\n if (pin) {\n params.pin = pin;\n }\n this.sendRemoteRequest('connect', params)\n .then(() => {\n this._connected = true;\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);\n this._connectCallback();\n })\n .catch(e => {\n this._handleRequestError(e);\n });\n }\n\n /**\n * Close the websocket.\n */\n disconnect () {\n if (this._connected) {\n this._connected = false;\n }\n\n if (this._socket.isOpen()) {\n this._socket.close();\n }\n\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n\n // Sets connection status icon to orange\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);\n }\n\n /**\n * @return {bool} whether the peripheral is connected.\n */\n isConnected () {\n return this._connected;\n }\n\n sendMessage (options) {\n return this.sendRemoteRequest('send', options)\n .catch(e => {\n this.handleDisconnectError(e);\n });\n }\n\n /**\n * Handle a received call from the socket.\n * @param {string} method - a received method label.\n * @param {object} params - a received list of parameters.\n * @return {object} - optional return value.\n */\n didReceiveCall (method, params) {\n // TODO: Add peripheral 'undiscover' handling\n switch (method) {\n case 'didDiscoverPeripheral':\n this._availablePeripherals[params.peripheralId] = params;\n this._runtime.emit(\n this._runtime.constructor.PERIPHERAL_LIST_UPDATE,\n this._availablePeripherals\n );\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n break;\n case 'userDidPickPeripheral':\n this._availablePeripherals[params.peripheralId] = params;\n this._runtime.emit(\n this._runtime.constructor.USER_PICKED_PERIPHERAL,\n this._availablePeripherals\n );\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n break;\n case 'userDidNotPickPeripheral':\n this._runtime.emit(\n this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT\n );\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n break;\n case 'didReceiveMessage':\n this._messageCallback(params); // TODO: refine?\n break;\n default:\n return 'nah';\n }\n }\n\n /**\n * Handle an error resulting from losing connection to a peripheral.\n *\n * This could be due to:\n * - battery depletion\n * - going out of bluetooth range\n * - being powered down\n *\n * Disconnect the socket, and if the extension using this socket has a\n * reset callback, call it. Finally, emit an error to the runtime.\n */\n handleDisconnectError (/* e */) {\n // log.error(`BT error: ${JSON.stringify(e)}`);\n\n if (!this._connected) return;\n\n this.disconnect();\n\n if (this._resetCallback) {\n this._resetCallback();\n }\n\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {\n message: `Scratch lost connection to`,\n extensionId: this._extensionId\n });\n }\n\n _handleRequestError (/* e */) {\n // log.error(`BT error: ${JSON.stringify(e)}`);\n\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {\n message: `Scratch lost connection to`,\n extensionId: this._extensionId\n });\n }\n\n _handleDiscoverTimeout () {\n if (this._discoverTimeoutID) {\n window.clearTimeout(this._discoverTimeoutID);\n }\n this._runtime.emit(this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT);\n }\n}\n\nmodule.exports = BT;\n","const Timer = require('../util/timer');\n\nclass Clock {\n constructor (runtime) {\n this._projectTimer = new Timer({now: () => runtime.currentMSecs});\n this._projectTimer.start();\n this._pausedTime = null;\n this._paused = false;\n /**\n * Reference to the owning Runtime.\n * @type{!Runtime}\n */\n this.runtime = runtime;\n }\n\n projectTimer () {\n if (this._paused) {\n return this._pausedTime / 1000;\n }\n return this._projectTimer.timeElapsed() / 1000;\n }\n\n pause () {\n this._paused = true;\n this._pausedTime = this._projectTimer.timeElapsed();\n }\n\n resume () {\n this._paused = false;\n const dt = this._projectTimer.timeElapsed() - this._pausedTime;\n this._projectTimer.startTime += dt;\n }\n\n resetProjectTimer () {\n this._projectTimer.start();\n }\n}\n\nmodule.exports = Clock;\n","const Variable = require('../engine/variable');\nconst log = require('../util/log');\n\nclass Cloud {\n /**\n * @typedef updateVariable\n * @param {string} name The name of the cloud variable to update on the server\n * @param {(string | number)} value The value to update the cloud variable with.\n */\n\n /**\n * A cloud data provider, responsible for managing the connection to the\n * cloud data server and for posting data about cloud data activity to\n * this IO device.\n * @typedef {object} CloudProvider\n * @property {updateVariable} updateVariable A function which sends a cloud variable\n * update to the cloud data server.\n * @property {Function} requestCloseConnection A function which closes\n * the connection to the cloud data server.\n */\n\n /**\n * Part of a cloud io data post indicating a cloud variable update.\n * @typedef {object} VarUpdateData\n * @property {string} name The name of the variable to update\n * @property {(number | string)} value The scalar value to update the variable with\n */\n\n /**\n * A cloud io data post message.\n * @typedef {object} CloudIOData\n * @property {VarUpdateData} varUpdate A {@link VarUpdateData} message indicating\n * a cloud variable update\n */\n\n /**\n * Cloud IO Device responsible for sending and receiving messages from\n * cloud provider (mananging the cloud server connection) and interacting\n * with cloud variables in the current project.\n * @param {Runtime} runtime The runtime context for this cloud io device.\n */\n constructor (runtime) {\n /**\n * Reference to the cloud data provider, responsible for mananging\n * the web socket connection to the cloud data server.\n * @type {?CloudProvider}\n */\n this.provider = null;\n\n /**\n * Reference to the runtime that owns this cloud io device.\n * @type {!Runtime}\n */\n this.runtime = runtime;\n\n /**\n * Reference to the stage target which owns the cloud variables\n * in the project.\n * @type {?Target}\n */\n this.stage = null;\n }\n\n /**\n * Set a reference to the cloud data provider.\n * @param {CloudProvider} provider The cloud data provider\n */\n setProvider (provider) {\n this.provider = provider;\n }\n\n /**\n * Set a reference to the stage target which owns the\n * cloud variables in the project.\n * @param {Target} stage The stage target\n */\n setStage (stage) {\n this.stage = stage;\n }\n\n /**\n * Handle incoming data to this io device.\n * @param {CloudIOData} data The {@link CloudIOData} object to process\n */\n postData (data) {\n if (data.varUpdate) {\n this.updateCloudVariable(data.varUpdate);\n }\n }\n\n requestCreateVariable (variable) {\n if (this.runtime.canAddCloudVariable()) {\n if (this.provider) {\n this.provider.createVariable(variable.name, variable.value);\n // We'll set the cloud flag and update the\n // cloud variable limit when we actually\n // get a confirmation from the cloud data server\n }\n } // TODO else track creation for later\n }\n\n /**\n * Request the cloud data provider to update the given variable with\n * the given value. Does nothing if this io device does not have a provider set.\n * @param {string} name The name of the variable to update\n * @param {string | number} value The value to update the variable with\n */\n requestUpdateVariable (name, value) {\n if (this.provider) {\n this.provider.updateVariable(name, value);\n }\n }\n\n /**\n * Request the cloud data provider to rename the variable with the given name\n * to the given new name. Does nothing if this io device does not have a provider set.\n * @param {string} oldName The name of the variable to rename\n * @param {string | number} newName The new name for the variable\n */\n requestRenameVariable (oldName, newName) {\n if (this.provider) {\n this.provider.renameVariable(oldName, newName);\n }\n }\n\n /**\n * Request the cloud data provider to delete the variable with the given name\n * Does nothing if this io device does not have a provider set.\n * @param {string} name The name of the variable to delete\n */\n requestDeleteVariable (name) {\n if (this.provider) {\n this.provider.deleteVariable(name);\n }\n }\n\n /**\n * Update a cloud variable in the runtime based on the message received\n * from the cloud provider.\n * @param {VarData} varUpdate A {@link VarData} object describing\n * a cloud variable update received from the cloud data provider.\n */\n updateCloudVariable (varUpdate) {\n const varName = varUpdate.name;\n\n const variable = this.stage.lookupVariableByNameAndType(varName, Variable.SCALAR_TYPE);\n if (!variable || !variable.isCloud) {\n log.warn(`Received an update for a cloud variable that does not exist: ${varName}`);\n return;\n }\n\n variable.value = varUpdate.value;\n }\n\n /**\n * Request the cloud data provider to close the web socket connection and\n * clear this io device of references to the cloud data provider and the\n * stage.\n */\n clear () {\n if (!this.provider) return;\n\n this.provider.requestCloseConnection();\n this.provider = null;\n this.stage = null;\n }\n}\n\nmodule.exports = Cloud;\n","const Cast = require('../util/cast');\n\n/**\n * Names used internally for keys used in scratch, also known as \"scratch keys\".\n * @enum {string}\n */\nconst KEY_NAME = {\n SPACE: 'space',\n LEFT: 'left arrow',\n UP: 'up arrow',\n RIGHT: 'right arrow',\n DOWN: 'down arrow',\n ENTER: 'enter'\n};\n\n/**\n * An array of the names of scratch keys.\n * @type {Array}\n */\nconst KEY_NAME_LIST = Object.keys(KEY_NAME).map(name => KEY_NAME[name]);\n\nclass Keyboard {\n constructor (runtime) {\n /**\n * List of currently pressed scratch keys.\n * A scratch key is:\n * A key you can press on a keyboard, excluding modifier keys.\n * An uppercase string of length one;\n * except for special key names for arrow keys and space (e.g. 'left arrow').\n * Can be a non-english unicode letter like: æ ø ש נ 手 廿.\n * @type{Array.}\n */\n this._keysPressed = [];\n /**\n * Reference to the owning Runtime.\n * Can be used, for example, to activate hats.\n * @type{!Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * Convert from a keyboard event key name to a Scratch key name.\n * @param {string} keyString the input key string.\n * @return {string} the corresponding Scratch key, or an empty string.\n */\n _keyStringToScratchKey (keyString) {\n keyString = Cast.toString(keyString);\n // Convert space and arrow keys to their Scratch key names.\n switch (keyString) {\n case ' ': return KEY_NAME.SPACE;\n case 'ArrowLeft':\n case 'Left': return KEY_NAME.LEFT;\n case 'ArrowUp':\n case 'Up': return KEY_NAME.UP;\n case 'Right':\n case 'ArrowRight': return KEY_NAME.RIGHT;\n case 'Down':\n case 'ArrowDown': return KEY_NAME.DOWN;\n case 'Enter': return KEY_NAME.ENTER;\n }\n // Ignore modifier keys\n if (keyString.length > 1) {\n return '';\n }\n return keyString.toUpperCase();\n }\n\n /**\n * Convert from a block argument to a Scratch key name.\n * @param {string} keyArg the input arg.\n * @return {string} the corresponding Scratch key.\n */\n _keyArgToScratchKey (keyArg) {\n // If a number was dropped in, try to convert from ASCII to Scratch key.\n if (typeof keyArg === 'number') {\n // Check for the ASCII range containing numbers, some punctuation,\n // and uppercase letters.\n if (keyArg >= 48 && keyArg <= 90) {\n return String.fromCharCode(keyArg);\n }\n switch (keyArg) {\n case 32: return KEY_NAME.SPACE;\n case 37: return KEY_NAME.LEFT;\n case 38: return KEY_NAME.UP;\n case 39: return KEY_NAME.RIGHT;\n case 40: return KEY_NAME.DOWN;\n }\n }\n\n keyArg = Cast.toString(keyArg);\n\n // If the arg matches a special key name, return it.\n if (KEY_NAME_LIST.includes(keyArg)) {\n return keyArg;\n }\n\n // Use only the first character.\n if (keyArg.length > 1) {\n keyArg = keyArg[0];\n }\n\n // Check for the space character.\n if (keyArg === ' ') {\n return KEY_NAME.SPACE;\n }\n\n return keyArg.toUpperCase();\n }\n\n /**\n * Keyboard DOM event handler.\n * @param {object} data Data from DOM event.\n */\n postData (data) {\n if (!data.key) return;\n const scratchKey = this._keyStringToScratchKey(data.key);\n if (scratchKey === '') return;\n const index = this._keysPressed.indexOf(scratchKey);\n if (data.isDown) {\n this.runtime.emit('KEY_PRESSED', scratchKey);\n // If not already present, add to the list.\n if (index < 0) {\n this._keysPressed.push(scratchKey);\n }\n } else if (index > -1) {\n // If already present, remove from the list.\n this._keysPressed.splice(index, 1);\n }\n }\n\n /**\n * Get key down state for a specified key.\n * @param {Any} keyArg key argument.\n * @return {boolean} Is the specified key down?\n */\n getKeyIsDown (keyArg) {\n if (keyArg === 'any') {\n return this._keysPressed.length > 0;\n }\n const scratchKey = this._keyArgToScratchKey(keyArg);\n return this._keysPressed.indexOf(scratchKey) > -1;\n }\n}\n\nmodule.exports = Keyboard;\n","const MathUtil = require('../util/math-util');\n\nclass Mouse {\n constructor (runtime) {\n this._x = 0;\n this._y = 0;\n this._isDown = false;\n /**\n * Reference to the owning Runtime.\n * Can be used, for example, to activate hats.\n * @type{!Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * Activate \"event_whenthisspriteclicked\" hats.\n * @param {Target} target to trigger hats on.\n * @private\n */\n _activateClickHats (target) {\n // Activate both \"this sprite clicked\" and \"stage clicked\"\n // They were separated into two opcodes for labeling,\n // but should act the same way.\n // Intentionally not checking isStage to make it work when sharing blocks.\n // @todo the blocks should be converted from one to another when shared\n this.runtime.startHats('event_whenthisspriteclicked',\n null, target);\n this.runtime.startHats('event_whenstageclicked',\n null, target);\n }\n\n /**\n * Find a target by XY location\n * @param {number} x X position to be sent to the renderer.\n * @param {number} y Y position to be sent to the renderer.\n * @return {Target} the target at that location\n * @private\n */\n _pickTarget (x, y) {\n if (this.runtime.renderer) {\n const drawableID = this.runtime.renderer.pick(x, y);\n for (let i = 0; i < this.runtime.targets.length; i++) {\n const target = this.runtime.targets[i];\n if (target.hasOwnProperty('drawableID') &&\n target.drawableID === drawableID) {\n return target;\n }\n }\n }\n // Return the stage if no target was found\n return this.runtime.getTargetForStage();\n }\n\n /**\n * Mouse DOM event handler.\n * @param {object} data Data from DOM event.\n */\n postData (data) {\n if (data.x) {\n this._clientX = data.x;\n this._scratchX = Math.round(MathUtil.clamp(\n 480 * ((data.x / data.canvasWidth) - 0.5),\n -240,\n 240\n ));\n }\n if (data.y) {\n this._clientY = data.y;\n this._scratchY = Math.round(MathUtil.clamp(\n -360 * ((data.y / data.canvasHeight) - 0.5),\n -180,\n 180\n ));\n }\n if (typeof data.isDown !== 'undefined') {\n const previousDownState = this._isDown;\n this._isDown = data.isDown;\n\n // Do not trigger if down state has not changed\n if (previousDownState === this._isDown) return;\n\n // Never trigger click hats at the end of a drag\n if (data.wasDragged) return;\n\n // Do not activate click hats for clicks outside canvas bounds\n if (!(data.x > 0 && data.x < data.canvasWidth &&\n data.y > 0 && data.y < data.canvasHeight)) return;\n\n const target = this._pickTarget(data.x, data.y);\n const isNewMouseDown = !previousDownState && this._isDown;\n const isNewMouseUp = previousDownState && !this._isDown;\n\n // Draggable targets start click hats on mouse up.\n // Non-draggable targets start click hats on mouse down.\n if (target.draggable && isNewMouseUp) {\n this._activateClickHats(target);\n } else if (!target.draggable && isNewMouseDown) {\n this._activateClickHats(target);\n }\n }\n }\n\n /**\n * Get the X position of the mouse in client coordinates.\n * @return {number} Non-clamped X position of the mouse cursor.\n */\n getClientX () {\n return this._clientX;\n }\n\n /**\n * Get the Y position of the mouse in client coordinates.\n * @return {number} Non-clamped Y position of the mouse cursor.\n */\n getClientY () {\n return this._clientY;\n }\n\n /**\n * Get the X position of the mouse in scratch coordinates.\n * @return {number} Clamped and integer rounded X position of the mouse cursor.\n */\n getScratchX () {\n return this._scratchX;\n }\n\n /**\n * Get the Y position of the mouse in scratch coordinates.\n * @return {number} Clamped and integer rounded Y position of the mouse cursor.\n */\n getScratchY () {\n return this._scratchY;\n }\n\n /**\n * Get the down state of the mouse.\n * @return {boolean} Is the mouse down?\n */\n getIsDown () {\n return this._isDown;\n }\n}\n\nmodule.exports = Mouse;\n","class MouseWheel {\n constructor (runtime) {\n /**\n * Reference to the owning Runtime.\n * @type{!Runtime}\n */\n this.runtime = runtime;\n }\n\n /**\n * Mouse wheel DOM event handler.\n * @param {object} data Data from DOM event.\n */\n postData (data) {\n const matchFields = {};\n if (data.deltaY < 0) {\n matchFields.KEY_OPTION = 'up arrow';\n } else if (data.deltaY > 0) {\n matchFields.KEY_OPTION = 'down arrow';\n } else {\n return;\n }\n\n this.runtime.startHats('event_whenkeypressed', matchFields);\n }\n}\n\nmodule.exports = MouseWheel;\n","class UserData {\n constructor () {\n this._username = '';\n }\n\n /**\n * Handler for updating the username\n * @param {object} data Data posted to this ioDevice.\n * @property {!string} username The new username.\n */\n postData (data) {\n this._username = data.username;\n }\n\n /**\n * Getter for username. Initially empty string, until set via postData.\n * @returns {!string} The current username\n */\n getUsername () {\n return this._username;\n }\n}\n\nmodule.exports = UserData;\n","const StageLayering = require('../engine/stage-layering');\n\nclass Video {\n constructor (runtime) {\n this.runtime = runtime;\n\n /**\n * @typedef VideoProvider\n * @property {Function} enableVideo - Requests camera access from the user, and upon success,\n * enables the video feed\n * @property {Function} disableVideo - Turns off the video feed\n * @property {Function} getFrame - Return frame data from the video feed in\n * specified dimensions, format, and mirroring.\n */\n this.provider = null;\n\n /**\n * Id representing a Scratch Renderer skin the video is rendered to for\n * previewing.\n * @type {number}\n */\n this._skinId = -1;\n\n /**\n * Id for a drawable using the video's skin that will render as a video\n * preview.\n * @type {Drawable}\n */\n this._drawable = -1;\n\n /**\n * Store the last state of the video transparency ghost effect\n * @type {number}\n */\n this._ghost = 0;\n\n /**\n * Store a flag that allows the preview to be forced transparent.\n * @type {number}\n */\n this._forceTransparentPreview = false;\n }\n\n static get FORMAT_IMAGE_DATA () {\n return 'image-data';\n }\n\n static get FORMAT_CANVAS () {\n return 'canvas';\n }\n\n /**\n * Dimensions the video stream is analyzed at after its rendered to the\n * sample canvas.\n * @type {Array.}\n */\n static get DIMENSIONS () {\n return [480, 360];\n }\n\n /**\n * Order preview drawable is inserted at in the renderer.\n * @type {number}\n */\n static get ORDER () {\n return 1;\n }\n\n /**\n * Set a video provider for this device. A default implementation of\n * a video provider can be found in scratch-gui/src/lib/video/video-provider\n * @param {VideoProvider} provider - Video provider to use\n */\n setProvider (provider) {\n this.provider = provider;\n }\n\n /**\n * Request video be enabled. Sets up video, creates video skin and enables preview.\n *\n * ioDevices.video.requestVideo()\n *\n * @return {Promise.