[
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.cache\n# Firebase deployment config files\n.firebaserc\nfirebase.json\n# Yarn build output folder\ndist/\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nfirebase-debug.log*\n\n# Firebase cache\n.firebase/\n\n# Firebase config\n\n# Uncomment this if you'd like others to create their own Firebase project.\n# For a team working on the same Firebase project(s), it is recommended to leave\n# it commented so all members can deploy to the same project(s) in .firebaserc.\n# .firebaserc\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to Contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guidelines you need to follow.\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement (CLA). You (or your employer) retain the copyright to your\ncontribution; this simply gives us permission to use and redistribute your\ncontributions as part of the project. Head over to\n<https://cla.developers.google.com/> to see your current agreements on file or\nto sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Code reviews\n\nAll submissions, including submissions by project members, require review. We\nuse GitHub pull requests for this purpose. Consult\n[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more\ninformation on using pull requests.\n\n## Community Guidelines\n\nThis project follows\n[Google's Open Source Community Guidelines](https://opensource.google/conduct/)."
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "# Pose Animator\n\nPose Animator takes a 2D vector illustration and animates its containing curves in real-time based on the recognition result from PoseNet and FaceMesh. It borrows the idea of skeleton-based animation from computer graphics and applies it to vector characters.\n\nThis is running in the browser in realtime using [TensorFlow.js](https://www.tensorflow.org/js). Check out more cool TF.js demos [here](https://www.tensorflow.org/js/demos).\n\n*This is not an officially supported Google product.*\n\n<img src=\"/resources/gifs/avatar-new-1.gif?raw=true\" alt=\"cameraDemo\" style=\"width: 250px;\"/>\n\n<img src=\"/resources/gifs/avatar-new-full-body.gif?raw=true\" alt=\"cameraDemo\" style=\"width: 250px;\"/>\n\nIn skeletal animation a character is represented in two parts:\n1. a surface used to draw the character, and \n1. a hierarchical set of interconnected bones used to animate the surface. \n\nIn Pose Animator, the surface is defined by the 2D vector paths in the input SVG files. For the bone structure, Pose Animator provides a predefined rig (bone hierarchy) representation, designed based on the keypoints from PoseNet and FaceMesh. This bone structure’s initial pose is specified in the input SVG file, along with the character illustration, while the real time bone positions are updated by the recognition result from ML models.\n\n<img src=\"https://firebasestorage.googleapis.com/v0/b/pose-animator-demo.appspot.com/o/ml-keypoints.png?alt=media\" style=\"width:250px;\"/>\n\n<img src=\"/resources/gifs/avatar-new-bezier-1.gif?raw=true\" alt=\"cameraDemo\" style=\"width: 250px;\"/>\n\n// TODO: Add blog post link.\nFor more details on its technical design please check out this blog post.\n\n### Demo 1: [Camera feed](https://pose-animator-demo.firebaseapp.com/camera.html)\n\nThe camera demo animates a 2D avatar in real-time from a webcam video stream.\n\n\n### Demo 2: [Static image](https://pose-animator-demo.firebaseapp.com/static_image.html)\n\nThe static image demo shows the avatar positioned from a single image.\n\n## Build And Run\n\nInstall dependencies and prepare the build directory:\n\n```sh\nyarn\n```\n\nTo watch files for changes, and launch a dev server:\n\n```sh\nyarn watch\n```\n\n## Platform support\n\nDemos are supported on Desktop Chrome and iOS Safari.\n\nIt should also run on Chrome on Android and potentially more Android mobile browsers though support has not been tested yet.\n\n# Animate your own design\n\n1. Download the [sample skeleton SVG here](/resources/samples/skeleton.svg).\n1. Create a new file in your vector graphics editor of choice. Copy the group named ‘skeleton’ from the above file into your working file. Note: \n\t* Do not add, remove or rename the joints (circles) in this group. Pose Animator relies on these named paths to read the skeleton’s initial position. Missing joints will cause errors.\n\t* However you can move the joints around to embed them into your illustration. See step 4.\n1. Create a new group and name it ‘illustration’, next to the ‘skeleton’ group. This is the group where you can put all the paths for your illustration.\n    * Flatten all subgroups so that ‘illustration’ only contains path elements.\n    * Composite paths are not supported at the moment.\n    * The working file structure should look like this:\n\t```\n        [Layer 1]\n        |---- skeleton\n        |---- illustration\n              |---- path 1\n              |---- path 2\n              |---- path 3\n\t```\n1. Embed the sample skeleton in ‘skeleton’ group into your illustration by moving the joints around.\n1. Export the file as an SVG file.\n1. Open [Pose Animator camera demo](https://pose-animator-demo.firebaseapp.com/camera.html). Once everything loads, drop your SVG file into the browser tab. You should be able to see it come to life :D\n"
  },
  {
    "path": "camera.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n    <title>PoseNet - Camera Feed Demo</title>\n    <style>\n        body {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n        }\n\n        .canvas-container {\n            width: 800px;\n            max-width: 100%;\n            display: flex;\n            justify-content: center;\n            position: relative;\n        }\n\n        .camera-canvas {\n            position: absolute;\n            transform: scale(0.5, 0.5);\n            transform-origin: 0 0;\n            left: 10px;\n            top: 10px;\n        }\n\n        #main {\n            left: 0;\n            top: 0;\n            position: absolute;\n        }\n\n        .illustration-canvas {\n            border: 1px solid #eeeeee;\n        }\n\n        .footer {\n            position: fixed;\n            left: 0;\n            bottom: 0;\n            width: 100%;\n            color: black;\n        }\n\n        .footer-text {\n            max-width: 600px;\n            text-align: center;\n            margin: auto;\n        }\n\n        @media only screen and (max-width: 600px) {\n\n            .footer-text,\n            .dg {\n                display: none;\n            }\n        }\n\n        /*\n         *  The following loading spinner CSS is from SpinKit project\n         *  https://github.com/tobiasahlin/SpinKit\n         */\n        .sk-spinner-pulse {\n            width: 20px;\n            height: 20px;\n            margin: auto 10px;\n            float: left;\n            background-color: #333;\n            border-radius: 100%;\n            -webkit-animation: sk-pulseScaleOut 1s infinite ease-in-out;\n            animation: sk-pulseScaleOut 1s infinite ease-in-out;\n        }\n\n        @-webkit-keyframes sk-pulseScaleOut {\n            0% {\n                -webkit-transform: scale(0);\n                transform: scale(0);\n            }\n\n            100% {\n                -webkit-transform: scale(1.0);\n                transform: scale(1.0);\n                opacity: 0;\n            }\n        }\n\n        @keyframes sk-pulseScaleOut {\n            0% {\n                -webkit-transform: scale(0);\n                transform: scale(0);\n            }\n\n            100% {\n                -webkit-transform: scale(1.0);\n                transform: scale(1.0);\n                opacity: 0;\n            }\n        }\n\n        .spinner-text {\n            float: left;\n        }\n    </style>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n</head>\n\n<body>\n    <div id=\"info\" style='display:none'>\n    </div>\n    <div id=\"loading\" style='position: relative; left: 0'>\n        <span class=\"spinner-text\" id=\"status\">\n            Loading PoseNet model...\n        </span>\n        <div class=\"sk-spinner sk-spinner-pulse\"></div>\n    </div>\n    <div class=\"canvas-container\">\n        <div id='main' style='display:none'>\n            <video id=\"video\" playsinline style=\" -moz-transform: scaleX(-1);\n            -o-transform: scaleX(-1);\n            -webkit-transform: scaleX(-1);\n            transform: scaleX(-1);\n            display: none;\n            \">\n            </video>\n            <canvas id=\"output\" class=\"camera-canvas\"></canvas>\n            <canvas id=\"keypoints\" class=\"camera-canvas\"></canvas>\n        </div>\n        <canvas class=\"illustration-canvas\"></cavnas>\n    </div>\n    <div class=\"footer\">\n        <div class=\"footer-text\">\n          <p>\n            Pose Animator runs TF.js <strong>FaceMesh</strong> and <strong>PoseNet</strong> models to animate SVG illustrations with camera feed / static images.<br>\n            It currently supports <strong>single-pose</strong>, <strong>single-face</strong> detection, and has been tested on Destkop Chrome & iOS Safari.\n            <br>\n            (PoseNet model config - MobileNetV1, output stride 16, quant bytes 2)\n          </p>\n        </div>\n      </div>\n    <script src=\"camera.js\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "camera.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nimport * as posenet_module from '@tensorflow-models/posenet';\nimport * as facemesh_module from '@tensorflow-models/facemesh';\nimport * as tf from '@tensorflow/tfjs';\nimport * as paper from 'paper';\nimport dat from 'dat.gui';\nimport Stats from 'stats.js';\nimport \"babel-polyfill\";\n\nimport {drawKeypoints, drawPoint, drawSkeleton, isMobile, toggleLoadingUI, setStatusText} from './utils/demoUtils';\nimport {SVGUtils} from './utils/svgUtils'\nimport {PoseIllustration} from './illustrationGen/illustration';\nimport {Skeleton, facePartName2Index} from './illustrationGen/skeleton';\nimport {FileUtils} from './utils/fileUtils';\n\nimport * as girlSVG from './resources/illustration/girl.svg';\nimport * as boySVG from './resources/illustration/boy.svg';\nimport * as abstractSVG from './resources/illustration/abstract.svg';\nimport * as blathersSVG from './resources/illustration/blathers.svg';\nimport * as tomNookSVG from './resources/illustration/tom-nook.svg';\n\n// Camera stream video element\nlet video;\nlet videoWidth = 300;\nlet videoHeight = 300;\n\n// Canvas\nlet faceDetection = null;\nlet illustration = null;\nlet canvasScope;\nlet canvasWidth = 800;\nlet canvasHeight = 800;\n\n// ML models\nlet facemesh;\nlet posenet;\nlet minPoseConfidence = 0.15;\nlet minPartConfidence = 0.1;\nlet nmsRadius = 30.0;\n\n// Misc\nlet mobile = false;\nconst stats = new Stats();\nconst avatarSvgs = {\n  'girl': girlSVG.default,\n  'boy': boySVG.default,\n  'abstract': abstractSVG.default,\n  'blathers': blathersSVG.default,\n  'tom-nook': tomNookSVG.default,\n};\n\n/**\n * Loads a the camera to be used in the demo\n *\n */\nasync function setupCamera() {\n  if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {\n    throw new Error(\n        'Browser API navigator.mediaDevices.getUserMedia not available');\n  }\n\n  const video = document.getElementById('video');\n  video.width = videoWidth;\n  video.height = videoHeight;\n\n  const stream = await navigator.mediaDevices.getUserMedia({\n    'audio': false,\n    'video': {\n      facingMode: 'user',\n      width: videoWidth,\n      height: videoHeight,\n    },\n  });\n  video.srcObject = stream;\n\n  return new Promise((resolve) => {\n    video.onloadedmetadata = () => {\n      resolve(video);\n    };\n  });\n}\n\nasync function loadVideo() {\n  const video = await setupCamera();\n  video.play();\n\n  return video;\n}\n\nconst defaultPoseNetArchitecture = 'MobileNetV1';\nconst defaultQuantBytes = 2;\nconst defaultMultiplier = 1.0;\nconst defaultStride = 16;\nconst defaultInputResolution = 200;\n\nconst guiState = {\n  avatarSVG: Object.keys(avatarSvgs)[0],\n  debug: {\n    showDetectionDebug: true,\n    showIllustrationDebug: false,\n  },\n};\n\n/**\n * Sets up dat.gui controller on the top-right of the window\n */\nfunction setupGui(cameras) {\n\n  if (cameras.length > 0) {\n    guiState.camera = cameras[0].deviceId;\n  }\n\n  const gui = new dat.GUI({width: 300});\n\n  let multi = gui.addFolder('Image');\n  gui.add(guiState, 'avatarSVG', Object.keys(avatarSvgs)).onChange(() => parseSVG(avatarSvgs[guiState.avatarSVG]));\n  multi.open();\n\n  let output = gui.addFolder('Debug control');\n  output.add(guiState.debug, 'showDetectionDebug');\n  output.add(guiState.debug, 'showIllustrationDebug');\n  output.open();\n}\n\n/**\n * Sets up a frames per second panel on the top-left of the window\n */\nfunction setupFPS() {\n  stats.showPanel(0);  // 0: fps, 1: ms, 2: mb, 3+: custom\n  document.getElementById('main').appendChild(stats.dom);\n}\n\n/**\n * Feeds an image to posenet to estimate poses - this is where the magic\n * happens. This function loops with a requestAnimationFrame method.\n */\nfunction detectPoseInRealTime(video) {\n  const canvas = document.getElementById('output');\n  const keypointCanvas = document.getElementById('keypoints');\n  const videoCtx = canvas.getContext('2d');\n  const keypointCtx = keypointCanvas.getContext('2d');\n\n  canvas.width = videoWidth;\n  canvas.height = videoHeight;\n  keypointCanvas.width = videoWidth;\n  keypointCanvas.height = videoHeight;\n\n  async function poseDetectionFrame() {\n    // Begin monitoring code for frames per second\n    stats.begin();\n\n    let poses = [];\n   \n    videoCtx.clearRect(0, 0, videoWidth, videoHeight);\n    // Draw video\n    videoCtx.save();\n    videoCtx.scale(-1, 1);\n    videoCtx.translate(-videoWidth, 0);\n    videoCtx.drawImage(video, 0, 0, videoWidth, videoHeight);\n    videoCtx.restore();\n\n    // Creates a tensor from an image\n    const input = tf.browser.fromPixels(canvas);\n    faceDetection = await facemesh.estimateFaces(input, false, false);\n    let all_poses = await posenet.estimatePoses(video, {\n      flipHorizontal: true,\n      decodingMethod: 'multi-person',\n      maxDetections: 1,\n      scoreThreshold: minPartConfidence,\n      nmsRadius: nmsRadius\n    });\n\n    poses = poses.concat(all_poses);\n    input.dispose();\n\n    keypointCtx.clearRect(0, 0, videoWidth, videoHeight);\n    if (guiState.debug.showDetectionDebug) {\n      poses.forEach(({score, keypoints}) => {\n      if (score >= minPoseConfidence) {\n          drawKeypoints(keypoints, minPartConfidence, keypointCtx);\n          drawSkeleton(keypoints, minPartConfidence, keypointCtx);\n        }\n      });\n      faceDetection.forEach(face => {\n        Object.values(facePartName2Index).forEach(index => {\n            let p = face.scaledMesh[index];\n            drawPoint(keypointCtx, p[1], p[0], 2, 'red');\n        });\n      });\n    }\n\n    canvasScope.project.clear();\n\n    if (poses.length >= 1 && illustration) {\n      Skeleton.flipPose(poses[0]);\n\n      if (faceDetection && faceDetection.length > 0) {\n        let face = Skeleton.toFaceFrame(faceDetection[0]);\n        illustration.updateSkeleton(poses[0], face);\n      } else {\n        illustration.updateSkeleton(poses[0], null);\n      }\n      illustration.draw(canvasScope, videoWidth, videoHeight);\n\n      if (guiState.debug.showIllustrationDebug) {\n        illustration.debugDraw(canvasScope);\n      }\n    }\n\n    canvasScope.project.activeLayer.scale(\n      canvasWidth / videoWidth, \n      canvasHeight / videoHeight, \n      new canvasScope.Point(0, 0));\n\n    // End monitoring code for frames per second\n    stats.end();\n\n    requestAnimationFrame(poseDetectionFrame);\n  }\n\n  poseDetectionFrame();\n}\n\nfunction setupCanvas() {\n  mobile = isMobile();\n  if (mobile) {\n    canvasWidth = Math.min(window.innerWidth, window.innerHeight);\n    canvasHeight = canvasWidth;\n    videoWidth *= 0.7;\n    videoHeight *= 0.7;\n  }  \n\n  canvasScope = paper.default;\n  let canvas = document.querySelector('.illustration-canvas');;\n  canvas.width = canvasWidth;\n  canvas.height = canvasHeight;\n  canvasScope.setup(canvas);\n}\n\n/**\n * Kicks off the demo by loading the posenet model, finding and loading\n * available camera devices, and setting off the detectPoseInRealTime function.\n */\nexport async function bindPage() {\n  setupCanvas();\n\n  toggleLoadingUI(true);\n  setStatusText('Loading PoseNet model...');\n  posenet = await posenet_module.load({\n    architecture: defaultPoseNetArchitecture,\n    outputStride: defaultStride,\n    inputResolution: defaultInputResolution,\n    multiplier: defaultMultiplier,\n    quantBytes: defaultQuantBytes\n  });\n  setStatusText('Loading FaceMesh model...');\n  facemesh = await facemesh_module.load();\n\n  setStatusText('Loading Avatar file...');\n  let t0 = new Date();\n  await parseSVG(Object.values(avatarSvgs)[0]);\n\n  setStatusText('Setting up camera...');\n  try {\n    video = await loadVideo();\n  } catch (e) {\n    let info = document.getElementById('info');\n    info.textContent = 'this device type is not supported yet, ' +\n      'or this browser does not support video capture: ' + e.toString();\n    info.style.display = 'block';\n    throw e;\n  }\n\n  setupGui([], posenet);\n  setupFPS();\n  \n  toggleLoadingUI(false);\n  detectPoseInRealTime(video, posenet);\n}\n\nnavigator.getUserMedia = navigator.getUserMedia ||\n    navigator.webkitGetUserMedia || navigator.mozGetUserMedia;\nFileUtils.setDragDropHandler((result) => {parseSVG(result)});\n\nasync function parseSVG(target) {\n  let svgScope = await SVGUtils.importSVG(target /* SVG string or file path */);\n  let skeleton = new Skeleton(svgScope);\n  illustration = new PoseIllustration(canvasScope);\n  illustration.bindSkeleton(skeleton, svgScope);\n}\n    \nbindPage();\n"
  },
  {
    "path": "illustrationGen/illustration.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nimport { Bone, allPartNames, Skeleton } from './skeleton';\nimport { MathUtils } from '../utils/mathUtils';\nimport { SVGUtils } from '../utils/svgUtils';\nimport { ColorUtils } from '../utils/colorUtils';\n\nconst allPartNamesMap = {};\nallPartNames.forEach(name => allPartNamesMap[name] = 1);\n\nconst MIN_CONFIDENCE_PATH_SCORE = 0.3;\n\n// Represents a skinned illustration.\nexport class PoseIllustration {\n    constructor(scope) {\n        this.scope = scope;\n        this.frames = [];\n    }\n\n    updateSkeleton(pose, face) {\n        this.pose = pose;\n        this.face = face;\n        this.skeleton.update(pose, face);\n        if (!this.skeleton.isValid) {\n            return;\n        }\n\n        let getConfidenceScore = (p) => {\n            return Object.keys(p.skinning).reduce((totalScore, boneName) => {\n                let bt = p.skinning[boneName];\n                return totalScore + bt.bone.score * bt.weight;\n            }, 0);\n        }\n\n        this.skinnedPaths.forEach(skinnedPath => {\n            let confidenceScore = 0;\n            skinnedPath.segments.forEach(seg => {\n                // Compute confidence score.\n                confidenceScore += getConfidenceScore(seg.point);\n                // Compute new positions for curve point and handles.\n                seg.point.currentPosition = Skeleton.getCurrentPosition(seg.point);\n                if (seg.handleIn) {\n                    seg.handleIn.currentPosition = Skeleton.getCurrentPosition(seg.handleIn);\n                }\n                if (seg.handleOut) {\n                    seg.handleOut.currentPosition = Skeleton.getCurrentPosition(seg.handleOut);\n                }\n            });\n            skinnedPath.confidenceScore = confidenceScore / (skinnedPath.segments.length || 1);\n        });\n    }\n\n    draw() {\n        if (!this.skeleton.isValid) {\n            return;\n        }\n        let scope = this.scope;\n        // Add paths\n        this.skinnedPaths.forEach(skinnedPath => {\n            // Do not render paths with low confidence scores.\n            if (!skinnedPath.confidenceScore || skinnedPath.confidenceScore < MIN_CONFIDENCE_PATH_SCORE) {\n                return;\n            }\n            let path = new scope.Path({\n                fillColor: skinnedPath.fillColor,\n                strokeColor: skinnedPath.strokeColor,\n                strokeWidth: skinnedPath.strokeWidth,\n                closed: skinnedPath.closed,\n            });\n            skinnedPath.segments.forEach(seg => {\n                path.addSegment(seg.point.currentPosition, \n                    seg.handleIn ? seg.handleIn.currentPosition.subtract(seg.point.currentPosition) : null,\n                    seg.handleOut ? seg.handleOut.currentPosition.subtract(seg.point.currentPosition) : null);\n            });\n            if (skinnedPath.closed) {\n                path.closePath();\n            }\n            scope.project.activeLayer.addChild(path);\n        });\n    }\n\n    debugDraw() {\n        let scope = this.scope;\n        let group = new scope.Group();\n        scope.project.activeLayer.addChild(group);\n        let drawCircle = (p, opt = {}) => {\n            group.addChild(new scope.Path.Circle({\n                center: [p.x, p.y],\n                radius: opt.radius || 2,\n                fillColor: opt.fillColor || 'red',\n            }));\n        }\n        let drawLine = (p0, p1, opt = {}) => {\n            group.addChild(new scope.Path({\n                segments: [p0, p1],\n                strokeColor: opt.strokeColor || 'red',\n                strokeWidth: opt.strokeWidth || 1\n            }));\n        }\n        // Draw skeleton.\n        this.skeleton.debugDraw(scope);\n        // Draw curve and handles.\n        this.skinnedPaths.forEach(skinnedPath => {\n            skinnedPath.segments.forEach(seg => {\n                // Color represents weight influence from bones.\n                let color = new scope.Color(0);\n                Object.keys(seg.point.skinning).forEach((boneName) => {\n                    let bt = seg.point.skinning[boneName];\n                    ColorUtils.addRGB(color, \n                        bt.weight * bt.bone.boneColor.red, \n                        bt.weight * bt.bone.boneColor.green, \n                        bt.weight * bt.bone.boneColor.blue);\n                        let anchor = bt.bone.kp0.currentPosition.multiply(1 - bt.transform.anchorPerc).add(bt.bone.kp1.currentPosition.multiply(bt.transform.anchorPerc));\n                        drawLine(anchor, seg.point.currentPosition, {strokeColor: 'blue', strokeWidth: bt.weight});\n                });\n\n                drawCircle(seg.point.currentPosition, {fillColor: color});\n                drawCircle(seg.handleIn.currentPosition, {fillColor: color});\n                drawLine(seg.point.currentPosition, seg.handleIn.currentPosition, {strokeColor: color});\n                drawCircle(seg.handleOut.currentPosition, {fillColor: color}, {strokeColor: color});\n                drawLine(seg.point.currentPosition, seg.handleOut.currentPosition);\n            });\n        });\n    }\n\n    debugDrawLabel(scope) {\n        this.skeleton.debugDrawLabels(scope);\n    }\n\n    bindSkeleton(skeleton, skeletonScope) {\n        let items = skeletonScope.project.getItems({ recursive: true });\n        items = items.filter(item => item.parent && item.parent.name && item.parent.name.startsWith('illustration'));\n        this.skeleton = skeleton;\n        this.skinnedPaths = [];\n\n        // Only support rendering path and shapes for now.\n        for (let i = 0; i < items.length; i++) {\n            let item = items[i];\n            if (SVGUtils.isGroup(item)) {\n                this.bindGroup(item, skeleton);\n            } else if (SVGUtils.isPath(item)) {\n                this.bindPathToBones(item);\n            } else if (SVGUtils.isShape(item)) {\n                this.bindPathToBones(item.toPath());\n            }\n        }\n    }\n\n    bindGroup(group, skeleton) {\n        let paths = [];\n        let keypoints = {};\n        let items = group.getItems({recursive: true});\n        // Find all paths and included keypoints.\n        items.forEach(item => {\n            let partName = item.name ? allPartNames.find(partName => item.name.startsWith(partName)) : null;\n            if (partName) {\n                keypoints[partName] = {\n                    position: item.bounds.center,\n                    name: partName,\n                };\n            } else if (SVGUtils.isPath(item)) {\n                paths.push(item);\n            } else if (SVGUtils.isShape(item)) {\n                paths.push(item.toPath());\n            }\n        });\n        let secondaryBones = [];\n        // Find all parent bones of the included keypoints.\n        let parentBones = skeleton.bones.filter(bone => keypoints[bone.kp0.name] && keypoints[bone.kp1.name]);\n        let nosePos = skeleton.bNose3Nose4.kp1.position;\n        if (!parentBones.length) {\n            return;\n        }\n\n        // Crete secondary bones for the included keypoints.\n        parentBones.forEach(parentBone => {\n            let kp0 = keypoints[parentBone.kp0.name];\n            let kp1 = keypoints[parentBone.kp1.name];\n            let secondaryBone = new Bone().set(kp0, kp1, parentBone.skeleton, parentBone.type);\n            kp0.transformFunc = MathUtils.getTransformFunc(parentBone.kp0.position, nosePos, kp0.position);\n            kp1.transformFunc = MathUtils.getTransformFunc(parentBone.kp1.position, nosePos, kp1.position);\n            secondaryBone.parent = parentBone;\n            secondaryBones.push(secondaryBone);\n        });        \n        skeleton.secondaryBones = skeleton.secondaryBones.concat(secondaryBones);\n        paths.forEach(path => {\n            this.bindPathToBones(path, secondaryBones);\n        });\n    }\n\n    // Assign weights from bones for point.\n    // Weight calculation is roughly based on linear blend skinning model.\n    getWeights(point, bones) {\n        let totalW = 0;\n        let weights = {};\n        bones.forEach(bone => {\n            let d = MathUtils.getClosestPointOnSegment(bone.kp0.position, bone.kp1.position, point)\n                .getDistance(point);\n            // Absolute weight = 1 / (distance * distance)\n            let w = 1 / (d * d);\n            weights[bone.name] = {\n                value: w,\n                bone: bone,\n            }\n        });\n\n        let values = Object.values(weights).sort((v0, v1) => {\n            return v1.value - v0.value;\n        });\n        weights = {};\n        totalW = 0;\n        values.forEach(v => {\n            weights[v.bone.name] = v;\n            totalW += v.value;\n        });\n        if (totalW === 0) {\n            // Point is outside of the influence zone of all bones. It will not be influence by any bone.\n            return {};\n        }\n\n        // Normalize weights to sum up to 1.\n        Object.values(weights).forEach(weight => {\n            weight.value /= totalW;\n        });\n\n        return weights;\n    }\n\n    // Binds a path to bones by compute weight contribution from each bones for each path segment.\n    // If selectedBones are set, bind directly to the selected bones. Otherwise auto select the bone group closest to each segment.\n    bindPathToBones(path, selectedBones) {\n        // Compute bone weights for each segment.\n        let segs = path.segments.map(s => {\n            // Check if control points are collinear.\n            // If so, use the middle point's weight for all three points (curve point, handleIn, handleOut).\n            // This makes sure smooth curves remain smooth after deformation.\n            let collinear = MathUtils.isCollinear(s.handleIn, s.handleOut);\n            let bones = selectedBones || this.skeleton.findBoneGroup(s.point);\n            let weightsP = this.getWeights(s.point, bones);\n            let segment = {\n                point: this.getSkinning(s.point, weightsP),\n            };\n            // For handles, compute transformation in world space.\n            if (s.handleIn) {\n                let pHandleIn = s.handleIn.add(s.point);\n                segment.handleIn = this.getSkinning(pHandleIn, collinear ? weightsP : this.getWeights(pHandleIn, bones));\n            }\n            if (s.handleOut) {\n                let pHandleOut = s.handleOut.add(s.point);\n                segment.handleOut = this.getSkinning(pHandleOut, collinear ? weightsP : this.getWeights(pHandleOut, bones));\n            }\n            return segment;\n        });\n        this.skinnedPaths.push({\n            segments: segs,\n            fillColor: path.fillColor,\n            strokeColor: path.strokeColor,\n            strokeWidth: path.strokeWidth,\n            closed: path.closed\n        });\n    }\n\n    getSkinning(point, weights) {\n        let skinning = {};\n        Object.keys(weights).forEach(boneName => {\n            skinning[boneName] = {\n                bone: weights[boneName].bone,\n                weight: weights[boneName].value,\n                transform: weights[boneName].bone.getPointTransform(point),\n            };\n        });\n        return {\n            skinning: skinning,\n            position: point,\n            currentPosition: new this.scope.Point(0, 0),\n        }\n    };\n}\n"
  },
  {
    "path": "illustrationGen/skeleton.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nimport * as paper from 'paper';\nimport { SVGUtils } from '../utils/svgUtils';\nimport { MathUtils } from '../utils/mathUtils';\nimport { ColorUtils } from '../utils/colorUtils';\n\nconst MIN_POSE_SCORE = 0.1;\nconst MIN_FACE_SCORE = 0.8;\n\nconst posePartNames = ['leftAnkle', 'leftKnee', 'leftHip', 'leftWrist', 'leftElbow', 'leftShoulder', \n    'rightAnkle', 'rightKnee', 'rightHip', 'rightWrist', 'rightElbow', 'rightShoulder',\n    'leftEar', 'rightEar'];\n\n// Mapping between face part names and their vertex indices in TF face mesh.\nexport const facePartName2Index = {\n    'topMid': 10,\n    'rightTop0': 67,\n    'rightTop1': 54,\n    'leftTop0': 297,\n    'leftTop1': 284,\n    'rightJaw0': 21,\n    'rightJaw1': 162,\n    'rightJaw2': 127, \n    'rightJaw3': 234,\n    'rightJaw4': 132, \n    'rightJaw5': 172, \n    'rightJaw6': 150,\n    'rightJaw7': 176,\n    'jawMid': 152,   // 0 - 8\n    'leftJaw7': 400, \n    'leftJaw6': 379, \n    'leftJaw5': 397, \n    'leftJaw4': 361,\n    'leftJaw3': 454,\n    'leftJaw2': 356,\n    'leftJaw1': 389,\n    'leftJaw0': 251, // 9 - 16\n    'rightBrow0': 46, \n    'rightBrow1': 53, \n    'rightBrow2': 52,\n    'rightBrow3': 65,\n    'rightBrow4': 55, // 17 - 21\n    'leftBrow4': 285,\n    'leftBrow3': 295, \n    'leftBrow2': 282,\n    'leftBrow1': 283,\n    'leftBrow0': 276, // 22 - 26\n    'nose0': 6,\n    'nose1': 197,\n    'nose2': 195,\n    'nose3': 5, // 27 - 30\n    'rightNose0': 48,\n    'rightNose1': 220,\n    'nose4': 4, \n    'leftNose1': 440,\n    'leftNose0': 278, // 31 - 35\n    'rightEye0': 33,\n    'rightEye1': 160,\n    'rightEye2': 158,\n    'rightEye3': 133,\n    'rightEye4': 153,\n    'rightEye5': 144, // 36 - 41\n    'leftEye3': 362,\n    'leftEye2': 385,\n    'leftEye1': 387,\n    'leftEye0': 263,\n    'leftEye5': 373, \n    'leftEye4': 380, // 42 - 47\n    'rightMouthCorner': 61,\n    'rightUpperLipTop0': 40,\n    'rightUpperLipTop1': 37,\n    'upperLipTopMid': 0,\n    'leftUpperLipTop1': 267,\n    'leftUpperLipTop0': 270,\n    'leftMouthCorner': 291, // 48 - 54\n    'leftLowerLipBottom0': 321,\n    'leftLowerLipBottom1': 314,\n    'lowerLipBottomMid': 17,\n    'rightLowerLipBottom1': 84,\n    'rightLowerLipBottom0': 91, // 55 - 59\n    'rightMiddleLip': 78,\n    'rightUpperLipBottom1': 81,\n    'upperLipBottomMid': 13,\n    'leftUpperLipBottom1': 311,\n    'leftMiddleLip': 308, // 60 - 64\n    'leftLowerLipTop0': 402, \n    'lowerLipTopMid': 14,\n    'rightLowerLipTop0': 178, // 65 - 67\n};\n\nconst facePartNames = [\n    'topMid', 'rightTop0', 'rightTop1', 'leftTop0', 'leftTop1',\n    'rightJaw0', 'rightJaw1', 'rightJaw2', 'rightJaw3', 'rightJaw4', 'rightJaw5', 'rightJaw6', 'rightJaw7', 'jawMid',   // 0 - 8\n    'leftJaw7', 'leftJaw6', 'leftJaw5', 'leftJaw4', 'leftJaw3', 'leftJaw2', 'leftJaw1', 'leftJaw0', // 9 - 16\n    'rightBrow0', 'rightBrow1', 'rightBrow2', 'rightBrow3', 'rightBrow4', // 17 - 21\n    'leftBrow4', 'leftBrow3', 'leftBrow2', 'leftBrow1', 'leftBrow0', // 22 - 26\n    'nose0', 'nose1', 'nose2', 'nose3', // 27 - 30\n    'rightNose0', 'rightNose1', 'nose4', 'leftNose1', 'leftNose0', // 31 - 35\n    'rightEye0', 'rightEye1', 'rightEye2', 'rightEye3', 'rightEye4', 'rightEye5', // 36 - 41\n    'leftEye3', 'leftEye2', 'leftEye1', 'leftEye0', 'leftEye5', 'leftEye4', // 42 - 47\n    'rightMouthCorner', 'rightUpperLipTop0', 'rightUpperLipTop1', 'upperLipTopMid', 'leftUpperLipTop1', 'leftUpperLipTop0', 'leftMouthCorner', // 48 - 54\n    'leftLowerLipBottom0', 'leftLowerLipBottom1', 'lowerLipBottomMid', 'rightLowerLipBottom1', 'rightLowerLipBottom0', // 55 - 59\n    'rightMiddleLip', 'rightUpperLipBottom1', 'upperLipBottomMid', 'leftUpperLipBottom1', 'leftMiddleLip', // 60 - 64\n    'leftLowerLipTop0', 'lowerLipTopMid', 'rightLowerLipTop0', // 65 - 67\n];\n\nexport const allPartNames = posePartNames.concat(facePartNames);\n\n// Represents a bone formed by two part keypoints.\nexport class Bone {\n    set(kp0, kp1, skeleton, type) {\n        this.name = `${kp0.name}-${kp1.name}`;\n        this.kp0 = kp0;\n        this.kp1 = kp1;\n        this.skeleton = skeleton;\n        this.type = type;\n        this.boneColor = ColorUtils.fromStringHash(this.name);\n        this.boneColor.saturation += 0.5;\n        return this;\n    };\n\n    // Finds a point's bone transform.\n    // Let anchor be the closest point on the bone to the point.\n    // A point's bone transformation is the transformation from anchor to the point.\n    getPointTransform(p) {\n        let dir = this.kp1.position.subtract(this.kp0.position).normalize();\n        let n = dir.clone();\n        n.angle += 90;\n        let closestP = MathUtils.getClosestPointOnSegment(this.kp0.position, this.kp1.position, p);\n        let v = p.subtract(closestP);\n        let dirProjD = v.dot(dir);\n        let dirProjN = v.dot(n);\n        let d = this.kp0.position.subtract(this.kp1.position).length;\n        let anchorPerc = closestP.subtract(this.kp0.position).length / d;\n        return {\n            transform: new paper.default.Point(dirProjD, dirProjN),\n            anchorPerc: anchorPerc,\n        };\n    }\n\n    // Finds a point's current position from the current bone position.\n    transform(trans) {\n        if (!this.kp1.currentPosition || !this.kp0.currentPosition) {\n            return;\n        }\n        // Scale distance from anchor point base on bone type.\n        // All face bones will share one distance scale. All body bones share another.\n        let scale = this.type === 'face' ? this.skeleton.currentFaceScale : this.skeleton.currentBodyScale;\n        let dir = this.kp1.currentPosition.subtract(this.kp0.currentPosition).normalize();\n        let n = dir.clone();\n        n.angle += 90;\n        let anchor = this.kp0.currentPosition.multiply(1 - trans.anchorPerc).add(this.kp1.currentPosition.multiply(trans.anchorPerc));\n        let p = anchor.add(dir.multiply(trans.transform.x * scale)).add(n.multiply(trans.transform.y * scale));\n        return p;\n    }\n}\n\nfunction getKeyPointFromSVG(group, partName) {\n    let shape = SVGUtils.findFirstItemWithPrefix(group, partName);\n    return {\n        position: shape.bounds.center,\n        name: partName,\n    };\n}\n\nfunction getPartFromPose(pose, name) {\n    if (!pose || !pose.keypoints) {\n        return null;\n    }\n    let part = pose.keypoints.find(kp => kp.part === name);\n    return {\n        position: new paper.default.Point(part.position.x, part.position.y),\n        score: part.score,\n    }\n}\n\nfunction getKeypointFromFaceFrame(face, i) {\n    if (!face || !face.scaledMesh || !face.scaledMesh.length);\n    return new paper.default.Point(face.positions[i * 2], face.positions[i * 2 + 1]);\n}\n\n// Represents a full body skeleton.\nexport class Skeleton {\n    constructor(scope) {\n        let skeletonGroup = SVGUtils.findFirstItemWithPrefix(scope.project, 'skeleton');\n        // Pose\n        let leftAnkle = getKeyPointFromSVG(skeletonGroup, 'leftAnkle');\n        let leftKnee = getKeyPointFromSVG(skeletonGroup, 'leftKnee');\n        let leftHip = getKeyPointFromSVG(skeletonGroup, 'leftHip');\n        let leftWrist = getKeyPointFromSVG(skeletonGroup, 'leftWrist');\n        let leftElbow = getKeyPointFromSVG(skeletonGroup, 'leftElbow');\n        let leftShoulder = getKeyPointFromSVG(skeletonGroup, 'leftShoulder');\n        let rightAnkle = getKeyPointFromSVG(skeletonGroup, 'rightAnkle');\n        let rightKnee = getKeyPointFromSVG(skeletonGroup, 'rightKnee');\n        let rightHip = getKeyPointFromSVG(skeletonGroup, 'rightHip');\n        let rightWrist = getKeyPointFromSVG(skeletonGroup, 'rightWrist');\n        let rightElbow = getKeyPointFromSVG(skeletonGroup, 'rightElbow');\n        let rightShoulder = getKeyPointFromSVG(skeletonGroup, 'rightShoulder');\n\n        // Face\n        let topMid = getKeyPointFromSVG(skeletonGroup, 'topMid');\n        let rightTop0 = getKeyPointFromSVG(skeletonGroup, 'rightTop0');\n        let rightTop1 = getKeyPointFromSVG(skeletonGroup, 'rightTop1');\n        let leftTop0 = getKeyPointFromSVG(skeletonGroup, 'leftTop0');\n        let leftTop1 = getKeyPointFromSVG(skeletonGroup, 'leftTop1');\n        let leftJaw2 = getKeyPointFromSVG(skeletonGroup, 'leftJaw2');\n        let leftJaw3 = getKeyPointFromSVG(skeletonGroup, 'leftJaw3');\n        let leftJaw4 = getKeyPointFromSVG(skeletonGroup, 'leftJaw4');\n        let leftJaw5 = getKeyPointFromSVG(skeletonGroup, 'leftJaw5');\n        let leftJaw6 = getKeyPointFromSVG(skeletonGroup, 'leftJaw6');\n        let leftJaw7 = getKeyPointFromSVG(skeletonGroup, 'leftJaw7');\n        let jawMid = getKeyPointFromSVG(skeletonGroup, 'jawMid');\n        let rightJaw2 = getKeyPointFromSVG(skeletonGroup, 'rightJaw2');\n        let rightJaw3 = getKeyPointFromSVG(skeletonGroup, 'rightJaw3');\n        let rightJaw4 = getKeyPointFromSVG(skeletonGroup, 'rightJaw4');\n        let rightJaw5 = getKeyPointFromSVG(skeletonGroup, 'rightJaw5');\n        let rightJaw6 = getKeyPointFromSVG(skeletonGroup, 'rightJaw6');\n        let rightJaw7 = getKeyPointFromSVG(skeletonGroup, 'rightJaw7');\n        let nose0 = getKeyPointFromSVG(skeletonGroup, 'nose0');\n        let nose1 = getKeyPointFromSVG(skeletonGroup, 'nose1');\n        let nose2 = getKeyPointFromSVG(skeletonGroup, 'nose2');\n        let nose3 = getKeyPointFromSVG(skeletonGroup, 'nose3');\n        let nose4 = getKeyPointFromSVG(skeletonGroup, 'nose4');\n        let leftNose0 = getKeyPointFromSVG(skeletonGroup, 'leftNose0');\n        let leftNose1 = getKeyPointFromSVG(skeletonGroup, 'leftNose1');\n        let rightNose0 = getKeyPointFromSVG(skeletonGroup, 'rightNose0');\n        let rightNose1 = getKeyPointFromSVG(skeletonGroup, 'rightNose1');\n        let leftEye0 = getKeyPointFromSVG(skeletonGroup, 'leftEye0');\n        let leftEye1 = getKeyPointFromSVG(skeletonGroup, 'leftEye1');\n        let leftEye2 = getKeyPointFromSVG(skeletonGroup, 'leftEye2');\n        let leftEye3 = getKeyPointFromSVG(skeletonGroup, 'leftEye3');\n        let leftEye4 = getKeyPointFromSVG(skeletonGroup, 'leftEye4');\n        let leftEye5 = getKeyPointFromSVG(skeletonGroup, 'leftEye5');\n        let rightEye0 = getKeyPointFromSVG(skeletonGroup, 'rightEye0');\n        let rightEye1 = getKeyPointFromSVG(skeletonGroup, 'rightEye1');\n        let rightEye2 = getKeyPointFromSVG(skeletonGroup, 'rightEye2');\n        let rightEye3 = getKeyPointFromSVG(skeletonGroup, 'rightEye3');\n        let rightEye4 = getKeyPointFromSVG(skeletonGroup, 'rightEye4');\n        let rightEye5 = getKeyPointFromSVG(skeletonGroup, 'rightEye5');\n        let leftBrow0 = getKeyPointFromSVG(skeletonGroup, 'leftBrow0');\n        let leftBrow1 = getKeyPointFromSVG(skeletonGroup, 'leftBrow1');\n        let leftBrow2 = getKeyPointFromSVG(skeletonGroup, 'leftBrow2');\n        let leftBrow3 = getKeyPointFromSVG(skeletonGroup, 'leftBrow3');\n        let leftBrow4 = getKeyPointFromSVG(skeletonGroup, 'leftBrow4');\n        let rightBrow0 = getKeyPointFromSVG(skeletonGroup, 'rightBrow0');\n        let rightBrow1 = getKeyPointFromSVG(skeletonGroup, 'rightBrow1');\n        let rightBrow2 = getKeyPointFromSVG(skeletonGroup, 'rightBrow2');\n        let rightBrow3 = getKeyPointFromSVG(skeletonGroup, 'rightBrow3');\n        let rightBrow4 = getKeyPointFromSVG(skeletonGroup, 'rightBrow4');\n        let leftMouthCorner = getKeyPointFromSVG(skeletonGroup, 'leftMouthCorner');\n        let leftUpperLipTop0 = getKeyPointFromSVG(skeletonGroup, 'leftUpperLipTop0');\n        let leftUpperLipTop1 = getKeyPointFromSVG(skeletonGroup, 'leftUpperLipTop1');\n        let upperLipTopMid = getKeyPointFromSVG(skeletonGroup, 'upperLipTopMid');\n        let rightMouthCorner = getKeyPointFromSVG(skeletonGroup, 'rightMouthCorner');\n        let rightUpperLipTop0 = getKeyPointFromSVG(skeletonGroup, 'rightUpperLipTop0');\n        let rightUpperLipTop1 = getKeyPointFromSVG(skeletonGroup, 'rightUpperLipTop1');\n        let rightMiddleLip = getKeyPointFromSVG(skeletonGroup, 'rightMiddleLip');\n        let rightUpperLipBottom1 = getKeyPointFromSVG(skeletonGroup, 'rightUpperLipBottom1');\n        let leftMiddleLip = getKeyPointFromSVG(skeletonGroup, 'leftMiddleLip');\n        let leftUpperLipBottom1 = getKeyPointFromSVG(skeletonGroup, 'leftUpperLipBottom1');\n        let upperLipBottomMid = getKeyPointFromSVG(skeletonGroup, 'upperLipBottomMid');\n        let rightLowerLipTop0 = getKeyPointFromSVG(skeletonGroup, 'rightLowerLipTop0');\n        let leftLowerLipTop0 = getKeyPointFromSVG(skeletonGroup, 'leftLowerLipTop0');\n        let lowerLipTopMid = getKeyPointFromSVG(skeletonGroup, 'lowerLipTopMid');\n        let rightLowerLipBottom0 = getKeyPointFromSVG(skeletonGroup, 'rightLowerLipBottom0');\n        let rightLowerLipBottom1 = getKeyPointFromSVG(skeletonGroup, 'rightLowerLipBottom1');\n        let leftLowerLipBottom0 = getKeyPointFromSVG(skeletonGroup, 'leftLowerLipBottom0');\n        let leftLowerLipBottom1 = getKeyPointFromSVG(skeletonGroup, 'leftLowerLipBottom1');\n        let lowerLipBottomMid = getKeyPointFromSVG(skeletonGroup, 'lowerLipBottomMid');\n\n        this.bLeftShoulderRightShoulder = new Bone().set(leftShoulder, rightShoulder, this, 'body');\n        this.bRightShoulderRightHip = new Bone().set(rightShoulder, rightHip, this, 'body');\n        this.bLeftHipRightHip = new Bone().set(leftHip, rightHip, this, 'body');\n        this.bLeftShoulderLeftHip = new Bone().set(leftShoulder, leftHip, this, 'body');\n        this.bLeftShoulderLeftElbow = new Bone().set(leftShoulder, leftElbow, this, 'body');\n        this.bLeftElbowLeftWrist = new Bone().set(leftElbow, leftWrist, this, 'body');\n        this.bRightShoulderRightElbow = new Bone().set(rightShoulder, rightElbow, this, 'body');\n        this.bRightElbowRightWrist = new Bone().set(rightElbow, rightWrist, this, 'body');\n        this.bLeftHipLeftKnee = new Bone().set(leftHip, leftKnee, this, 'body');\n        this.bLeftKneeLeftAnkle = new Bone().set(leftKnee, leftAnkle, this, 'body');\n        this.bRightHipRightKnee = new Bone().set(rightHip, rightKnee, this, 'body');\n        this.bRightKneeRightAnkle = new Bone().set(rightKnee, rightAnkle, this, 'body');\n\n        this.bTopMidRightTop0 = new Bone().set(topMid, rightTop0, this, 'face');\n        this.bTopMidLeftTop0 = new Bone().set(topMid, leftTop0, this, 'face');\n        this.bLeftTop0LeftTop1 = new Bone().set(leftTop0, leftTop1, this, 'face');\n        this.bLeftTop1LeftJaw2 = new Bone().set(leftTop1, leftJaw2, this, 'face');\n        this.bLeftJaw2LeftJaw3 = new Bone().set(leftJaw2, leftJaw3, this, 'face');\n        this.bLeftJaw3LeftJaw4 = new Bone().set(leftJaw3, leftJaw4, this, 'face');\n        this.bLeftJaw4LeftJaw5 = new Bone().set(leftJaw4, leftJaw5, this, 'face');\n        this.bLeftJaw5LeftJaw6 = new Bone().set(leftJaw5, leftJaw6, this, 'face');\n        this.bLeftJaw6LeftJaw7 = new Bone().set(leftJaw6, leftJaw7, this, 'face');\n        this.bLeftJaw7JawMid = new Bone().set(leftJaw7, jawMid, this, 'face');\n        this.bRightTop0RightTop1 = new Bone().set(rightTop0, rightTop1, this, 'face');\n        this.bRightTop1RightJaw2 = new Bone().set(rightTop1, rightJaw2, this, 'face');\n        this.bRightJaw2RightJaw3 = new Bone().set(rightJaw2, rightJaw3, this, 'face');\n        this.bRightJaw3RightJaw4 = new Bone().set(rightJaw3, rightJaw4, this, 'face');\n        this.bRightJaw4RightJaw5 = new Bone().set(rightJaw4, rightJaw5, this, 'face');\n        this.bRightJaw5RightJaw6 = new Bone().set(rightJaw5, rightJaw6, this, 'face');\n        this.bRightJaw6RightJaw7 = new Bone().set(rightJaw6, rightJaw7, this, 'face');\n        this.bRightJaw7JawMid = new Bone().set(rightJaw7, jawMid, this, 'face');\n        this.bLeftEye0LeftEye1 = new Bone().set(leftEye0, leftEye1, this, 'face');\n        this.bLeftEye1LeftEye2 = new Bone().set(leftEye1, leftEye2, this, 'face');\n        this.bLeftEye2LeftEye3 = new Bone().set(leftEye2, leftEye3, this, 'face');\n        this.bLeftEye3LeftEye4 = new Bone().set(leftEye3, leftEye4, this, 'face');\n        this.bLeftEye4LeftEye5 = new Bone().set(leftEye4, leftEye5, this, 'face');\n        this.bLeftEye5LeftEye0 = new Bone().set(leftEye5, leftEye0, this, 'face');\n        this.bRightEye0RightEye1 = new Bone().set(rightEye0, rightEye1, this, 'face');\n        this.bRightEye1RightEye2 = new Bone().set(rightEye1, rightEye2, this, 'face');\n        this.bRightEye2RightEye3 = new Bone().set(rightEye2, rightEye3, this, 'face');\n        this.bRightEye3RightEye4 = new Bone().set(rightEye3, rightEye4, this, 'face');\n        this.bRightEye4RightEye5 = new Bone().set(rightEye4, rightEye5, this, 'face');\n        this.bRightEye5RightEye0 = new Bone().set(rightEye5, rightEye0, this, 'face');\n        this.bLeftBrow0LeftBrow1 = new Bone().set(leftBrow0, leftBrow1, this, 'face');\n        this.bLeftBrow1LeftBrow2 = new Bone().set(leftBrow1, leftBrow2, this, 'face');\n        this.bLeftBrow2LeftBrow3 = new Bone().set(leftBrow2, leftBrow3, this, 'face');\n        this.bLeftBrow3LeftBrow4 = new Bone().set(leftBrow3, leftBrow4, this, 'face');\n        this.bRightBrow0RightBrow1 = new Bone().set(rightBrow0, rightBrow1, this, 'face');\n        this.bRightBrow1RightBrow2 = new Bone().set(rightBrow1, rightBrow2, this, 'face');\n        this.bRightBrow2RightBrow3 = new Bone().set(rightBrow2, rightBrow3, this, 'face');\n        this.bRightBrow3RightBrow4 = new Bone().set(rightBrow3, rightBrow4, this, 'face');\n        this.bNose0Nose1 = new Bone().set(nose0, nose1, this, 'face');\n        this.bNose1Nose2 = new Bone().set(nose1, nose2, this, 'face');\n        this.bNose2Nose3 = new Bone().set(nose2, nose3, this, 'face');\n        this.bNose3Nose4 = new Bone().set(nose3, nose4, this, 'face');\n        this.bLeftNose0LeftNose1 = new Bone().set(leftNose0, leftNose1, this, 'face');\n        this.bLeftNose1Nose4 = new Bone().set(leftNose1, nose4, this, 'face');\n        this.bRightNose0RightNose1 = new Bone().set(rightNose0, rightNose1, this, 'face');\n        this.bRightNose1Nose4 = new Bone().set(rightNose1, nose4, this, 'face');\n        this.bLeftMouthCornerLeftUpperLipTop0 = new Bone().set(leftMouthCorner, leftUpperLipTop0, this, 'face');\n        this.bLeftUpperLipTop0LeftUpperLipTop1 = new Bone().set(leftUpperLipTop0, leftUpperLipTop1, this, 'face');\n        this.bLeftUpperLipTop1UpperLipTopMid = new Bone().set(leftUpperLipTop1, upperLipTopMid, this, 'face');\n        this.bRigthMouthCornerRigthUpperLipTop0 = new Bone().set(rightMouthCorner, rightUpperLipTop0, this, 'face');\n        this.bRigthUpperLipTop0RigthUpperLipTop1 = new Bone().set(rightUpperLipTop0, rightUpperLipTop1, this, 'face');\n        this.bRigthUpperLipTop1UpperLipTopMid = new Bone().set(rightUpperLipTop1, upperLipTopMid, this, 'face');\n        this.bLeftMouthCornerLeftMiddleLip = new Bone().set(leftMouthCorner, leftMiddleLip, this, 'face');\n        this.bLeftMiddleLipLeftUpperLipBottom1 = new Bone().set(leftMiddleLip, leftUpperLipBottom1, this, 'face');\n        this.bLeftUpperLipBottom1UpperLipBottomMid = new Bone().set(leftUpperLipBottom1, upperLipBottomMid, this, 'face');\n        this.bRightMouthCornerRightMiddleLip = new Bone().set(rightMouthCorner, rightMiddleLip, this, 'face');\n        this.bRightMiddleLipRightUpperLipBottom1 = new Bone().set(rightMiddleLip, rightUpperLipBottom1, this, 'face');\n        this.bRightUpperLipBottom1UpperLipBototmMid = new Bone().set(rightUpperLipBottom1, upperLipBottomMid, this, 'face');\n        this.bLeftMiddleLipLeftLowerLipTop0 = new Bone().set(leftMiddleLip, leftLowerLipTop0, this, 'face');\n        this.bLeftLowerLipTop0LowerLipTopMid = new Bone().set(leftLowerLipTop0, lowerLipTopMid, this, 'face');\n        this.bRightMiddleLipRightLowerLipTop0 = new Bone().set(rightMiddleLip, rightLowerLipTop0, this, 'face');\n        this.bRightLowerLipTop0LowerLipTopMid = new Bone().set(rightLowerLipTop0, lowerLipTopMid, this, 'face');\n        this.bLeftMouthCornerLeftLowerLipBottom0 = new Bone().set(leftMouthCorner, leftLowerLipBottom0, this, 'face');\n        this.bLeftLowerLipBottom0LeftLowerLipBottom1 = new Bone().set(leftLowerLipBottom0, leftLowerLipBottom1, this, 'face');\n        this.bLeftLowerLipBottom1LowerLipBottomMid = new Bone().set(leftLowerLipBottom1, lowerLipBottomMid, this, 'face');\n        this.bRightMouthCornerRightLowerLipBottom0 = new Bone().set(rightMouthCorner, rightLowerLipBottom0, this, 'face');\n        this.bRightLowerLipBottom0RightLowerLipBottom1 = new Bone().set(rightLowerLipBottom0, rightLowerLipBottom1, this, 'face');\n        this.bRightLowerLipBottom1LowerLipBottomMid = new Bone().set(rightLowerLipBottom1, lowerLipBottomMid, this, 'face');\n\n        this.faceBones = [\n            // Face\n            this.bTopMidRightTop0,\n            this.bRightTop0RightTop1,\n            this.bTopMidLeftTop0,\n            this.bLeftTop0LeftTop1,\n            this.bLeftTop1LeftJaw2,\n            this.bLeftJaw2LeftJaw3,\n            this.bLeftJaw3LeftJaw4,\n            this.bLeftJaw4LeftJaw5,\n            this.bLeftJaw5LeftJaw6,\n            this.bLeftJaw6LeftJaw7,\n            this.bLeftJaw7JawMid,\n            this.bRightTop1RightJaw2,\n            this.bRightJaw2RightJaw3,\n            this.bRightJaw3RightJaw4,\n            this.bRightJaw4RightJaw5,\n            this.bRightJaw5RightJaw6,\n            this.bRightJaw6RightJaw7,\n            this.bRightJaw7JawMid,\n            this.bLeftEye0LeftEye1,\n            this.bLeftEye1LeftEye2,\n            this.bLeftEye2LeftEye3,\n            this.bLeftEye3LeftEye4,\n            this.bLeftEye4LeftEye5,\n            this.bLeftEye5LeftEye0,\n            this.bRightEye0RightEye1,\n            this.bRightEye1RightEye2,\n            this.bRightEye2RightEye3,\n            this.bRightEye3RightEye4,\n            this.bRightEye4RightEye5,\n            this.bRightEye5RightEye0,\n            this.bLeftBrow0LeftBrow1,\n            this.bLeftBrow1LeftBrow2,\n            this.bLeftBrow2LeftBrow3,\n            this.bLeftBrow3LeftBrow4,\n            this.bRightBrow0RightBrow1,\n            this.bRightBrow1RightBrow2,\n            this.bRightBrow2RightBrow3,\n            this.bRightBrow3RightBrow4,\n            this.bNose0Nose1,\n            this.bNose1Nose2,\n            this.bNose2Nose3,\n            this.bNose3Nose4,\n            this.bLeftNose0LeftNose1,\n            this.bLeftNose1Nose4,\n            this.bRightNose0RightNose1,\n            this.bRightNose1Nose4,\n            this.bLeftMouthCornerLeftUpperLipTop0,\n            this.bLeftUpperLipTop0LeftUpperLipTop1,\n            this.bLeftUpperLipTop1UpperLipTopMid,\n            this.bRigthMouthCornerRigthUpperLipTop0,\n            this.bRigthUpperLipTop0RigthUpperLipTop1,\n            this.bRigthUpperLipTop1UpperLipTopMid,\n            this.bLeftMouthCornerLeftMiddleLip,\n            this.bLeftMiddleLipLeftUpperLipBottom1,\n            this.bLeftUpperLipBottom1UpperLipBottomMid,\n            this.bRightMouthCornerRightMiddleLip,\n            this.bRightMiddleLipRightUpperLipBottom1,\n            this.bRightUpperLipBottom1UpperLipBototmMid,\n            this.bLeftMiddleLipLeftLowerLipTop0,\n            this.bLeftLowerLipTop0LowerLipTopMid,\n            this.bRightMiddleLipRightLowerLipTop0,\n            this.bRightLowerLipTop0LowerLipTopMid,\n            this.bLeftMouthCornerLeftLowerLipBottom0,\n            this.bLeftLowerLipBottom0LeftLowerLipBottom1,\n            this.bLeftLowerLipBottom1LowerLipBottomMid,\n            this.bRightMouthCornerRightLowerLipBottom0,\n            this.bRightLowerLipBottom0RightLowerLipBottom1,\n            this.bRightLowerLipBottom1LowerLipBottomMid,\n        ]\n        this.bodyBones = [\n            // Body\n            this.bLeftShoulderRightShoulder,\n            this.bRightShoulderRightHip,\n            this.bLeftHipRightHip,\n            this.bLeftShoulderLeftHip,\n            this.bLeftShoulderLeftElbow,\n            this.bLeftElbowLeftWrist,\n            this.bRightShoulderRightElbow,\n            this.bRightElbowRightWrist,\n            this.bLeftHipLeftKnee,\n            this.bLeftKneeLeftAnkle,\n            this.bRightHipRightKnee,\n            this.bRightKneeRightAnkle\n        ];\n        this.bones = this.faceBones.concat(this.bodyBones);\n        this.secondaryBones = [];\n        this.parts = {};\n        this.bodyLen0 = this.getTotalBoneLength(this.bodyBones);\n        this.faceLen0 = this.getTotalBoneLength(this.faceBones);\n\n        this.boneGroups = {\n            'torso': [\n                this.bLeftShoulderRightShoulder,\n                this.bRightShoulderRightHip,\n                this.bLeftHipRightHip,\n                this.bLeftShoulderLeftHip,\n            ],\n            'leftLeg': [\n                this.bLeftHipLeftKnee,\n                this.bLeftKneeLeftAnkle,\n            ],\n            'rightLeg': [\n                this.bRightHipRightKnee,\n                this.bRightKneeRightAnkle\n            ],\n            'leftArm': [\n                this.bLeftShoulderLeftElbow,\n                this.bLeftElbowLeftWrist,\n            ],\n            'rightArm': [\n                this.bRightElbowRightWrist,\n                this.bRightShoulderRightElbow,\n            ],\n            'face': this.faceBones,\n        };\n\n        this.faceBones.forEach(bone => {\n            let parts = [bone.kp0, bone.kp1];\n            parts.forEach(part => {\n                part.baseTransFunc = MathUtils.getTransformFunc(this.bLeftJaw2LeftJaw3.kp0.position,\n                    this.bRightJaw2RightJaw3.kp0.position, part.position);\n            });\n        });\n    }\n\n    update(pose, face) {\n        if (pose.score < MIN_POSE_SCORE) {\n            this.isValid = false;\n            return;\n        }\n\n        this.isValid = this.updatePoseParts(pose);\n        if (!this.isValid) return;\n\n        this.isValid = this.updateFaceParts(face);\n        if (!this.isValid) return;\n\n        // Update bones.\n        this.bones.forEach(bone => {\n            let part0 = this.parts[bone.kp0.name];\n            let part1 = this.parts[bone.kp1.name];\n            bone.kp0.currentPosition = part0.position;\n            bone.kp1.currentPosition = part1.position;\n            bone.score = (part0.score + part1.score) / 2;\n            bone.latestCenter = bone.kp1.currentPosition.add(bone.kp0.currentPosition).divide(2);\n        });\n        // Update secondary bones.\n        let nosePos = this.bNose3Nose4.kp1.currentPosition;\n        this.secondaryBones.forEach(bone => {\n            bone.kp0.currentPosition = bone.kp0.transformFunc(bone.parent.kp0.currentPosition, nosePos);\n            bone.kp1.currentPosition = bone.kp1.transformFunc(bone.parent.kp1.currentPosition, nosePos);\n            bone.score = bone.parent.score;\n            bone.latestCenter = bone.kp1.currentPosition.add(bone.kp0.currentPosition).divide(2);\n        });\n        // Recompute face & body bone scale.\n        this.currentFaceScale = this.getTotalBoneLength(this.faceBones) / this.faceLen0;\n        this.currentBodyScale = this.getTotalBoneLength(this.bodyBones) / this.bodyLen0;\n        this.isValid = true;\n    }\n\n    updatePoseParts(pose) {\n        posePartNames.forEach(partName => {\n            // Use new and old pose's confidence scores as weights to compute the new part position.\n            let part1 = getPartFromPose(pose, partName);\n            let part0 = (this.parts[partName] || part1);\n            let weight0 = part0.score / (part1.score + part0.score);\n            let weight1 = part1.score / (part1.score + part0.score);\n            let pos = part0.position.multiply(weight0).add(part1.position.multiply(weight1));\n            this.parts[partName] = {\n                position: pos,\n                score: part0.score * weight0 + part1.score * weight1,\n            };\n        });\n        if (!this.parts['rightEar'] || !this.parts['leftEar']) {\n            return false;\n        }\n        return true;\n    }\n    \n    updateFaceParts(face) {\n        let posLeftEar = this.parts['leftEar'].position;\n        let posRightEar = this.parts['rightEar'].position;\n        if (face && face.positions && face.positions.length && face.faceInViewConfidence > MIN_FACE_SCORE) {\n            // Valid face results.\n            for (let i = 0; i < facePartNames.length; i++) {\n                let partName = facePartNames[i];\n                let pos = getKeypointFromFaceFrame(face, i);\n                if (!pos) continue;\n                this.parts[partName] = {\n                    position: pos,\n                    score: face.faceInViewConfidence\n                };\n            }\n            // Keep track of the transformation from pose ear positions to face ear positions.\n            // This can be used to infer face position when face tracking is lost.\n            this.leftEarP2FFunc = MathUtils.getTransformFunc(posLeftEar, posRightEar, this.parts['leftJaw2'].position);\n            this.rightEarP2FFunc = MathUtils.getTransformFunc(posLeftEar, posRightEar, this.parts['rightJaw2'].position);\n        } else {\n            // Invalid face keypoints. Infer face keypoints from pose.\n            let fLeftEar = this.leftEarP2FFunc ? this.leftEarP2FFunc(posLeftEar, posRightEar) : posLeftEar;\n            let fRightEar = this.rightEarP2FFunc ? this.rightEarP2FFunc(posLeftEar, posRightEar) : posRightEar;\n            // Also infer face scale from pose.\n            this.currentFaceScale = this.currentBodyScale;\n            this.faceBones.forEach(bone => {\n                let parts = [bone.kp0, bone.kp1];\n                parts.forEach(part => {\n                    this.parts[part.name] = {\n                        position: part.baseTransFunc(fLeftEar, fRightEar),\n                        score: 1,\n                    };\n                });\n            });\n        }\n        return true;\n    }\n\n    findBoneGroup(point) {\n        let minDistances = {};\n        Object.keys(this.boneGroups).forEach(boneGroupKey => {\n            let minDistance = Infinity;\n            let boneGroup = this.boneGroups[boneGroupKey];\n            boneGroup.forEach(bone => {\n                let d = MathUtils.getClosestPointOnSegment(bone.kp0.position, bone.kp1.position, point)\n                    .getDistance(point);\n                minDistance = Math.min(minDistance, d);\n            });\n            minDistances[boneGroupKey] = minDistance;\n        });\n        let minDistance = Math.min(...Object.values(minDistances));\n        let selectedGroups = [];\n        Object.keys(minDistances).forEach(key => {\n            let distance = minDistances[key];\n            if (distance <= minDistance) {\n                selectedGroups.push(this.boneGroups[key]);\n            }\n        });\n        return selectedGroups.flatten();\n    }\n\n    getTotalBoneLength(bones) {\n        let totalLen = 0;\n        bones.forEach(bone => {\n            let d = (bone.kp0.currentPosition || bone.kp0.position).subtract(bone.kp1.currentPosition || bone.kp1.position);\n            totalLen += d.length;\n        });\n        return totalLen;\n    }\n\n    debugDraw(scope) {\n        let group = new scope.Group();\n        scope.project.activeLayer.addChild(group);\n        this.bones.forEach(bone => {\n            let path = new scope.Path({\n                segments: [bone.kp0.currentPosition, bone.kp1.currentPosition],\n                strokeWidth: 2,\n                strokeColor: bone.boneColor\n            });\n            group.addChild(path);\n        });\n        // this.secondaryBones.forEach(bone => {\n        //     let path = new scope.Path({\n        //         segments: [bone.kp0.currentPosition, bone.kp1.currentPosition],\n        //         strokeColor: '#00ff00',\n        //         strokeWidth: 5,\n        //     });\n        //     group.addChild(path);\n        // });\n    }\n\n    debugDrawLabels(scope) {\n        let group = new scope.Group();\n        scope.project.activeLayer.addChild(group);\n        this.bones.forEach(bone => {\n            let addLabel = (kp, name) => {\n                let text = new scope.PointText({\n                    point: [kp.currentPosition.x, kp.currentPosition.y],\n                    content: name,\n                    fillColor: 'black',\n                    fontSize: 7\n                });\n                group.addChild(text);\n            };\n            addLabel(bone.kp0, bone.kp0.name);\n            addLabel(bone.kp1, bone.kp1.name);\n        });\n    }\n\n    reset() {\n        this.parts = [];\n    }\n\n    static getCurrentPosition(segment) {\n        let position = new paper.default.Point();\n        Object.keys(segment.skinning).forEach(boneName => {\n            let bt = segment.skinning[boneName];\n            position = position.add(bt.bone.transform(bt.transform).multiply(bt.weight));\n        });\n        return position;\n    }\n\n    static flipPose(pose) {\n        pose.keypoints.forEach(kp => {\n            if (kp.part && kp.part.startsWith('left')) {\n                kp.part = 'right' + kp.part.substring('left'.length, kp.part.length);\n            } else if (kp.part && kp.part.startsWith('right')) {\n                kp.part = 'left' + kp.part.substring('right'.length, kp.part.length);\n            }\n        });\n    }\n\n    static flipFace(face) {\n        Object.keys(facePartName2Index).forEach(partName => {\n            if (partName.startsWith('left')) {\n                let rightName = 'right' + partName.substr('left'.length, partName.length);\n                let temp = face.scaledMesh[facePartName2Index[partName]];\n                face.scaledMesh[facePartName2Index[partName]] = face.scaledMesh[facePartName2Index[rightName]];\n                face.scaledMesh[facePartName2Index[rightName]] = temp;\n            }\n        });\n    }\n\n    static getBoundingBox(pose) {\n        let minX = 100000;\n        let maxX = -100000;\n        let minY = 100000;\n        let maxY = -100000;\n        let updateMinMax = (x, y) => {\n            minX = Math.min(x, minX);\n            maxX = Math.max(x, maxX);\n            minY = Math.min(y, minY);\n            maxY = Math.max(y, maxY);\n        }\n        pose.frames.forEach(frame => {\n            frame.pose.keypoints.forEach(kp => {\n                updateMinMax(kp.position.x, kp.position.y);\n            });\n            let faceKeypoints = frame.face.positions;\n            for (let i = 0; i < faceKeypoints.length; i += 2) {\n                updateMinMax(faceKeypoints[i], faceKeypoints[i+1]);\n            }\n        });\n        return [minX, maxX, minY, maxY];\n    }\n\n    static translatePose(pose, d) {\n        pose.frames.forEach(frame => {\n            frame.pose.keypoints.forEach(kp => {\n                kp.position.x += d.x;\n                kp.position.y += d.y;\n            });\n            let faceKeypoints = frame.face.positions;\n            for (let i = 0; i < faceKeypoints.length; i += 2) {\n                faceKeypoints[i] += d.x;\n                faceKeypoints[i+1] += d.y;\n            }\n        });\n    }\n\n    static resizePose(pose, origin, scale) {\n        pose.frames.forEach(frame => {\n            frame.pose.keypoints.forEach(kp => {\n                kp.position.x = origin.x + (kp.position.x - origin.x) * scale.x;\n                kp.position.y = origin.y + (kp.position.y - origin.y) * scale.y;\n            });\n            let faceKeypoints = frame.face.positions;\n            for (let i = 0; i < faceKeypoints.length; i += 2) {\n                faceKeypoints[i] = origin.x + (faceKeypoints[i] - origin.x) * scale.x;\n                faceKeypoints[i+1] = origin.y + (faceKeypoints[i+1] - origin.y) * scale.y;\n            }\n        });\n    }\n\n    static toFaceFrame(faceDetection) {\n        let frame = {\n            positions: [],\n            faceInViewConfidence: faceDetection.faceInViewConfidence,\n        };\n        for (let i = 0; i < facePartNames.length; i++) {\n            let partName = facePartNames[i];\n            let p = faceDetection.scaledMesh[facePartName2Index[partName]];\n            frame.positions.push(p[0]);\n            frame.positions.push(p[1]);\n        }\n        return frame;\n    }\n}\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Pose Animator Demos</title>\n  </head>\n  <body>\n    <h1>Pose Animator Demos</h1>\n    <ul>\n      <li><a href='camera.html'>Animated 2D avatar - Camera feed demo</a></li>\n      <li><a href='static_image.html'>Animated 2D avatar - Image demo</a></li>\n    </ul>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"tfjs-models\",\n  \"version\": \"0.0.1\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"license\": \"Apache-2.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \">=8.9.0\"\n  },\n  \"dependencies\": {\n    \"@tensorflow-models/facemesh\": \"^0.0.1\",\n    \"@tensorflow-models/posenet\": \"^2.2.1\",\n    \"@tensorflow/tfjs\": \"^1.7.0\",\n    \"@tensorflow/tfjs-converter\": \"^1.7.0\",\n    \"@tensorflow/tfjs-core\": \"^1.7.0\",\n    \"face-api.js\": \"^0.22.1\",\n    \"paper\": \"file:third_party/paper\",\n    \"stats.js\": \"^0.17.0\"\n  },\n  \"scripts\": {\n    \"watch\": \"cross-env NODE_ENV=development parcel index.html --no-hmr --open \",\n    \"build\": \"cross-env NODE_ENV=production parcel build index.html --public-url ./\",\n    \"build-camera\": \"cross-env NODE_ENV=production parcel build camera.html --public-url ./\",\n    \"lint\": \"eslint .\",\n    \"link-local\": \"yalc link\"\n  },\n  \"browser\": {\n    \"crypto\": false\n  },\n  \"devDependencies\": {\n    \"babel-core\": \"^6.26.3\",\n    \"babel-plugin-transform-runtime\": \"~6.23.0\",\n    \"babel-polyfill\": \"~6.26.0\",\n    \"babel-preset-env\": \"~1.6.1\",\n    \"babel-preset-es2017\": \"^6.24.1\",\n    \"clang-format\": \"~1.2.2\",\n    \"cross-env\": \"^5.2.0\",\n    \"dat.gui\": \"^0.7.2\",\n    \"eslint\": \"^4.19.1\",\n    \"eslint-config-google\": \"^0.9.1\",\n    \"parcel-bundler\": \"~1.12.4\",\n    \"yalc\": \"~1.0.0-pre.27\"\n  },\n  \"eslintConfig\": {\n    \"extends\": \"google\",\n    \"rules\": {\n      \"require-jsdoc\": 0,\n      \"valid-jsdoc\": 0\n    },\n    \"env\": {\n      \"es6\": true\n    },\n    \"parserOptions\": {\n      \"ecmaVersion\": 8,\n      \"sourceType\": \"module\"\n    }\n  },\n  \"eslintIgnore\": [\n    \"dist/\"\n  ]\n}\n"
  },
  {
    "path": "static_image.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <title>PoseNet - Coco Images Demo</title>\n  <style type='text/css'>\n    .illustration-canvas {\n      position: absolute;\n      left: 600px;\n      top: 100px;\n      border: 1px solid #eeeeee;\n    }\n\n    .detection-canvas {\n      position: absolute;\n      left: 10px;\n      top: 100px;\n    }\n\n    h3,\n    h4,\n    p {\n      margin: 10px 0;\n    }\n\n    #main ul {\n      margin: 10px 0;\n      padding: 0;\n    }\n\n    #main li {\n      list-style-type: none;\n      display: inline-block;\n    }\n\n    #status {\n      display: inline-block;\n    }\n\n    #results>div {\n      display: inline-block;\n      width: 513px\n    }\n\n    .footer {\n      position: fixed;\n      left: 0;\n      bottom: 0;\n      width: 100%;\n      color: black;\n    }\n\n    .footer-text {\n      max-width: 600px;\n      margin: auto;\n    }\n\n    /*\n     *  The following loading spinner CSS is from SpinKit project\n     *  https://github.com/tobiasahlin/SpinKit\n     */\n    .sk-spinner-pulse {\n      width: 20px;\n      height: 20px;\n      margin: auto 10px;\n      float: left;\n      background-color: #333;\n      border-radius: 100%;\n      -webkit-animation: sk-pulseScaleOut 1s infinite ease-in-out;\n      animation: sk-pulseScaleOut 1s infinite ease-in-out;\n    }\n\n    @-webkit-keyframes sk-pulseScaleOut {\n      0% {\n        -webkit-transform: scale(0);\n        transform: scale(0);\n      }\n\n      100% {\n        -webkit-transform: scale(1.0);\n        transform: scale(1.0);\n        opacity: 0;\n      }\n    }\n\n    @keyframes sk-pulseScaleOut {\n      0% {\n        -webkit-transform: scale(0);\n        transform: scale(0);\n      }\n\n      100% {\n        -webkit-transform: scale(1.0);\n        transform: scale(1.0);\n        opacity: 0;\n      }\n    }\n\n    .spinner-text {\n      float: left;\n    }\n  </style>\n</head>\n\n<body>\n  <div id=\"loading\">\n    <span id=\"status\"></span>\n    <span class=\"sk-spinner sk-spinner-pulse\"></span>\n  </div>\n  <div id='main'>\n    <div id='results' style='display:none'>\n      <div id=\"multi\">\n        <h4>Pose Animator - Static Image Demo</h4>\n        <video id=\"video\" playsinline style=\" -moz-transform: scaleX(-1);\n          -o-transform: scaleX(-1);\n          -webkit-transform: scaleX(-1);\n          transform: scaleX(-1);\n          display: none;\n          \">\n        </video>\n        <canvas class=\"detection-canvas\"></canvas>\n        <canvas class=\"illustration-canvas\"></cavnas>\n      </div>\n    </div>\n  </div>\n  <div class=\"footer\">\n    <div class=\"footer-text\">\n      <p>\n        Pose Animator runs TF.js <strong>FaceMesh</strong> and <strong>PoseNet</strong> models to animate SVG illustrations with camera feed / static images.<br>\n        It currently supports <strong>single-pose</strong>, <strong>single-face</strong> detection, and has been tested on Destkop Chrome & iOS Safari.\n        <br>\n        (PoseNet model config - MobileNet V1, output stride 16, quant bytes 2)\n      </p>\n    </div>\n  </div>\n  <script src=\"static_image.js\"></script>\n</body>\n\n</html>\n"
  },
  {
    "path": "static_image.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nimport * as posenet_module from '@tensorflow-models/posenet';\nimport * as facemesh_module from '@tensorflow-models/facemesh';\nimport * as tf from '@tensorflow/tfjs';\nimport * as paper from 'paper';\nimport \"babel-polyfill\";\n\nimport dat from 'dat.gui';\nimport {SVGUtils} from './utils/svgUtils'\nimport {PoseIllustration} from './illustrationGen/illustration';\nimport {Skeleton, facePartName2Index} from './illustrationGen/skeleton';\nimport {toggleLoadingUI, setStatusText} from './utils/demoUtils';\n\nimport * as boySVG from './resources/illustration/boy.svg';\nimport * as girlSVG from './resources/illustration/girl.svg';\nimport * as abstractSVG from './resources/illustration/abstract.svg';\nimport * as blathersSVG from './resources/illustration/blathers.svg';\nimport * as tomNookSVG from './resources/illustration/tom-nook.svg';\nimport * as boy_doughnut from './resources/images/boy_doughnut.jpg';\nimport * as tie_with_beer from './resources/images/tie_with_beer.jpg';\nimport * as test_img from './resources/images/test.png';\nimport * as full_body from './resources/images/full-body.png';\nimport * as full_body_1 from './resources/images/full-body_1.png';\nimport * as full_body_2 from './resources/images/full-body_2.png';\n\n// clang-format off\nimport {\n  drawKeypoints,\n  drawPoint,\n  drawSkeleton,\n  renderImageToCanvas,\n} from './utils/demoUtils';\nimport { FileUtils } from './utils/fileUtils';\n\n// clang-format on\nconst resnetArchitectureName = 'MobileNetV1';\nconst avatarSvgs = {\n  'girl': girlSVG.default,\n  'boy': boySVG.default,\n  'abstract': abstractSVG.default,\n  'blathers': blathersSVG.default,\n  'tom-nook': tomNookSVG.default,\n};\nconst sourceImages = {\n  'boy_doughnut': boy_doughnut.default,\n  'tie_with_beer': tie_with_beer.default,\n  'test_img': test_img.default,\n  'full_body': full_body.default,\n  'full_body_1': full_body_1.default,\n  'full_body_2': full_body_2.default,\n};\n\nlet skeleton;\nlet illustration;\nlet canvasScope;\n\nlet posenet;\nlet facemesh;\n\nconst VIDEO_WIDTH = 513;\nconst VIDEO_HEIGHT = 513;\n\nconst CANVAS_WIDTH = 513;\nconst CANVAS_HEIGHT = 513;\n\nconst defaultQuantBytes = 2;\nconst defaultMultiplier = 1.0;\nconst defaultStride = 16;\nconst defaultInputResolution = 257;\nconst defaultMaxDetections = 1;\nconst defaultMinPartConfidence = 0.1;\nconst defaultMinPoseConfidence = 0.2;\nconst defaultNmsRadius = 20.0;\n\nlet predictedPoses;\nlet faceDetection;\nlet sourceImage;\n\n/**\n * Draws a pose if it passes a minimum confidence onto a canvas.\n * Only the pose's keypoints that pass a minPartConfidence are drawn.\n */\nfunction drawResults(image, canvas, faceDetection, poses) {\n  renderImageToCanvas(image, [VIDEO_WIDTH, VIDEO_HEIGHT], canvas);\n  const ctx = canvas.getContext('2d');\n  poses.forEach((pose) => {\n    if (pose.score >= defaultMinPoseConfidence) {\n      if (guiState.showKeypoints) {\n        drawKeypoints(pose.keypoints, defaultMinPartConfidence, ctx);\n      }\n\n      if (guiState.showSkeleton) {\n        drawSkeleton(pose.keypoints, defaultMinPartConfidence, ctx);\n      }\n    }\n  });\n  if (guiState.showKeypoints) {\n    faceDetection.forEach(face => {\n      Object.values(facePartName2Index).forEach(index => {\n          let p = face.scaledMesh[index];\n          drawPoint(ctx, p[1], p[0], 3, 'red');\n      });\n    });\n  }\n}\n\nasync function loadImage(imagePath) {\n  const image = new Image();\n  const promise = new Promise((resolve, reject) => {\n    image.crossOrigin = '';\n    image.onload = () => {\n      resolve(image);\n    }\n  });\n\n  image.src = imagePath;\n  return promise;\n}\n\nfunction multiPersonCanvas() {\n  return document.querySelector('#multi canvas');\n}\n\nfunction getIllustrationCanvas() {\n  return document.querySelector('.illustration-canvas');\n}\n\n/**\n * Draw the results from the multi-pose estimation on to a canvas\n */\nfunction drawDetectionResults() {\n  const canvas = multiPersonCanvas();\n  drawResults(sourceImage, canvas, faceDetection, predictedPoses);\n  if (!predictedPoses || !predictedPoses.length || !illustration) {\n    return;\n  }\n\n  skeleton.reset();\n  canvasScope.project.clear();\n\n  if (faceDetection && faceDetection.length > 0) {\n    let face = Skeleton.toFaceFrame(faceDetection[0]);\n    illustration.updateSkeleton(predictedPoses[0], face);\n  } else {\n    illustration.updateSkeleton(predictedPoses[0], null);\n  }\n  illustration.draw(canvasScope, sourceImage.width, sourceImage.height);\n\n  if (guiState.showCurves) {\n    illustration.debugDraw(canvasScope);\n  }\n  if (guiState.showLabels) {\n    illustration.debugDrawLabel(canvasScope);\n  }\n}\n\n/**\n * Loads an image, feeds it into posenet the posenet model, and\n * calculates poses based on the model outputs\n */\nasync function testImageAndEstimatePoses() {\n  toggleLoadingUI(true);\n  setStatusText('Loading FaceMesh model...');\n  document.getElementById('results').style.display = 'none';\n\n  // Reload facemesh model to purge states from previous runs.\n  facemesh = await facemesh_module.load();\n\n  // Load an example image\n  setStatusText('Loading image...');\n  sourceImage = await loadImage(sourceImages[guiState.sourceImage]);\n\n  // Estimates poses\n  setStatusText('Predicting...');\n  predictedPoses = await posenet.estimatePoses(sourceImage, {\n    flipHorizontal: false,\n    decodingMethod: 'multi-person',\n    maxDetections: defaultMaxDetections,\n    scoreThreshold: defaultMinPartConfidence,\n    nmsRadius: defaultNmsRadius,\n  });\n  faceDetection = await facemesh.estimateFaces(sourceImage, false, false);\n\n  // Draw poses.\n  drawDetectionResults();\n\n  toggleLoadingUI(false);\n  document.getElementById('results').style.display = 'block';\n}\n\nlet guiState = {\n  // Selected image\n  sourceImage: Object.keys(sourceImages)[0],\n  avatarSVG: Object.keys(avatarSvgs)[0],\n  // Detection debug\n  showKeypoints: true,\n  showSkeleton: true,\n  // Illustration debug\n  showCurves: false,\n  showLabels: false,\n};\n\nfunction setupGui() {\n  const gui = new dat.GUI();\n  \n  const imageControls = gui.addFolder('Image');\n  imageControls.open();\n  gui.add(guiState, 'sourceImage', Object.keys(sourceImages)).onChange(() => testImageAndEstimatePoses());\n  gui.add(guiState, 'avatarSVG', Object.keys(avatarSvgs)).onChange(() => loadSVG(avatarSvgs[guiState.avatarSVG]));\n  \n  const debugControls = gui.addFolder('Debug controls');\n  debugControls.open();\n  gui.add(guiState, 'showKeypoints').onChange(drawDetectionResults);\n  gui.add(guiState, 'showSkeleton').onChange(drawDetectionResults);\n  gui.add(guiState, 'showCurves').onChange(drawDetectionResults);\n  gui.add(guiState, 'showLabels').onChange(drawDetectionResults);\n}\n\n/**\n * Kicks off the demo by loading the posenet model and estimating\n * poses on a default image\n */\nexport async function bindPage() {\n  toggleLoadingUI(true);\n  canvasScope = paper.default;\n  let canvas = getIllustrationCanvas();\n  canvas.width = CANVAS_WIDTH;\n  canvas.height = CANVAS_HEIGHT;\n  canvasScope.setup(canvas);\n\n  await tf.setBackend('webgl');\n  setStatusText('Loading PoseNet model...');\n  posenet = await posenet_module.load({\n    architecture: resnetArchitectureName,\n    outputStride: defaultStride,\n    inputResolution: defaultInputResolution,\n    multiplier: defaultMultiplier,\n    quantBytes: defaultQuantBytes\n  });\n\n  setupGui(posenet);\n  setStatusText('Loading SVG file...');\n  await loadSVG(Object.values(avatarSvgs)[0]);\n}\n\nwindow.onload = bindPage;\nFileUtils.setDragDropHandler(loadSVG);\n\n// Target is SVG string or path\nasync function loadSVG(target) {\n  let svgScope = await SVGUtils.importSVG(target);\n  skeleton = new Skeleton(svgScope);\n  illustration = new PoseIllustration(canvasScope);\n  illustration.bindSkeleton(skeleton, svgScope);\n  testImageAndEstimatePoses();\n}\n"
  },
  {
    "path": "third_party/paper/AUTHORS.md",
    "content": "## Authors\n\n- Jürg Lehni <juerg@scratchdisk.com>\n- Jonathan Puckey <jonathan@studiomoniker.com>\n\n## Contributors\n\n- Harikrishnan Gopalakrishnan <hari.exeption@gmail.com>\n- Jan Bösenberg <development@iconexperience.com>\n- Jt Whissel <jtwhissel@gmail.com>\n- Andrew Roles <jaroles58@gmail.com>\n- Jacob Lites <jnightlight@gmail.com>\n- Justin Ridgewell <justinridgewell@gmail.com>\n- Andrew Wagenheim <abwagenheim@gmail.com>\n- Scott Kieronski <baroes0239@gmail.com>\n- Samuel Asensi <asensi.samuel@gmail.com>\n- Takahiro Nishino <sapics.dev@gmail.com>\n"
  },
  {
    "path": "third_party/paper/CHANGELOG.md",
    "content": "# Change Log\n\n## `0.12.4`\n\n### Added\n\n- Allow paper core import in TypeScript (#1713).\n- Boolean: Improve performance from `O(n^2)` to nearly `O(n)` by the use of the\n  sweep and prune algorithm (#1737).\n- Docs: Add support for nullable values.\n\n### Fixed\n\n- Fix `PathItem#getCrossing()` to not return overlaps (#1409).\n- Fix regression in `Curve.getIntersections()` (#1638).\n- Fix edge cases in `CurveLocation.isCrossing()` (#1419, #1263).\n- Fix `SymbolItem#hitTestAll()` to return only one match per symbol item\n  (#1680).\n- Fix handling of negative `Shape` sizes (#1733).\n- Fix parsing of RGB `Color` strings with percentages (#1736).\n- Fix `Shape` bounds when passing position in constructor (#1686).\n- Prevent nested group matrix from reset when transforming parent (#1711).\n- Boolean: Fix edge cases in overlap detection (#1262).\n- Boolean: Add check for paths with only one segment (#1351).\n- Boolean: Correctly handle open filled paths (#1647).\n- Boolean: Avoid winding number edge cases (#1619).\n- Docs: Fix some documentation return types (#1679).\n\n## `0.12.3`\n\n### Added\n\n- Add documentation for `Item#internalBounds`.\n\n### Fixed\n\n- Fix regression in `Color` change propagation (#1672, #1674).\n- SVG Export: Fix viewport size of exported `Symbol` (#1668).\n- Handle non-invertible matrices in `Item#contains()` (#1651).\n- Improve documentation for `Item#clipMask` (#1673).\n- Improve TypeScript definitions (#1659, #1663, #1664, #1667).\n\n## `0.12.2`\n\n### Fixed\n\n- Fix drawing with compound-paths as clip-items (#1361).\n- Fix drawing of path selection with small handle size (#1327).\n- Do not ignore `Group#clipItem.matrix` in `Group#internalBounds` (#1427).\n- Correctly calculate bounds with nested empty items (#1467).\n- Fix color change propagation on groups (#1152).\n- Fix `Path#arcTo()` where `from` and `to` points are equal (#1613).\n- Improve `new Raster(size[, position])` constructor (#1621).\n- SVG Export: Fix error when `Item#matrix` is not invertible (#1580).\n- SVG Export: Include missing viewBox attribute (#1576).\n- SVG Import: Use correct default values for gradients (#1632, #1660).\n- SVG Import: Add basic `<switch/>` support (#1597).\n- JSON Import: Prevent `Item#insert()` method from being overridden (#1392).\n- PaperScript: Fix issues with increment/decrement operators (#1450, #1611).\n\n## `0.12.1`\n\n### Added\n\n- Add TypeScript definition, automatically generated from JSDoc comments\n  (#1612).\n- Support `new Raster(size[, position])` constructor.\n- Expose `Raster#context` accessor.\n- Implement `Raster#clear()` method to clear associated canvas context.\n- Node.js: Add support for Node.js v11 and v12.\n\n### Fixed\n\n- Fix parsing of CSS colors with spaces in parentheses (#1629).\n- Improve `Color.random()` documentation.\n- Fix `Tween#then()` documentation.\n\n### Removed\n\n- Node.js: Remove support for Node.js v6.\n\n## `0.12.0`\n\n### News\n\nAnother release, another new member on the team: Please welcome\n[@arnoson](https://github.com/arnoson), who has worked hard on the all new\nanimation support, exposed through the `Tween` class and its various methods on\nthe `Item` class, see below for details:\n\n### Added\n\n- Add new `Tween` class and related methods on `Item`, to animate and\n  interpolate their various properties, including colors, sub-properties, etc.:\n  `Item#tween(from, to, options)`, `Item#tween(to, options)`,\n  `Item#tween(options)`, `Item#tweenFrom(from, options)`,\n  `Item#tweenTo(to, options)`\n\n### Fixed\n\n- Only draw Raster if image is not empty (#1320).\n- Emit mousedrag events on correct items when covered by other items (#1465).\n- Fix drawing issues of bounds and position with `Group#selectedColor` (#1571).\n- Fix `Item.once()` to actually only emit event once.\n- Various documentation fixes and improvements (#1399).\n\n## `0.11.8`\n\n### News\n\nThis is the first release in quite a while, and it was made possible thanks to\ntwo new people on the team:\n\nA warm welcome to [@sasensi](https://github.com/sasensi) and\n[@sapics](https://github.com/sapics), the two new and very active maintainers /\ncontributors! :tada:\n\nTheir efforts mean that many issues are finally getting proper attention and\nsolid fixes, as we are paving the way for the upcoming release of `1.0.0`. Here\nthe fixes and additions from the past two weeks:\n\n### Fixed\n\n- Prevent `paper` object from polluting the global scope (#1544).\n- Make sure `Path#arcTo()` always passes through the provide through point\n  (#1477).\n- Draw shadows on `Raster` images (#1437).\n- Fix boolean operation edge case (#1506, #1513, #1515).\n- Handle closed paths with only one segment in `Path#flatten()` (#1338).\n- Remove memory leak on gradient colors (#1499).\n- Support alpha channel in CSS colors (#1468, #1539, #1565).\n- Improve color CSS string parsing and documentation.\n- Improve caching of item positions (#1503).\n- Always draw selected position in global coordinates system (#1545).\n- Prevent empty `Symbol` items from causing issues with transformations (#1561).\n- Better detect when a cached global matrix is not valid anymore (#1448).\n- Correctly draw selected position when item is in a group with matrix not\n  applied (#1535).\n- Improve handling of huge amounts of segments in paths (#1493).\n- Do not trigger error messages about passive event listeners on Chrome (#1501).\n- Fix errors with event listeners on mobile (#1533).\n- Prevent first mouse drag event from being emitted twice (#1553).\n- Support optional arguments in translate and rotate statements in SVG Import\n  (#1487).\n- Make sure SVG import always applies imported attributes (#1416).\n- Correctly handle `Raster` images positions in SVG import (#1328).\n- Improve documentation for `Shape#toPath()` (#1374).\n- Improve documentation of hit test coordinate system (#1430).\n- Add documentation for `Item#locked` (#1436).\n- Support Webpack bundling in Node.js server (#1482).\n- Travis CI: Get unit tests to run correctly again.\n- Travis CI: Remove Node 4 and add Node 9.\n\n### Added\n\n- `Curve#getTimesWithTangent()` and `Path#getOffsetsWithTangent()` as a way to\n  get the curve-times / offsets where the path is tangential to a given vector.\n- `Raster#smoothing` to control if pixels should be blurred or repeated when a\n  raster is scaled up (#1521).\n- Allow `PaperScript`to export from executed code, supporting `export default`,\n  named exports, as well as `module.exports`.\n\n## `0.11.5`\n\n### Fixed\n\n- Fix `Curve#isSelected()` to correctly reflect the state of `#handle1` (#1378).\n- Key Events: Fix auto-filling issue on Chrome (#1358, #1365).\n- Boolean: Check that overlaps are on the right path (#1321).\n- Boolean: Add better filtering for invalid segments (#1385).\n\n### Added\n\n- Node.js: Add JPEG support to exportFrames() (#1166).\n\n## `0.11.4`\n\n### Changed\n\n- Node.js: Add support for v8, and keep testing v4, v6, v7 in Travis CI.\n\n## `0.11.3`\n\n### Fixed\n\n- Mouse Events: Fix item-based `doubleclick` events (#1316).\n- Overhaul the caching of bounds and matrix decomposition, improving reliability\n  of `Item#rotation` and `#scaling` and fixing situations caused by wrongly\n  caching `Item#position` and `#bounds` values.\n- Prevent consumed properties in object literal constructors from being set on\n  the instance.\n\n### Changed\n\n- Make all functions and accessors enumerable on all Paper.js classes.\n\n## `0.11.2`\n\n### Fixed\n\n- PaperScript: Fix a parsing error in math operations without white-space\n  (#1314).\n\n## `0.11.1`\n\n### Fixed\n\n- Bring back deactivation of Node.js modules on browsers. This has most probably\n  broken Webpack bundling in `0.11.0`.\n\n## `0.11.0`\n\n### Changed\n\n- Separate `paper` module on NPM into: `paper`, `paper-jsdom` and\n `paper-jsdom-canvas` (#1252):\n    - `paper` is the main library, and can be used directly in a browser\n      context, e.g. a web browser or worker.\n    - `paper-jsdom` is a shim module for Node.js, offering headless use with SVG\n      importing and exporting through [jsdom](https://github.com/tmpvar/jsdom).\n    - `paper-jsdom-canvas` is a shim module for Node.js, offering canvas\n      rendering through [Node-Canvas](https://github.com/Automattic/node-canvas)\n      as well as SVG importing and exporting through\n      [jsdom](https://github.com/tmpvar/jsdom).\n\n### Added\n\n- PaperScript: Support newer, external versions of Acorn.js for PaperScript\n  parsing, opening the doors to ES 2015 (#1183, #1275).\n- Hit Tests: Implement `options.position` to hit `Item#position` (#1249).\n- Split `Item#copyTo()` into `#addTo()` and `#copyTo()`.\n\n### Fixed\n\n- Intersections: Bring back special handling of curve end-points (#1284).\n- Intersections: Correctly handle `Item#applyMatrix = false` (#1289).\n- Boolean: Bring back on-path winding handling (#1281).\n- Boolean: Pass on options in `PathItem#subtract(path, options)` (#1221).\n- Boolean: Implement `options.trace` as a way to perform boolean operations on\n  the strokes / traces instead of the fills / areas of the involved paths\n  (#1221).\n- Boolean: Always return `CompoundPath items (#1221).\n- Style: Fix handling of gradient matrices when `Item#applyMatrix = false`\n  (#1238).\n- Style: Prevent cleaning pre-existing styles when setting `Item#style` to an\n  object (#1277).\n- Mouse Events: Only handle dragItem if the hitItem responds to `mousedrag`\n  events (#1247, #1286).\n- Bounds: Clear parent's bounds cache when item's visibility changes (#1248).\n- Bounds: Fix calculation of internal bounds with children and\n  `Item#applyMatrix = false` (#1250).\n- Hit-Tests: Fix issue with non-invertible matrices ( #1271).\n- SVG Import: Improve handling of sizes in percent (#1242).\n- Rectangle: Improve handling of dimension properties, dealing better with\n  `left` / `top` / `right` / `bottom` / `center` values (#1147).\n- Scene Graph: Do not allow inserting same item as child multiple times.\n- Path: Fix handling of `insert = false` in `new Path.Constructor()`\n  initialization (#1305).\n- PaperScript: Fix positive unary operator.\n- Docs: Fix parameter sequence in Matrix constructor (#1273).\n- Docs: Add documentation for options.bound and options.matrix in `#exportSVG()`\n  (#1254).\n- Docs: Fix wrong `@link` references to bean properties.\n\n## `0.10.3`\n\n### Changed\n\n- Node.js: Support v7, and keep testing v4 up to v7 in Travis CI.\n- Node.js: Loosely couple Node.js / Electron code to `Canvas` module, and treat\n  its absence like a headless web worker context in the browser (#1103).\n- Clean up handling of `Item#_set()`, `#set()` and `#initialize()`:\n    - Use `#_set()` for actually setting internal properties, e.g. on `Point`,\n      `Size`, so that derived classes can reuse other parts without having to\n      override each individual function (e.g. in `SegmentPoint`)\n    - Define `#set()` as a shortcut to `#initialize()` on all basic types, to\n      offer the same amount of flexibility when setting values, accepting object\n      literals as well as lists of value arguments.\n- SVG Export: Add support for shorter `h` / `v` commands for horizontal /\n  vertical lines in SVG output.\n- JSON Import / Export: Implement new and shorter segments array notation:\n    - Close paths by including `true` as the last entry\n    - Allow nested segment arrays to be passed to `PathItem.create()` as well as\n      the `CompoundPath` constructor to create all sub-paths.\n- Reflect `View#zoom` and `View#center` through matrix decomposition, and\n  implement additional decomposed properties such as `#scaling` and `#rotation`.\n- Reduce various internal epsilon values for general improved precision while\n  maintaining reliability.\n- Split `PathItem#resolveCrossings()` into `#resolveCrossings()` and\n  `#reorient()` (#973).\n\n### Added\n\n- Implement `Path#divideAt(location)`, in analogy to `Curve#divideAt(location)`.\n- Add `PathItem#compare()` as a way to compare the geometry of two paths to see\n  if they describe the same shape, handling cases where paths start in different\n  segments or use different amounts of curves to describe the same shape.\n- Implement `Curve#hasLength()` as an optimized check for curve-length (#1109).\n- Implement `Curve#classify()` to determine the type of cubic Bézier curve via\n  discriminant classification, based on an approach described by Loop and Blinn,\n  and use it to simplify curve self-intersection handling (#773, #1074, #1235).\n- Add `Curve.getPeaks()` as a fast way to retrieve points that are often similar\n  to the more costly curvature extrema for use in curve offsetting.\n- Expose `Curve. getCurveLineIntersections()` for use in curve offsetting.\n- Add `Line.getDistance()` and use it in `Curve.getOverlaps()` (#1253).\n- Implement `Segment#isSmooth()` and use it in handling of stroke-joins.\n- Bring back caching of `Item#rotation` and `#scaling`, but only allow matrix\n  decomposition-based properties on items with `#applyMatrix = false`\n  (#1004, #1177).\n\n### Fixed\n\n- Many improvements to boolean operations:\n    - Improve performance of boolean operations when there no actual crossings\n      between the paths, but paths may be contained within each other.\n    - Improve path tracing approach by implementing a branching structure and\n      sorting segments according to their reliability as starting points for\n      traces (#1073).\n    - Improve calculation and reliability of winding contributions.\n    - Improve code that resolves crossings and reorients compound-paths based\n      on how the sub-paths are nested.\n    - Fix issue where unite operation wrongly fills inner path (#1075).\n    - Better handle cases where one `Path` is open and the other closed (#1089).\n    - Solve `null` exceptions during complex boolean operations (#1091).\n    - Improve bidirectional curve-time rescaling in `divideLocations()` (#1191).\n    - Improve handling of intersections between touching curves (#1165).\n    - Improve reliability of `Curve#getIntersections()` (#1174).\n    - Fix `getOverlaps()` to always return overlaps in correct sequence (#1223).\n    - Improve handling of multiple crossings on the same curve.\n- Improve tangent direction handling in `CurveLocation#isCrossing()`, by finding\n  unambiguous vectors, taking the curve's loop, cusp, inflection, and \"peak\"\n  points into account (#1073, #1074).\n- Prevent `Path#getStrokeBounds(matrix)` from accidentally modifying segments\n  (#1102).\n- Improve compatibility with JSPM (#1104).\n- SVG Import: Correctly handle multiple sequential move commands (#1134).\n- SVG Export: Properly handle generated IDs (#1138).\n- SVG Export: Support multiple gradient offsets at 0 (#1241).\n- Fix imprecision in `Numerical.findRoot()` (#1149).\n- PaperScript: Prevent invalid JavaScript in assignment operators (#1151).\n- Hit Tests: Improve handling of SymbolItem in#hitTestAll() (#1199).\n- Hit Tests: Fix stroke hit-testing for rounded shape items (#1207).\n- Fix matrix cloning for groups with `#applyMatrix = false` ( #1225).\n- Correctly handle offset in `Curve#divideAt(offset)` (#1230).\n- Fix issue with `Curve#isStraight()` where handles were checked incorrectly\n  for collinearity (#1269). \n- Fix `Line#getSide()` imprecisions when points are on the line.\n- Docs: Fix documentation of `Project#hitTestAll()` (#536).\n- Docs: Improve description of `option.class` value in `Project#hitTest()`\n  (#632).\n\n### Removed\n\n- Remove `Numerical.TOLERANCE = 1e-6` as there is no internal use for it\n  anymore.\n\n## `0.10.2`\n\n### Fixed\n\n- Get published version to work correctly in Bower again.\n\n## `0.10.1`\n\n### Fixed\n\n- Correct a few issues with documentation and NPM publishing that slipped\n  through in the `0.10.0` release.\n\n## `0.10.0`\n\n### Preamble\n\nThis is a huge release for Paper.js as we aim for a version `1.0.0` release\nlater this year. As of this version, all notable changes are documented in the\nchange-log following common [CHANGELOG](http://keepachangelog.com/) conventions.\nPaper.js now also adheres to [Semantic Versioning](http://semver.org/).\n\nThere are many items in the changelog (and many more items not in the changelog)\nso here a high-level overview to frame the long list of changes:\n\n- Boolean operations have been improved and overhauled for reliability and\n  efficiency. These include the path functions to unite, intersect, subtract,\n  exclude, and divide with another path.\n\n- There was a large amount of work implementing test coverage under QUnit.\n\n- Mouse and key handling has been re-engineered and extended to work with view.\n  Many outstanding bugs have been fixed with mouse and key handling.\n\n- Many SVG-handling enhancements and bug-fixes, including handling browser-\n  specific interpretations of the SVG standard, have been added.\n\n- There are API name changes for more consistency as well as some required by\n  changes in the EcmaScript 6 standard (e.g., `Symbol` → `SymbolDefinition`).\n\n- Even though it is not new, since version `0.9.22` Paper.js no longer resizes\n  the canvas to match the view. The canvas must be resized independently.\n\nThank you all for using Paper.js, submitting bugs and ideas, and all those that\ncontribute to the code.\n\n### Changed\n\n- Significant overhaul and improvements of boolean path operations\n  `PathItem#unite()`, `#subtract()`, `#intersect()`, `#exclude()`, `#divide()`:\n    - Improve handling of self-intersecting paths and non-zero fill-rules.\n    - Handle operations on identical paths.\n    - Improve handling of near-collinear lines.\n    - Handle self-intersecting paths that merely \"touch\" themselves.\n    - Handle situations where all encountered intersections are part of overlaps.\n- Methods that accepted a `time` parameter or boolean second parameter causing\n  the argument to be interpreted as curve-time instead of offset are now\n  separate functions with distinct names (#563):\n    - `Curve#getNormalAt(time, true)` → `#getNormalAtTime(true)`\n    - `Curve#divide()` → `#divideAt(offset)` / ` #divideAtTime(time)`\n    - `Curve#split()` → `#splitAt(offset)` / `#splitAtTime(time)`\n    - `Curve#getParameterAt(offset)` → `#getTimeAt(offset)`\n    - `Curve#getParameterOf(point)` → `getTimeOf(point)`\n    - `Curve#getPointAt(time, true)` → `#getPointAtTime(time)`\n    - `Curve#getTangentAt(time, true)` → `#getTangentAtTime(time)`\n    - `Curve#getNormalAt(time, true)` → `#getNormalAtTime(time)`\n    - `Curve#getCurvatureAt(time, true)` → `#getCurvatureAtTime(time)`\n    - `CurveLocation#parameter` → `#time`\n    - `Path#split(offset/location)` → `#splitAt(offset/location)`\n- Significant improvement of reliability of bezier fat-line clipping code in\n  `PathItem#getIntersections()` and `#getCrossings()`.\n- `PathItem#smooth()` now accepts an `options.type` string  specifying which\n  smoothing algorithm to use: `'asymmetric'` (default), `'continuous'`,\n  `'catmull-rom'`, and `'geometric'` (#338).\n- `PathItem#flatten()`: argument has been changed from `tolerance` (maximum\n  allowed distance between points) to `flatness` (maximum allowed error) (#618).\n- Update internal Acorn JavaScript parser to `0.5.0`, the last small version.\n- Transition to Gulp based build process.\n- Update QUnit to `1.20.0`.\n- Update to JSDOM `8.3.0`, to benefit from integrated image and canvas support.\n- Complete refactoring of keyboard event handling to increase reliably when\n  handling special keys.\n- Complete refactoring of mouse-event handling on item and view, to better\n  handle event propagation, default behavior and `Item#removeOn()` calls.\n- Simplify and streamline the mouse-handling code on `Tool` (#595).\n- Mouse handlers can to return `false` to call `event.stop()`, stopping event\n  propagation and prevent the default browser behavior.\n- `event.preventDefault()` is called by default after any handled mouse mouse\n  events, except `'mousemove'`, and only on a `'mousedown'` event if the view\n  or tool respond to `'mouseup'`.\n- Switch to the new HTML5 Page Visibility API when detecting invisible documents\n  and canvases.\n- Rename `#windingRule` to `#fillRule` on `Item` and `Style`.\n- Do not replace existing named child reference on `Item#children` with new one\n  when the name is identical.\n- Limit the effects of `#strokeScaling` to `PathItem` and `Shape` (#721).\n- Throw an exception if arguments to `#smooth()` are segments or curves from\n  incorrect paths.\n- Rename `Matrix#concatenate()` to `#append()` and `preConcatenate()` to\n  `#prepend()`.\n- Make `Matrix#shiftless()` and `#orNullIfIdentity()` internal functions.\n- De-bounce internal `View#update()` calls to minimize the number of times a\n  canvas is redrawn (#830, #925).\n- `Symbol` now clashes with ES6 definition of Symbol and has been changed\n  (#770):\n    - `Symbol` → `SymbolDefinition`\n    - `PlacedSymbol` → `SymbolItem`\n    - `Symbol#definition` → `SymbolDefinition#item`\n    - `PlacedSymbol#symbol` → `SymbolItem#definition`\n- Don't serialize deprecated `Style#font` property.\n- Don't serialize text-styles in non-text items (#934).\n- Changed argument `parameter` to `time` for Postscript-style drawing commands.\n- `Item#clone()`: optional argument is now an options object with defaults\n  `{insert: true, deep: true}`. `insert` controls whether the clone is inserted\n  into the project and `deep` controls whether the item's children are cloned.\n  The previous boolean optional argument is still interpreted as the `insert`\n  option (#941).\n- `Matrix` properties `#b` and `#c` have been reversed to match common standard.\n- `#importSVG()`: improve handling of style inheritance for nested `<defs>`.\n- Move `PaperScript#execute()` URL argument into `options.url` (#902).\n- PaperScript: Only translate `==` to `equals() calls for `Point`, `Size` and\n  `Color` (#1043).\n\n### Added\n\n- Use unified code-base for browsers, Node.js, Electron, and anything\n  in-between, and enable npm install for browser use (#739).\n- Start using automatic code testing and deployment of prebuilt versions through\n  Travis CI.\n- Reach JSHint compliance and include regular linting in Travis CI tests.\n- Use QUnit tests for leaked globals.\n- Define code format standards in .editorconfig file\n- Add support for running without a canvas for Web Workers, Node.js\n  (#561, #582, #634).\n- Add support for all common mouse events to `View`.\n- Add support for `'keydown'` and `'keyup'` events to `View` (#896).\n- Add `View#requestUpdate()` function to minimize number of actual canvas\n  redraw.\n- Add `View#matrix` to allow matrix transformation to be accessed and modified\n  directly on the view (#832).\n- Multiple additions to SVG export (`#exportSVG()`):\n    - Support `{ precision: value }` option.\n    - Support `#fillRule` through the SVG `fill-rule` attribute.\n    - Support `#blendMode` through the CSS `mix-blend-mode` attribute.\n- Various additions to `#getItems()` on `Project` and `Item`:\n    - Add support for `{ recursive: false }` as a way to prevent iterating over\n      all children of children.\n    - Add support for `{ match: function() {} }`, so the match function can be\n      passed in combination with other options.\n- Add `Item#copyAttributes()` and `Item#copyContent()`, and use them in\n  `Item#clone()`.\n- Add optional `insert` boolean argument to `Path#toShape()`, `Shape#toPath()`,\n  `Item#rasterize()`. Default is to insert, set to `false` to prevent the\n  created item from being inserted into the scene graph.\n- Add visual item comparison to QUnit, through rasterization and Resemble.js\n  diffing.\n- Add many unit tests for known edge cases in boolean operations and curve\n  intersections.\n- Add `Project#addLayer()` and `Project#insertLayer()` (#903).\n- Layers may now be given names and be accessed through `project.layers[name]`\n  (#491).\n- Add `Matrix#prepended()` and `#appended()` to return copies of the modified\n  matrix.\n- `Shape#hitTest()`: Add boolean option `options.stroke` (#911).\n- Insert version number into docs.\n- Support `Raster#onLoad()` events on `Raster#setImage()` now (#924).\n- Add `Raster#onError()` event support (#849).\n- Allow the control of automatic updating of the canvas through\n  `View#autoUpdate` (default: `true`)(#921).\n- Set `1px` default `strokeWidth` for SVG imports to fix IE/Edge default (#467).\n- `ImportSVG()` passes imported SVG data to `onLoad` callback as second\n  parameter.\n- Add `#interpolate` for `Segment`, `Path`, and `CompoundPath` (#624).\n- Implement `CompoundPath#flatten()`, `#simplify()`, `#smooth()` (#727).\n- Implement `#hitTestAll()` to return all items that were hit (#536).\n- `#importSVG()` implements `option.onError` callback (#969).\n- `settings.insertItems` controls whether newly created items are inserted or\n  not (default: `true`).\n- Add `#importSVG()` `option.insert` (default: `true`) to control insertion\n  (#763).\n- Add new options to `#exportSVG()` to control output bounds and transformation\n  matrix (#972).\n- Allow `Item#position` to be selected via `Item#position.selected` (#980).\n- Add `tolerance` argument to `Path#join(path, tolerance)`.\n- Add `Curve#getOffsetAtTime(time)`, as the reverse of\n  `Curve#getTimeAt(offset)`.\n- Add `Raster#loaded` to reflect the loading state of its image.\n\n### Fixed\n\n- Fix calculations of `Item#strokeBounds` for all possible combinations of\n  `Item#strokeScaling` and `Item#applyMatrix` for `Path`, `Shape` and\n  `SymbolItem`, along with correct handling of such strokes in Item#hitTest()\n  (#697, #856, #1014). \n- Make new code-base unified for Node.js/browser work with module bundlers like\n  Webpack (#986).\n- Improve hit-testing and `#contains()` checks on path with horizontal lines\n  (#819).\n- Improve reliability of `Path#getInteriorPoint()` in rare edge-cases.\n- Handle non-reversible matrices in `Item#hitTest()` (#617).\n- Fix various issues with adding and removing of segments in paths (#815).\n- Support bubbling up of `doubleclick` events on `Group` (#834).\n- Fix wrong `#key` values in key-events that do not match character (#881).\n- Fix keyboard event handling of control and meta keyboard sequences and special\n  character handling (#860).\n- Handle incorrect mouse event on `ctrl-alt-del` key sequence on Chrome/Windows\n  (#800).\n- Do not rasterize items if the resulting raster will be empty (#828).\n- Fix SVG serialization in JSDOM `7.0.0` and newer (#821).\n- Correctly handle gradients in SVG import on Firefox (#666).\n- Consistently interpret curves as straight or not-straight (#838).\n- Switch blendMode to 'lighter' in Candy Crash example for better performance\n  (#453).\n- Don't block touch actions when using paper in JavaScript mode (#686).\n- Convert touch event coordinates to project coordinates (#633).\n- Fix exceptions when a top-level layer is selected.\n- Don't allow layers to turn up in hit-tests (#608).\n- Maintain `Raster#source` correctly on Node.js (#914).\n- Boolean operations correctly handle open `Path` items within `CompoundPath`\n  (#912).\n- Don't modify an array of child items passed to `CompoundPath#insertChildren()`\n  when it is a child items array of a `CompoundPath`.\n- Correctly handle `#strokeScaling` in `Shape` hit-tests (#697).\n- Support clip-masks in hit-testing (#671).\n- Fix incorrect `#hitTest()` and `#contains()` cases (#819, #884).\n- Update documentation to note appropriate use for `#simplify()` (#920).\n- `#importSVG()` now supports percentage dimensions and\n  `gradientUnits=\"objectBoundingBox\"`. (#954, #650).\n- `Group` items with clip-masks now calculate correct bounding boxes (#956).\n- Calling `event.stopPropagation()` in `'mousedown'` handler no longer prevents\n  `'mousedrag'` events (#952).\n- Draw `Item` shadows when `#shadowBlur` is zero (#955).\n- Fixes for web site examples (#967).\n- Prevent `Item` bounds from permanently collapsing to 0 when applying non-\n  invertible transformations (#558).\n- Scaling shadows now works correctly with browser- and view-zoom (#831).\n- `Path#arcTo()` correctly handles zero sizes.\n- `#importSVG()` handles `onLoad` and `onError` callbacks for string inputs that\n  load external resources (#827).\n- `#importJSON()` and `#exportJSON()` now handle non-`Item` objects correctly\n  (#392).\n- `#exportSVG()` now exports empty paths if used as a clip-mask.\n- `#importJSON()` no longer generates callstack exceeded exceptions (#764).\n- Fix problems with group selection structures after `Group#importJSON()`\n  (#785).\n- Fix an issue in `Item#importJSON()` where `#parent` is `null` when calling it\n  on existing, already inserted items (#1041).\n- Correct issue when using paper-core in Node.js (#975).\n- Fix `event.delta` on mousedrag events (#981).\n- Improve handling of XML attribute namespaces for IE's XMLSerializer() (#984).\n- Make sure `Item#removeChildren()` fully removes children (#991).\n- Improve handling of event propagation on `View` and `Item` (#995).\n- `#importSVG()`: Improve handling of viewBox.\n- Make sure all named item lookup structures are kept in sync (#1009).\n- Convert absolute local gradient URLs to relative ones (#1001).\n- Fix TypeError in `Path#unite()` (#1000).\n- Allow the selection of a `Path` item's bounds without selecting the segments\n  (#769).\n- Fix wrong indices in `Item#insertChildren()`, when inserting children that\n  were previously inserted in the same parent (#1015).\n- Add capability to `PathItem#closePath()` to handle imprecise SVG data due to\n  rounding (#1045).\n- Improve reliability of fat-line clipping for curves that are very similar\n  (#904).\n- Improve precision of `Numerical.solveQuadratic()` and\n  `Numerical.solveCubic()` for edge-cases (#1085).\n\n### Removed\n\n- Canvas attributes \"resize\" and \"data-paper-resize\" no longer cause paper to\n  resize the canvas when the viewport size changes; Additional CSS styles are\n  required since `0.9.22`, e.g.:\n  \n  ```css\n  /* Scale canvas with resize attribute to full size */\n  canvas[resize] {\n      width: 100%;\n      height: 100%;\n  }\n  ```\n- Legacy `Color` constructors (removed in `0.9.25`): `GrayColor`, `RgbColor`,\n  `HsbColor`, `HslColor`, and `GradientColor`. These have been replaced\n   with corresponding forms of the `Color` constructor.\n- Undocumented function `Project#addChild()` that added a layer to a project.\n  It is replaced by `Project#addLayer()` and `Project#insertLayer()`.\n\n### Deprecated\n\n- `#windingRule` on `Item` and `Style` → `#fillRule`\n- `Curve#getNormalAt(time, true)` → `#getNormalAtTime(true)`\n- `Curve#divide()` → `#divideAt(offset)` / `#divideAtTime(time)`\n- `Curve#split()` → `#splitAt(offset)` / `#splitAtTime(time)`\n- `Curve#getParameterAt(offset)` → `#getTimeAt(offset)`\n- `Curve#getParameterOf(point)` → `getTimeOf(point)`\n- `Curve#getPointAt(time, true)` → `#getPointAtTime(time)`\n- `Curve#getTangentAt(time, true)` → `#getTangentAtTime(time)`\n- `Curve#getNormalAt(time, true)` → `#getNormalAtTime(time)`\n- `Curve#getCurvatureAt(time, true)` → `#getCurvatureAtTime(time)`\n- `CurveLocation#parameter` → `#time`\n- `Path#split(offset/location)` → `#splitAt(offset/location)`\n- `Symbol` → `SymbolDefinition`\n- `PlacedSymbol` → `SymbolItem`\n- `Symbol#definition` → `SymbolDefinition#item`\n- `PlacedSymbol#symbol` → `SymbolItem#definition`\n- `Project#symbols` → `#symbolDefinitions`\n- `Matrix#concatenate` → `#append`\n- `Matrix#preConcatenate` → `#prepend`\n- `Matrix#chain` → `#appended`\n- `GradientStop#rampPoint` → `#offset`\n"
  },
  {
    "path": "third_party/paper/LICENSE.txt",
    "content": "Copyright (c) 2011 - 2019, Juerg Lehni & Jonathan Puckey\nhttp://scratchdisk.com/ & https://puckey.studio/\nAll rights reserved.\n\nThe MIT License (MIT)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "third_party/paper/README.md",
    "content": "# Paper.js - The Swiss Army Knife of Vector Graphics Scripting [![Build Status](https://travis-ci.org/paperjs/paper.js.svg?branch=develop)](https://travis-ci.org/paperjs/paper.js) [![NPM](https://img.shields.io/npm/v/paper.svg)](https://www.npmjs.com/package/paper)\n\nIf you want to work with Paper.js, simply download the latest \"stable\" version\nfrom [http://paperjs.org/download/](http://paperjs.org/download/)\n\n- Website: <http://paperjs.org/>\n- Questions: <https://stackoverflow.com/questions/tagged/paperjs>\n- Discussion forum: <https://groups.google.com/group/paperjs>\n- Mainline source code: <https://github.com/paperjs/paper.js>\n- Twitter: [@paperjs](https://twitter.com/paperjs)\n- Latest releases: <http://paperjs.org/download/>\n- Pre-built development versions:\n  [`prebuilt/module`](https://github.com/paperjs/paper.js/tree/prebuilt/module)\n  and [`prebuilt/dist`](https://github.com/paperjs/paper.js/tree/prebuilt/dist)\n  branches.\n\n## Installing Paper.js\n\nThe recommended way to install and maintain Paper.js as a dependency in your\nproject is through the [Node.js Package Manager (NPM)](https://www.npmjs.com/)\nfor browsers, Node.js or Electron.\n\nIf NPM is already installed, simply type one of these commands in your project\nfolder:\n\n```sh\nnpm install paper\n```\n\nUpon execution, you will find a `paper` folder inside the project's\n`node_modules` folder.\n\nFor more information on how to install Node.js and NPM, read the chapter\n[Installing Node.js and NPM](#installing-nodejs-and-npm).\n\n### Which Version to Use\n\nThe various distributions come with two different pre-build versions of\nPaper.js, in minified and normal variants:\n\n- `paper-full.js` – The full version for the browser, including PaperScript\n  support and Acorn.js\n- `paper-core.js` – The core version for the browser, without PaperScript\n  support nor Acorn.js. You can use this to shave off some bytes and compilation\n  time when working with JavaScript directly.\n\n### Installing Node.js and NPM\n\nNode.js comes with the Node Package Manager (NPM). There are many tutorials\nexplaining the different ways to install Node.js on different platforms. It is\ngenerally not recommended to install Node.js through OS-supplied package\nmanagers, as the its development cycles move fast and these versions are often\nout-of-date.\n\nOn macOS, [Homebrew](https://brew.sh/) is a good option if one version of\nNode.js that is kept up to date with `brew upgrade` is enough:  \n<https://treehouse.github.io/installation-guides/mac/node-mac.html>\n\n[NVM](https://github.com/creationix/nvm) can be used instead to install and\nmaintain multiple versions of Node.js on the same platform, as often required by\ndifferent projects:  \n<https://nodesource.com/blog/installing-node-js-tutorial-using-nvm-on-mac-os-x-and-ubuntu/>\n\nHomebrew is recommended on macOS also if you intend to install Paper.js with\nrendering to the Canvas on Node.js, as described in the next paragraph.\n\nFor Linux, see <https://nodejs.org/download/> to locate 32-bit and 64-bit Node.js\nbinaries as well as sources, or use NVM, as described in the paragraph above.\n\n### Installing Paper.js Using NPM\n\nPaper.js comes in three different versions on NPM: `paper`, `paper-jsdom` and\n`paper-jsdom-canvas`. Depending on your use case, you need to required a\ndifferent one:\n\n- `paper` is the main library, and can be used directly in a browser\n  context, e.g. a web browser or worker.\n- `paper-jsdom` is a shim module for Node.js, offering headless use with SVG\n  importing and exporting through [jsdom](https://github.com/tmpvar/jsdom).\n- `paper-jsdom-canvas` is a shim module for Node.js, offering canvas rendering\n  through [Node-Canvas](https://github.com/Automattic/node-canvas) as well as\n  SVG importing and exporting through [jsdom](https://github.com/tmpvar/jsdom).\n\nIn order to install `paper-jsdom-canvas`, you need the [Cairo Graphics\nlibrary](https://cairographics.org/) installed in your system:\n\n### Installing Native Dependencies\n\nPaper.js relies on [Node-Canvas](https://github.com/Automattic/node-canvas) for\nrendering, which in turn relies on the native libraries\n[Cairo](https://cairographics.org/) and [Pango](https://www.pango.org/).\n\n#### Installing Native Dependencies on macOS\n\nPaper.js relies on Node-Canvas for rendering, which in turn relies on Cairo and\nPango. The easiest way to install Cairo is through\n[Homebrew](https://brew.sh/), by issuing the command:\n\n    brew install cairo pango\n\nNote that currently there is an issue on macOS with Cairo. If the above causes\nerrors, the following will most likely fix it:\n\n    PKG_CONFIG_PATH=/opt/X11/lib/pkgconfig/ npm install paper\n\nAlso, whenever you would like to update the modules, you will need to execute:\n\n    PKG_CONFIG_PATH=/opt/X11/lib/pkgconfig/ npm update\n\nIf you keep forgetting about this requirement, or would like to be able to type\nsimple and clean commands, add this to your `.bash_profile` file:\n\n    # PKG Config for Pango / Cairo\n    export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/opt/X11/lib/pkgconfig\n\nAfter adding this line, your commands should work in the expected way:\n\n    npm install paper\n    npm update\n\n#### Installing Native Dependencies on Debian/Ubuntu Linux\n\n    sudo apt-get install pkg-config libcairo2-dev libpango1.0-dev libssl-dev libjpeg62-dev libgif-dev\n\nYou might also need to install the build-essential package if you don't usually\nbuild from c++ sources:\n\n    sudo apt-get install build-essential\n\n#### Installing Native Dependencies for Electron\n\nIn order to build Node-Canvas for use of `paper-jsdom-canvas` in Electron, which\nis likely to use a different version of V8 than the Node binary installed in\nyour system, you need to manually specify the location of Electron’s headers.\nFollow these steps to do so:\n\n[Electron — Using Native Node\nModules](https://electron.atom.io/docs/tutorial/using-native-node-modules/)\n\n#### After Native Dependencies have been installed\n\nYou should now be able to install the Paper.js module with jsdom and Canvas\nrendering from NPM:\n\n    npm install paper-jsdom-canvas\n\n## Development\n\nThe main Paper.js source tree is hosted on\n[GitHub](https://github.com/paperjs/paper.js/). `git` is required to create a\nclone of the repository, and can be easily installed through your preferred\npackage manager on your platform.\n\n### Get the Source\n\n    git clone --recursive git://github.com/paperjs/paper.js.git\n    cd paper.js\n\nTo refresh your clone and fetch changes from origin, run:\n\n    git fetch origin\n\nTo update the `jsdoc-toolkit` submodule, used to generate the documentation,\nrun:\n\n    git submodule update  --init --recursive\n\n### Setting Up For Building\n\nAs of 2016, Paper.js uses [Gulp.js](https://gulpjs.com/) for building, and has a\ncouple of dependencies as NPM modules. Read the chapter [Installing\nNode.js and NPM](#installing-nodejs-and-npm) if you still need to\ninstall these.\n\nIn order to be able to build Paper.js, after checking out the repository, paper\nhas dependencies that need to be installed. Install them by issuing the\nfollowing commands from the Paper.js directory:\n\n    npm install\n\nIt is also recommended to install Gulp.js globally, so you can easier execute\nthe build commands from anywhere in the command line:\n\n    npm install -g gulp\n\n### Building the Library\n\nThe Paper.js sources are distributed across many separate files, organised in\nsubfolders inside the `src` folder. To compile them all into distributable\nfiles, you can run the `build` task:\n\n    gulp build\n\nYou will then find the built library files inside the `dist` folder, named\n`paper-full.js` and `paper-core.js`, along with their minified versions. Read\nmore about this in [Which Version to Use?](#which-version-to-use).\n\n### Running Directly from Separate Source Files\n\nAs a handy alternative to building the library after each change to try it out\nin your scripts, there is the `load` task, that replaces the built libraries\nwith symbolic links to the `scrc/load.js` script. This script then load the\nlibrary directly from all the separate source files in the `src` folder, through\nthe [Prepro.js](https://github.com/lehni/prepro.js) JavaScript preprocessing\nlibrary.\n\nThis means you can switch between loading from sources and loading a built\nlibrary simply by running.\n\n    gulp load\n\nAnd to go back to a built library\n\n    gulp build\n\nNote that your PaperScripts examples do not need to change, they can keep\nloading `dist/paper-full.js`, which will always do the right thing. Note also\nthat `src/load.js` handles both browsers and Node.js, as supported by Prepro.js.\n\n### Other Build Tasks\n\nCreate a final zipped distribution file inside the `dist` folder:\n\n    gulp dist\n\nAnd since `dist` is the default task, this is the same:\n\n    gulp\n\n### Branch structure\n\nSince the release of version `0.9.22`, Paper.js has adopted aspects of the Git-\nFlow workflow. All development is taking place in the\n[`develop`](https://github.com/paperjs/paper.js/tree/develop) branch, which is\nonly merged into [`master`](https://github.com/paperjs/paper.js/tree/master)\nwhen a new release occurs.\n\nAs of version `0.9.26`, the `dist` folder is excluded on all branches, and the\nbuilding is now part of the `npm publish` process by way of the `prepublish`\nscript.\n\nWe also offer prebuilt versions of the latest state of the `develop` branch on\n[`prebuilt/module`](https://github.com/paperjs/paper.js/tree/prebuilt/module)\nand [`prebuilt/dist`](https://github.com/paperjs/paper.js/tree/prebuilt/dist).\n\n### Building the Documentation\n\nSimilarly to building the library, you can run the `docs` task to build the\ndocumentation:\n\n    gulp docs\n\nYour docs will then be located at `dist/docs`.\n\n### Testing\n\nPaper.js was developed and tested from day 1 using proper unit testing through\njQuery's [Qunit](https://qunitjs.com/). To run the tests after any\nchange to the library's source, simply open `index.html` inside the `test`\nfolder in your web browser. There should be a green bar at the top, meaning all\ntests have passed. If the bar is red, some tests have not passed. These will be\nhighlighted and become visible when scrolling down.\n\nIf you are testing on Chrome, some of the tests will fail due to the browser's\nCORS restrictions. In order to run the browser based tests on Chrome, you need\nto run a local web-server through Gulp.js. The following command will handle it\nfor you, and will also open the browser at the right address straight away:\n\n    gulp test:browser\n\nYou can also run the unit tests through PhantomJS in Gulp directly on the\ncommand line:\n\n    gulp test:phantom\n\nTo test the Node.js version of Paper.js, use this command:\n\n    gulp test:node\n\nAnd to test both the PhantomJS and Node.js environments together, simply run:\n\n    gulp test\n\n### Contributing [![Open Source Helpers](https://www.codetriage.com/paperjs/paper.js/badges/users.svg)](https://www.codetriage.com/paperjs/paper.js)\n\nThe main Paper.js source tree is hosted on GitHub, thus you should create a fork\nof the repository in which you perform development. See\n<https://help.github.com/articles/fork-a-repo/>.\n\nWe prefer that you send a\n[pull request on GitHub](https://help.github.com/articles/about-pull-requests/) \nwhich will then be merged into the official main line repository.\nYou need to sign the Paper.js CLA to be able to contribute (see below).\n\nAlso, in your first contribution, add yourself to the end of `AUTHORS.md` (which\nof course is optional).\n\nIn addition to contributing code you can also triage issues which may include\nreproducing bug reports or asking for vital information, such as version numbers\nor reproduction instructions. If you would like to start triaging issues, one\neasy way to get started is to\n[subscribe to paper.js on CodeTriage](https://www.codetriage.com/paperjs/paper.js).\n\n**Get the source (for contributing):**\n\nIf you want to contribute to the project you will have to [make a\nfork](https://help.github.com/articles/fork-a-repo/). Then do this:\n\n    git clone --recursive git@github.com:yourusername/paper.js.git\n    cd paper.js\n    git remote add upstream git://github.com/paperjs/paper.js.git\n\nTo then fetch changes from upstream, run\n\n    git fetch upstream\n\n#### Creating and Submitting a Patch\n\nAs mentioned above, we prefer that you send a\n[pull request](https://help.github.com/articles/about-pull-requests/) on GitHub:\n\n1. Create a fork of the upstream repository by visiting\n   <https://github.com/paperjs/paper.js/fork>. If you feel insecure, here's a\n   great guide: <https://help.github.com/articles/fork-a-repo/>\n\n2. Clone of your repository: `git clone\n   https://yourusername@github.com/yourusername/paper.js.git`\n\n3. This is important: Create a so-called *topic branch* based on the `develop`\n   branch: `git checkout -tb name-of-my-patch develop` where `name-of-my-patch`\n   is a short but descriptive name of the patch you're about to create. Don't\n   worry about the perfect name though -- you can change this name at any time\n   later on.\n\n4. Hack! Make your changes, additions, etc., commit them then push them to your\n   GitHub fork: `git push origin name-of-my-patch`\n\n5. Send a pull request to the upstream repository's owner by visiting your\n   repository's site at GitHub (i.e. https://github.com/yourusername/paper.js)\n   and press the \"Pull Request\" button. Make sure you are creating the pull\n   request to the `develop` branch, not the `master` branch. Here's a good guide\n   on pull requests: <https://help.github.com/articles/about-pull-requests/>\n\n##### Use one topic branch per feature:\n\nDon't mix different kinds of patches in the same branch. Instead, merge them all\ntogether into your `develop` branch (or develop everything in your `develop`\nbranch and then cherry-pick-and-merge into the different topic branches). Git\nprovides for an extremely flexible workflow, which in many ways causes more\nconfusion than it helps you when new to collaborative software development. The\nguides provided by GitHub at <https://help.github.com/> are a really good\nstarting point and reference. If you are fixing an issue, a convenient way to\nname the branch is to use the issue number as a prefix, like this: `git checkout\n-tb issue-937-feature-add-text-styling`.\n\n#### Contributor License Agreement\n\nBefore we can accept any contributions to Paper.js, you need to sign this\n[CLA](https://en.wikipedia.org/wiki/Contributor_License_Agreement):\n\n[Contributor License Agreement](https://spreadsheets.google.com/a/paperjs.org/spreadsheet/embeddedform?formkey=dENxd0JBVDY2REo3THVuRmh4YjdWRlE6MQ)\n\n> The purpose of this agreement is to clearly define the terms under which\n> intellectual property has been contributed to Paper.js and thereby allow us to\n> defend the project should there be a legal dispute regarding the software at\n> some future time.\n\nFor a list of authors and contributors, please see \n[AUTHORS](https://github.com/paperjs/paper.js/blob/master/AUTHORS.md).\n\n## License\n\nDistributed under the MIT license. See \n[LICENSE](https://github.com/paperjs/paper.js/blob/master/LICENSE.txt)\nfo details.\n"
  },
  {
    "path": "third_party/paper/package.json",
    "content": "{\n  \"name\": \"paper\",\n  \"version\": \"0.12.4\",\n  \"description\": \"The Swiss Army Knife of Vector Graphics Scripting\",\n  \"license\": \"MIT\",\n  \"homepage\": \"http://paperjs.org\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/paperjs/paper.js\"\n  },\n  \"bugs\": \"https://github.com/paperjs/paper.js/issues\",\n  \"contributors\": [\"Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)\", \"Jonathan Puckey <jonathan@studiomoniker.com> (http://studiomoniker.com)\"],\n  \"main\": \"dist/paper-full.js\",\n  \"types\": \"dist/paper.d.ts\",\n  \"scripts\": {\n    \"build\": \"gulp build\",\n    \"dist\": \"gulp dist\",\n    \"zip\": \"gulp zip\",\n    \"docs\": \"gulp docs\",\n    \"load\": \"gulp load\",\n    \"jshint\": \"gulp jshint\",\n    \"test\": \"gulp test\"\n  },\n  \"files\": [\"AUTHORS.md\", \"CHANGELOG.md\", \"dist/\", \"examples/\", \"LICENSE.txt\", \"README.md\"],\n  \"engines\": {\n    \"node\": \">=8.0.0\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"gulp jshint --ensure-branch develop\",\n      \"pre-push\": \"gulp test --ensure-branch develop\"\n    }\n  },\n  \"browser\": {\n    \"canvas\": false,\n    \"jsdom\": false,\n    \"jsdom/lib/jsdom/living/generated/utils\": false,\n    \"source-map-support\": false,\n    \"./dist/node/self.js\": false,\n    \"./dist/node/extend.js\": false\n  },\n  \"devDependencies\": {\n    \"acorn\": \"~0.5.0\",\n    \"canvas\": \"^2.4.1\",\n    \"del\": \"^4.1.0\",\n    \"gulp\": \"^3.9.1\",\n    \"gulp-cached\": \"^1.1.0\",\n    \"gulp-git-streamed\": \"^2.8.1\",\n    \"gulp-jshint\": \"^2.1.0\",\n    \"gulp-json-editor\": \"^2.5.2\",\n    \"gulp-prepro\": \"^2.4.0\",\n    \"gulp-qunits\": \"^2.1.2\",\n    \"gulp-rename\": \"^1.4.0\",\n    \"gulp-shell\": \"^0.7.0\",\n    \"gulp-symlink\": \"^2.1.4\",\n    \"gulp-uglify\": \"^1.5.4\",\n    \"gulp-uncomment\": \"^0.3.0\",\n    \"gulp-util\": \"^3.0.7\",\n    \"gulp-webserver\": \"^0.9.1\",\n    \"gulp-whitespace\": \"^0.1.0\",\n    \"gulp-zip\": \"^3.2.0\",\n    \"husky\": \"^2.3.0\",\n    \"jsdom\": \"^15.1.1\",\n    \"jshint\": \"^2.10.2\",\n    \"jshint-summary\": \"^0.4.0\",\n    \"merge-stream\": \"^2.0.0\",\n    \"minimist\": \"^1.2.0\",\n    \"mustache\": \"^3.0.1\",\n    \"prepro\": \"^2.4.0\",\n    \"qunitjs\": \"^1.23.0\",\n    \"require-dir\": \"^1.2.0\",\n    \"resemblejs\": \"^3.1.0\",\n    \"run-sequence\": \"^2.2.1\",\n    \"source-map-support\": \"^0.5.12\",\n    \"stats.js\": \"0.17.0\",\n    \"straps\": \"^3.0.1\",\n    \"typescript\": \"^3.1.6\"\n  },\n  \"keywords\": [\"vector\", \"graphic\", \"graphics\", \"2d\", \"geometry\", \"bezier\", \"curve\", \"curves\", \"path\", \"paths\", \"canvas\", \"svg\", \"paper\", \"paper.js\", \"paperjs\"]\n}\n"
  },
  {
    "path": "utils/colorUtils.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nimport {MathUtils} from './mathUtils';\nimport * as paper from 'paper';\n\nexport class Palette {\n    constructor(colors) {\n        let scope = paper.default;\n        this.colors = colors.map(c => ({\n            light: new scope.Color(c[0]).convert('hsb'),\n            dark: new scope.Color(c[1]).convert('hsb'),\n        }));\n    }\n\n    select(variance) {\n        let scope = paper.default;\n        let pair = this.colors[Math.floor(Math.random() * this.colors.length)];\n        let varColor = (c) => new scope.Color({\n            hue: c.hue + 360 * MathUtils.gaussian(0, variance),\n            saturation: c.saturation + MathUtils.gaussian(0, variance),\n            brightness: c.brightness + MathUtils.gaussian(0, variance),\n        });\n        return {\n            light: varColor(pair.light),\n            dark: varColor(pair.dark),\n        };\n    }\n}\n\nexport class ColorUtils {\n    static addRGB(color, red, green, blue) {\n        color.red = color.red + red;\n        color.green = color.green + green;\n        color.blue = color.blue + blue;\n    }\n\n    static lerp(color0, color1, amt) {\n        return new paper.default.Color(\n            MathUtils.lerp(color0.red, color1.red, amt),\n            MathUtils.lerp(color0.green, color1.green, amt),\n            MathUtils.lerp(color0.blue, color1.blue, amt),\n        );\n    }\n\n    // Generates random color from string hash.\n    static fromStringHash(str) {\n        // Compute hash from string\n        // Source http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/\n        var hash = 0, i, chr;\n        for (i = 0; i < str.length; i++) {\n        chr   = str.charCodeAt(i);\n        hash  = ((hash << 5) - hash) + chr;\n        hash |= 0; // Convert to 32bit integer\n        }\n        // Hash to rgb color.\n        let r = hash & 255;\n        let g = (hash & (255 << 8)) >> 8;\n        let b = (hash & (255 << 16)) >> 16;\n        return new paper.default.Color(r / 255, g / 255, b / 255);\n    }\n}\n"
  },
  {
    "path": "utils/demoUtils.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nimport * as posenet from '@tensorflow-models/posenet';\nimport * as tf from '@tensorflow/tfjs';\n\nconst color = 'aqua';\nconst boundingBoxColor = 'red';\nconst lineWidth = 2;\n\nfunction isAndroid() {\n  return /Android/i.test(navigator.userAgent);\n}\n\nfunction isiOS() {\n  return /iPhone|iPad|iPod/i.test(navigator.userAgent);\n}\n\nexport function isMobile() {\n  return isAndroid() || isiOS();\n}\n\nfunction setDatGuiPropertyCss(propertyText, liCssString, spanCssString = '') {\n  var spans = document.getElementsByClassName('property-name');\n  for (var i = 0; i < spans.length; i++) {\n    var text = spans[i].textContent || spans[i].innerText;\n    if (text == propertyText) {\n      spans[i].parentNode.parentNode.style = liCssString;\n      if (spanCssString !== '') {\n        spans[i].style = spanCssString;\n      }\n    }\n  }\n}\n\n/**\n * Toggles between the loading UI and the main canvas UI.\n */\nexport function toggleLoadingUI(\n    showLoadingUI, loadingDivId = 'loading', mainDivId = 'main') {\n  if (showLoadingUI) {\n    document.getElementById(loadingDivId).style.display = 'block';\n    document.getElementById(mainDivId).style.display = 'none';\n  } else {\n    document.getElementById(loadingDivId).style.display = 'none';\n    document.getElementById(mainDivId).style.display = 'block';\n  }\n}\n\nfunction toTuple({y, x}) {\n  return [y, x];\n}\n\nexport function drawPoint(ctx, y, x, r, color) {\n  ctx.beginPath();\n  ctx.arc(x, y, r, 0, 2 * Math.PI);\n  ctx.fillStyle = color;\n  ctx.fill();\n}\n\n/**\n * Draws a line on a canvas, i.e. a joint\n */\nexport function drawSegment([ay, ax], [by, bx], color, scale, ctx) {\n  ctx.beginPath();\n  ctx.moveTo(ax * scale, ay * scale);\n  ctx.lineTo(bx * scale, by * scale);\n  ctx.lineWidth = lineWidth;\n  ctx.strokeStyle = color;\n  ctx.stroke();\n}\n\n/**\n * Draws a pose skeleton by looking up all adjacent keypoints/joints\n */\nexport function drawSkeleton(keypoints, minConfidence, ctx, scale = 1) {\n  const adjacentKeyPoints =\n      posenet.getAdjacentKeyPoints(keypoints, minConfidence);\n\n  adjacentKeyPoints.forEach((keypoints) => {\n    drawSegment(\n        toTuple(keypoints[0].position), toTuple(keypoints[1].position), color,\n        scale, ctx);\n  });\n}\n\n/**\n * Draw pose keypoints onto a canvas\n */\nexport function drawKeypoints(keypoints, minConfidence, ctx, scale = 1) {\n  for (let i = 0; i < keypoints.length; i++) {\n    const keypoint = keypoints[i];\n\n    if (keypoint.score < minConfidence) {\n      continue;\n    }\n\n    const {y, x} = keypoint.position;\n    drawPoint(ctx, y * scale, x * scale, 3, color);\n  }\n}\n\n/**\n * Draw an image on a canvas\n */\nexport function renderImageToCanvas(image, size, canvas) {\n  canvas.width = size[0];\n  canvas.height = size[1];\n  const ctx = canvas.getContext('2d');\n\n  ctx.drawImage(image, 0, 0);\n}\n\nexport function setStatusText(text) {\n  const resultElement = document.getElementById('status');\n  resultElement.innerText = text;\n}\n"
  },
  {
    "path": "utils/fileUtils.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nexport class FileUtils {\n    static setDragDropHandler(handler) {\n        window.addEventListener(\"dragover\", function (e) {\n            e = e || event;\n            e.preventDefault();\n          }, false);\n          window.addEventListener(\"drop\", function (e) {\n            e = e || event;\n            e.preventDefault();\n            if (e.dataTransfer.items) {\n              let files = e.dataTransfer.items;\n              if (files.length < 1) {\n                return;\n              }\n              let reader = new FileReader();\n              reader.onload = (event) => {\n                handler(event.target.result);\n              }\n              reader.readAsText(e.dataTransfer.files[0]);\n            }\n          }, false);\n    }\n}\n"
  },
  {
    "path": "utils/mathUtils.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nfunction getDistance(p0, p1) {\n    return Math.sqrt((p0.x - p1.x) * (p0.x - p1.x) + (p0.y - p1.y) * (p0.y - p1.y));\n}\n\nexport class MathUtils {\n    static lerp(v0, v1, perc) {\n        return v0 + (v1 - v0) * perc;\n    }\n\n    static random(v0, v1) {\n        return v0 + Math.random() * (v1 - v0);\n    }\n\n    static smoothStep(v, min, max) {\n        var x = Math.max(0, Math.min(1, (v-min)/(max-min)));\n        return x*x*(3 - 2*x);\n    }\n\n    // Generate a transform function of p in the coordinate system defined by p0 and p1.\n    static getTransformFunc(p0, p1, p) {\n        let d = p1.subtract(p0);\n        let dir = d.normalize();\n        let l0 = d.length;\n        let n = dir.clone();\n        n.angle += 90;\n        let v = p.subtract(p0);\n        let x = v.dot(dir);\n        let y = v.dot(n);\n        return (p0New, p1New) => {\n            let d = p1New.subtract(p0New);\n            if (d.length === 0) {\n                return p0New.clone();\n            }\n            let scale = d.length / l0;\n            let dirNew = d.normalize();\n            let nNew = dirNew.clone();\n            nNew.angle += 90;\n            return p0New.add(dirNew.multiply(x * scale)).add(nNew.multiply(y * scale));\n        }\n    }\n\n    static getClosestPointOnSegment(p0, p1, p) {\n        let d = p1.subtract(p0);\n        let c = p.subtract(p0).dot(d) / (d.dot(d));\n        if (c >= 1) {\n            return p1.clone();\n        } else if (c <= 0) {\n            return p0.clone();\n        } else {\n            return p0.add(d.multiply(c));\n        }\n    }\n\n    // Check if v0 and v1 are collinear.\n    // Returns true if cosine of the angle between v0 and v1 is within threshold to 1.\n    static isCollinear(v0, v1, threshold = 0.01) {\n        let colinear = false;\n        if (v0 && v1) {\n            let n0 = v0.normalize();\n            let n1 = v1.normalize();\n            colinear = Math.abs(n0.dot(n1)) > 1 - threshold;\n        }\n        return colinear;\n    }\n\n    static gaussian(mean, variance) {\n        var u = 0, v = 0;\n        while(u === 0) u = Math.random(); //Converting [0,1) to (0,1)\n        while(v === 0) v = Math.random();\n        let value = Math.sqrt( -2.0 * Math.log( u ) ) * Math.cos( 2.0 * Math.PI * v );\n        return value * variance + mean;\n    }\n\n    static clamp(v, minV, maxV) {\n        return Math.min(Math.max(v, minV), maxV);\n    }\n\n    static selectSegments(selectPerc, count, selectVar, segVar) {\n        let segments = [];\n        let totalSeg = 0;\n        for (let i = 0; i < count; i++) {\n            let seg = MathUtils.gaussian(1, segVar);\n            segments.push(seg);\n            totalSeg += seg;\n        }\n        for (let i = 0; i < segments.length; i++) {\n            segments[i] = segments[i] / totalSeg;\n        }\n        let cursor = 0;\n        let selected = [];\n        for (let i = 0; i < count; i++) {\n            let s0 = cursor;\n            let s1 = cursor + segments[i] * MathUtils.clamp(MathUtils.gaussian(1, selectVar) * selectPerc, 0, 1);\n            selected.push([s0, s1]);\n            cursor += segments[i];\n        }\n        return selected;\n    }\n\n    static isLeft(p0, p1, p){\n        return ((p1.x - p0.x)*(p.y - p0.y) - (p1.y - p0.y)*(p.x - p0.x)) > 0;\n   }\n\n    static packCircles(center, radius, seedCount, maxR, minR, maxIter = 10) {\n        let circles = [];\n        let iterCount = 0;\n        while (circles.length < seedCount && iterCount < maxIter) {\n            while (circles.length < seedCount) {\n                let c = {\n                    x: radius * (Math.random() * 2 - 1) + center.x,\n                    y: radius * (Math.random() * 2 - 1) + center.y,\n                };\n                if (getDistance(c, center) > radius || circles.some(circle => getDistance(circle, c) < circle.radius)) continue;\n                circles.push({\n                    c: c,\n                    r: 0,\n                });\n            }\n            let growthIterCount = 20;\n            let intersects = (c0, c1) => {\n                let d = getDistance(c0.c, c1.c);\n                return (d < c0.r + c1.r) && (d > Math.abs(c0.r - c1.r));\n            }\n            let bound = {\n                c: center,\n                r: radius,\n            };\n            for (let i = 0; i < growthIterCount; i++) {\n                let grew = false;\n                circles.forEach(s => {\n                    let intersecting = circles.some(other => (s !== other) && (intersects(s, other) || intersects(s, bound)));\n                    if (!intersecting && s.r < maxR) {\n                        s.r += maxR / growthIterCount;\n                        grew = true;\n                    }\n                });\n                if (!grew) break;\n            }\n            circles = circles.filter(c => c.r >= minR);\n            iterCount++;\n        }\n        return circles;\n    }\n}\n\nclass KeySpline {\n    constructor(mX1, mY1, mX2, mY2) {\n        this.mX1 = mX1;\n        this.mY1 = mY1;\n        this.mX2 = mX2;\n        this.mY2 = mY2;\n    }\n\n    get(aX) {\n        if (this.mX1 == this.mY1 && this.mX2 == this.mY2) return aX; // linear\n        return this.CalcBezier(this.GetTForX(aX), this.mY1, this.mY2);\n    }\n    \n    A( aA1,  aA2)  { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }\n    B( aA1,  aA2)  { return 3.0 * aA2 - 6.0 * aA1; }\n    C( aA1)  { return 3.0 * aA1; }\n    \n    // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.\n    CalcBezier( aT,  aA1,  aA2)  {\n        return ((this.A(aA1, aA2) * aT + this.B(aA1, aA2)) * aT + this.C(aA1)) * aT;\n    }\n    \n    // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.\n    GetSlope( aT,  aA1,  aA2)  {\n        return 3.0 * this.A(aA1, aA2) * aT * aT + 2.0 * this.B(aA1, aA2) * aT + this.C(aA1);\n    }\n    \n    GetTForX( aX)  {\n        // Newton raphson iteration\n        let aGuessT = aX;\n        for (let i = 0; i < 4; ++i) {\n            let currentSlope = this.GetSlope(aGuessT, this.mX1, this.mX2);\n            if (currentSlope == 0.0) return aGuessT;\n            let currentX = this.CalcBezier(aGuessT, this.mX1, this.mX2) - aX;\n            aGuessT -= currentX / currentSlope;\n        }\n        return aGuessT;\n    }\n};\n\nexport class MultiSpline {\n    constructor() {\n        this.keySplines = [];\n        this.segments = [];\n        this.x0 = 0;\n        this.y0 = 0;\n    }\n\n    add(mX1, mY1, mX2, mY2, x1, y1) {\n        let ks = new KeySpline(mX1, mY1, mX2, mY2);\n        let x0 = this.x0;\n        let y0 = this.y0;\n        if (this.segments.length) {\n            x0 = this.segments[this.segments.length - 1][1].x;\n            y0 = this.segments[this.segments.length - 1][1].y;\n        }\n        this.keySplines.push(ks);\n        this.segments.push([{x: x0, y: y0}, {x: x1, y: y1}]);\n    }\n\n    get(x) {\n        let index = -1;\n        for (let i = 0; i < this.segments.length; i++) {\n            if (x >= this.segments[i][0].x && x < this.segments[i][1].x) {\n                index = i;\n                break;\n            }\n        }\n        if (index < 0) {\n            return 0;\n        }\n        let seg = this.segments[index];\n        let ks = this.keySplines[index];\n        let perc = (x - seg[0].x) / (seg[1].x - seg[0].x);\n        if (index % 2 == 0) {\n            return MathUtils.lerp(seg[0].y, seg[1].y, ks.get(perc));\n        } else {\n            return MathUtils.lerp(seg[1].y, seg[0].y, ks.get(1 - perc));\n        }\n    }\n}\n"
  },
  {
    "path": "utils/svgUtils.js",
    "content": "/**\n * @license\n * Copyright 2020 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n * =============================================================================\n */\n\nimport * as paper from 'paper';\n\nexport class SVGUtils {\n    static importSVG(file) {\n        let svgScope = new paper.default.PaperScope();\n        let canvas = svgScope.createCanvas(0, 0);\n        svgScope.setup(canvas);\n        return new Promise((resolve, reject) => {\n            svgScope.project.importSVG(file, () => {\n                console.log('** SVG imported **');\n                resolve(svgScope);\n            }, (e) => {\n                console.log('** SVG improt error: ', e);\n                reject(svgScope);\n            });\n        })\n    }\n\n    static drawEllipse(p, va, vb, ctrlDA, ctrlDB, scope, options) {\n        let va1 = va.multiply(-1);\n        let vb1 = vb.multiply(-1);\n        let p0 = p.add(va);\n        let p1 = p.add(vb);\n        let p2 = p.add(va1);\n        let p3 = p.add(vb1);\n        let path = new scope.Path(options);\n        path.addSegment(p0, vb1.normalize().multiply(ctrlDB), vb.normalize().multiply(ctrlDB));\n        path.addSegment(p1, va.normalize().multiply(ctrlDA), va1.normalize().multiply(ctrlDA));\n        path.addSegment(p2, vb.normalize().multiply(ctrlDB), vb1.normalize().multiply(ctrlDB));\n        path.addSegment(p3, va1.normalize().multiply(ctrlDA), va.normalize().multiply(ctrlDA));\n        path.closePath();\n        return path;\n    }\n\n    static genPathWithSpline(path, spline, height, options, scope) {\n        let pathLen = path.length;\n        if (pathLen == 0) {\n            return path.clone();\n        }\n        let to = [];\n        let back = [];;\n        let segCount = Math.max(pathLen / 3, 1.0);\n        for (let i = 0; i < segCount; i++) {\n            let perc = i / (segCount - 1);\n            let p = path.getPointAt(perc * pathLen);\n            let n = path.getNormalAt(perc * pathLen);\n            let easeHeight = spline.get(perc);\n            if (!p || !n) continue;\n            let pp0 = p.add(n.multiply(height * easeHeight));\n            let pp1 = p.subtract(n.multiply(height * easeHeight));\n            to.push(pp0);\n            back.unshift(pp1);\n        }\n\n        let outPath = new scope.Path(options);\n        outPath.addSegments(to.concat(back));\n        outPath.simplify();\n        return outPath;\n    }\n\n    static isPath(item) {\n        return item.constructor === item.project._scope.Path;\n    }\n\n    static isShape(item) {\n        return item.constructor === item.project._scope.Shape;\n    }\n\n    static isGroup(item) {\n        return item.constructor === item.project._scope.Group;\n    }\n    \n    static findFirstItemWithPrefix(root, prefix) {\n        let items = root.getItems({ recursive: true });\n        for (let i = 0; i < items.length; i++) {\n            if (items[i].name && items[i].name.startsWith(prefix)) {\n            return items[i];\n            }\n        }\n        return null;\n    }\n}\n"
  }
]