├── modd.conf ├── .gitattributes ├── src ├── ui │ ├── ui.html │ ├── assets │ │ ├── loader.gif │ │ ├── check-circle.svg │ │ └── icons.tsx │ ├── theme │ │ ├── index.tsx │ │ ├── utils.tsx │ │ └── core.tsx │ ├── components │ │ ├── label.tsx │ │ ├── footer-links.tsx │ │ ├── selected-list.tsx │ │ ├── controls.tsx │ │ └── shared.tsx │ ├── ui.css │ ├── index.tsx │ ├── routes │ │ ├── selecting.tsx │ │ └── docs.tsx │ └── state.ts ├── __tests__ │ └── build.ts ├── types.ts ├── utils.ts └── main │ └── index.ts ├── assets └── Plugin │ ├── Plugin icon - 1.png │ └── file cover - 1.png ├── manifest.json ├── applescript.sh ├── jest.config.js ├── tsconfig.json ├── README.md ├── LICENSE ├── .gitignore ├── webpack.config.js ├── package.json └── dist └── plugin.js /modd.conf: -------------------------------------------------------------------------------- 1 | **dist/** { 2 | prep: ./applescript.sh 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/ui/ui.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /src/ui/assets/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steveruizok/figma-plugin-perfect-freehand/HEAD/src/ui/assets/loader.gif -------------------------------------------------------------------------------- /assets/Plugin/Plugin icon - 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steveruizok/figma-plugin-perfect-freehand/HEAD/assets/Plugin/Plugin icon - 1.png -------------------------------------------------------------------------------- /assets/Plugin/file cover - 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steveruizok/figma-plugin-perfect-freehand/HEAD/assets/Plugin/file cover - 1.png -------------------------------------------------------------------------------- /src/ui/theme/index.tsx: -------------------------------------------------------------------------------- 1 | import { styled, css, darkTheme, lightTheme } from "./core" 2 | 3 | export { styled, css, darkTheme, lightTheme } 4 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Perfect Freehand", 3 | "api": "1.0.0", 4 | "main": "dist/plugin.js", 5 | "ui": "dist/ui.html", 6 | "id": "950892731860805817" 7 | } 8 | -------------------------------------------------------------------------------- /applescript.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | osascript <<'EOF' 4 | tell application "Figma" to activate 5 | tell application "System Events" to tell process "Figma" 6 | keystroke "p" using {command down, option down} 7 | end tell 8 | EOF -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | }, 6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], 8 | }; 9 | -------------------------------------------------------------------------------- /src/ui/components/label.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "../theme" 2 | import * as React from "react" 3 | import * as _Label from "@radix-ui/react-label" 4 | 5 | export default function Label(props: _Label.LabelOwnProps) { 6 | return 7 | } 8 | 9 | const StyledLabel = styled(_Label.Root, { 10 | fontSize: "$0", 11 | fontFamily: "system-ui", 12 | }) 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "es2017"], 4 | "target": "es6", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "moduleResolution": "node", 9 | "jsx": "react", 10 | "noImplicitReturns": true, 11 | "noImplicitThis": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "**/*.test.ts", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /src/ui/assets/check-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/__tests__/build.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import manifest from '../../manifest.json'; 4 | 5 | describe('build script', () => { 6 | it('should create the plugin.js and ui.html files in the dist folder', async () => { 7 | const pluginFilePath = manifest.main; 8 | const uiFilePath = manifest.ui; 9 | 10 | // JS plugin file has been generated 11 | expect(fs.existsSync(`./${pluginFilePath}`)).toBeDefined(); 12 | 13 | // HTML plugin file has been generated (test only if it's declared in the manifest) 14 | if (uiFilePath) { 15 | expect(fs.existsSync(`./${uiFilePath}`)).toBeDefined(); 16 | } 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/ui/components/footer-links.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import state from "../state" 3 | import { styled } from "../theme" 4 | 5 | import { Button } from "../components/shared" 6 | 7 | export default function FooterLinks() { 8 | return ( 9 | 10 | 13 | 16 | 17 | ) 18 | } 19 | 20 | const Container = styled.div({ 21 | pt: "$0", 22 | px: "$2", 23 | display: "flex", 24 | justifyContent: "space-between", 25 | }) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect Freehand Figma Plugin 2 | 3 | A plugin for using [perfect-freehand](https://github.com/steveruizok/perfect-freehand) in Figma. 4 | 5 | ## Developing 6 | 7 | - Clone or download this repo. 8 | - Install dependencies (`npm install` or `yarn install`). 9 | - Start the development server (`npm run start` or `yarn start`). 10 | - Download the Figma Desktop App. 11 | - In the Menu, select _Plugins_, _Development_, _New Plugin..._. 12 | - Click the option to choose a `manifest.json` file. 13 | - Select the `manifest.json` from this repo. 14 | 15 | ## Cold Refreshing 16 | 17 | - In Figma, start the plugin using _Plugins_, _Play_. 18 | - In a separate Terminal tab, run `modd` to hot reload changes to Figma using [Modd](https://github.com/cortesi/modd). 19 | 20 | # Author 21 | 22 | - [@steveruizok](https://twitter.com/steveruizok) 23 | -------------------------------------------------------------------------------- /src/ui/ui.css: -------------------------------------------------------------------------------- 1 | /* Fonts */ 2 | @font-face { 3 | font-family: "Inter"; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url("https://rsms.me/inter/font-files/Inter-Regular.woff2") 7 | format("woff2"), 8 | url("https://rsms.me/inter/font-files/Inter-Regular.woff") format("woff"); 9 | } 10 | 11 | @font-face { 12 | font-family: "Inter"; 13 | font-style: medium; 14 | font-weight: 500; 15 | src: url("https://rsms.me/inter/font-files/Inter-Medium.woff2") 16 | format("woff2"), 17 | url("https://rsms.me/inter/font-files/Inter-Medium.woff") format("woff"); 18 | } 19 | 20 | @font-face { 21 | font-family: "Inter"; 22 | font-style: bold; 23 | font-weight: 600; 24 | src: url("https://rsms.me/inter/font-files/Inter-Bold.woff2") format("woff2"), 25 | url("https://rsms.me/inter/font-files/Inter-Bold.woff") format("woff"); 26 | } 27 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // UI actions 2 | export enum UIActionTypes { 3 | CLOSE = "CLOSE", 4 | ZOOM_TO_NODE = "ZOOM_TO_NODE", 5 | DESELECT_NODE = "DESELECT_NODE", 6 | TRANSFORM_NODES = "TRANSFORM_NODES", 7 | RESET_NODES = "RESET_NODES", 8 | UPDATED_OPTIONS = "UPDATED_OPTIONS", 9 | } 10 | 11 | export interface UIAction { 12 | type: UIActionTypes 13 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 14 | payload?: any 15 | } 16 | 17 | // Worker actions 18 | export enum WorkerActionTypes { 19 | SELECTED_NODES = "SELECTED_NODES", 20 | FOUND_SELECTED_NODES = "FOUND_SELECTED_NODES", 21 | } 22 | 23 | export interface WorkerAction { 24 | type: WorkerActionTypes 25 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 26 | payload?: any 27 | } 28 | 29 | export interface NodeInfo { 30 | id: string 31 | type: string 32 | name: string 33 | canReset: boolean 34 | } 35 | -------------------------------------------------------------------------------- /src/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ReactDOM from "react-dom" 3 | 4 | import { useStateDesigner } from "@state-designer/react" 5 | import state from "./state" 6 | 7 | import "./ui.css" 8 | 9 | import Selecting from "./routes/selecting" 10 | import Docs from "./routes/docs" 11 | 12 | import { styled } from "./theme" 13 | 14 | const Wrapper = styled.div({ 15 | position: "relative", 16 | width: "100%", 17 | overflow: "hidden", 18 | height: "100%", 19 | maxHeight: "100%", 20 | }) 21 | 22 | function App() { 23 | const local = useStateDesigner(state) 24 | 25 | React.useEffect(() => { 26 | return () => { 27 | // Important! Leaving this out might be causing a 28 | // memory leak when using modd for hot reload in dev. 29 | state.send("CLOSED_PLUGIN") 30 | } 31 | }, []) 32 | 33 | return ( 34 |
35 | 36 | {local.whenIn({ 37 | selectingNodes: , 38 | readingDocs: , 39 | })} 40 | 41 |
42 | ) 43 | } 44 | 45 | ReactDOM.render(, document.getElementById("react-page")) 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Aarón García Hervás 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_STORE 2 | dist/* 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # vuepress build output 72 | .vuepress/dist 73 | 74 | # Serverless directories 75 | .serverless 76 | 77 | # FuseBox cache 78 | .fusebox/ 79 | -------------------------------------------------------------------------------- /src/ui/theme/utils.tsx: -------------------------------------------------------------------------------- 1 | export default { 2 | m: () => (value: number | string) => ({ 3 | marginTop: value, 4 | marginBottom: value, 5 | marginLeft: value, 6 | marginRight: value, 7 | }), 8 | mt: () => (value: number | string) => ({ 9 | marginTop: value, 10 | }), 11 | mr: () => (value: number | string) => ({ 12 | marginRight: value, 13 | }), 14 | mb: () => (value: number | string) => ({ 15 | marginBottom: value, 16 | }), 17 | ml: () => (value: number | string) => ({ 18 | marginLeft: value, 19 | }), 20 | mx: () => (value: number | string) => ({ 21 | marginLeft: value, 22 | marginRight: value, 23 | }), 24 | my: () => (value: number | string) => ({ 25 | marginTop: value, 26 | marginBottom: value, 27 | }), 28 | p: () => (value: number | string) => ({ 29 | paddingTop: value, 30 | paddingBottom: value, 31 | paddingLeft: value, 32 | paddingRight: value, 33 | padding: value, 34 | }), 35 | pt: () => (value: number | string) => ({ 36 | paddingTop: value, 37 | }), 38 | pr: () => (value: number | string) => ({ 39 | paddingRight: value, 40 | }), 41 | pb: () => (value: number | string) => ({ 42 | paddingBottom: value, 43 | }), 44 | pl: () => (value: number | string) => ({ 45 | paddingLeft: value, 46 | }), 47 | px: () => (value: number | string) => ({ 48 | paddingLeft: value, 49 | paddingRight: value, 50 | }), 51 | py: () => (value: number | string) => ({ 52 | paddingTop: value, 53 | paddingBottom: value, 54 | }), 55 | size: () => (value: number | string) => ({ 56 | width: value, 57 | height: value, 58 | }), 59 | bg: () => (value: string) => ({ 60 | background: value, 61 | }), 62 | fadeBg: () => (value: number) => ({ 63 | transition: `background-color ${value}s`, 64 | }), 65 | } 66 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const HtmlWebpackInlineSourcePlugin = require("html-webpack-inline-source-plugin") 3 | const HtmlWebpackPlugin = require("html-webpack-plugin") 4 | const path = require("path") 5 | 6 | module.exports = (env, argv) => ({ 7 | mode: argv.mode === "production" ? "production" : "development", 8 | 9 | // This is necessary because Figma's 'eval' works differently than normal eval 10 | devtool: argv.mode === "production" ? false : "inline-source-map", 11 | 12 | entry: { 13 | ui: "./src/ui/index.tsx", // The entry point for your UI code 14 | plugin: "./src/main/index.ts", // The entry point for your plugin code 15 | }, 16 | 17 | module: { 18 | rules: [ 19 | // Converts TypeScript code to JavaScript 20 | { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/ }, 21 | 22 | // Enables including CSS by doing "import './file.css'" in your TypeScript code 23 | { 24 | test: /\.css$/, 25 | loader: [{ loader: "style-loader" }, { loader: "css-loader" }], 26 | }, 27 | 28 | // Allows you to use "<%= require('./file.svg') %>" in your HTML code to get a data URI 29 | { 30 | test: /\.(png|jpg|gif|webp|svg|zip)$/, 31 | loader: [{ loader: "url-loader" }], 32 | }, 33 | ], 34 | }, 35 | 36 | // Webpack tries these extensions for you if you omit the extension like "import './file'" 37 | resolve: { extensions: [".tsx", ".ts", ".jsx", ".js"] }, 38 | 39 | output: { 40 | filename: "[name].js", 41 | path: path.resolve(__dirname, "dist"), // Compile into a folder called "dist" 42 | }, 43 | 44 | // Tells Webpack to generate "ui.html" and to inline "ui.ts" into it 45 | plugins: [ 46 | new HtmlWebpackPlugin({ 47 | template: "./src/ui/ui.html", 48 | filename: "ui.html", 49 | inlineSource: ".(js)$", 50 | chunks: ["ui"], 51 | }), 52 | new HtmlWebpackInlineSourcePlugin(), 53 | ], 54 | }) 55 | -------------------------------------------------------------------------------- /src/ui/routes/selecting.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { useStateDesigner } from "@state-designer/react" 3 | import state from "../state" 4 | import { styled } from "../theme" 5 | import Controls from "../components/controls" 6 | import SelectedList from "../components/selected-list" 7 | import { Text, Button } from "../components/shared" 8 | 9 | export default function Selecting() { 10 | const local = useStateDesigner(state) 11 | 12 | return ( 13 | 14 | {local.isIn("noNodesSelected") && ( 15 | 16 | 17 | Select a vector node to begin. 18 | 19 | 20 | )} 21 | 22 | 23 | 29 | 30 | 37 | 40 | 41 | 42 | ) 43 | } 44 | 45 | const Layout = styled.div({ 46 | display: "grid", 47 | gridTemplateRows: "1fr auto auto auto", 48 | pb: "$2", 49 | height: "100%", 50 | maxHeight: "100%", 51 | gridGap: 0, 52 | "& > button": { 53 | mx: "$2", 54 | mt: "$1", 55 | mb: "$1", 56 | }, 57 | "& *": { 58 | outlineColor: "#00A5FF", 59 | }, 60 | }) 61 | 62 | const Instructions = styled.div({ 63 | display: "flex", 64 | alignItems: "center", 65 | justifyContent: "center", 66 | flexDirection: "column", 67 | gridRow: "span 2", 68 | pt: "$1", 69 | height: "100%", 70 | variants: { 71 | variant: { 72 | text: { 73 | alignItems: "flex-start", 74 | justifyContent: "flex-start", 75 | overflowY: "scroll", 76 | }, 77 | }, 78 | }, 79 | }) 80 | 81 | const FooterContainer = styled.div({ 82 | pt: "$0", 83 | px: "$2", 84 | display: "flex", 85 | justifyContent: "space-between", 86 | }) 87 | -------------------------------------------------------------------------------- /src/ui/theme/core.tsx: -------------------------------------------------------------------------------- 1 | import { createStyled } from "@stitches/react" 2 | import utils from "./utils" 3 | 4 | const { styled, css } = createStyled({ 5 | tokens: { 6 | colors: { 7 | $text: "#000000", 8 | $bg: "#ffffff", 9 | $highlight: "#00a3ff", 10 | $figma: "#00A2FF", 11 | $hover: "rgba(144, 144, 144, .08)", 12 | $accent: "#00a3ff", 13 | $primaryFill: "rgba(0,0,0,1)", 14 | $secondaryFill: "rgba(0,0,0,.4)", 15 | $tertiaryFill: "rgba(0,0,0,.3)", 16 | $quaternaryFill: "rgba(0,0,0, .07)", 17 | $secondaryBg: "#FFFFFF", 18 | $primaryGray: "rgba(174, 174, 178, 1)", 19 | $secondaryGray: "rgba(209, 209, 214, 1)", 20 | $locked: "#BBBBC0", 21 | $dash: "rgba(209, 209, 214, .5)", 22 | $backDrop: "rgba(0, 0, 0, .5)", 23 | $border: "rgba(142, 142, 147, .3)", 24 | $warn: "#FF4568", 25 | }, 26 | lineHeights: { 27 | $ui: "1", 28 | $body: "1.62", 29 | $code: "1.5", 30 | }, 31 | space: { 32 | $0: "8px", 33 | $1: "16px", 34 | $2: "24px", 35 | $3: "32px", 36 | $4: "40px", 37 | $5: "48px", 38 | $6: "64px", 39 | $7: "80px", 40 | $8: "96px", 41 | $9: "128px", 42 | }, 43 | fontSizes: { 44 | $detail: "11px", 45 | $body: "12px", 46 | $title: "16px", 47 | }, 48 | fontWeights: { 49 | $detail: "500", 50 | $body: "400", 51 | $strong: "600", 52 | }, 53 | radii: { 54 | $0: "4px", 55 | $1: "8px", 56 | $2: "16px", 57 | }, 58 | fonts: { 59 | $body: "'Inter', system-ui, sans-serif", 60 | $ui: "'Inter', system-ui, sans-serif", 61 | $heading: '"Inter", system-ui, sans-serif', 62 | $display: '"Inter", system-ui, sans-serif', 63 | $monospace: "Menlo, monospace", 64 | }, 65 | }, 66 | utils, 67 | }) 68 | 69 | const lightTheme = css.theme({}) 70 | 71 | const darkTheme = css.theme({ 72 | colors: { 73 | $text: "rgba(255, 255, 255, 1)", 74 | $bg: "#050505", 75 | $primaryFill: "rgba(255, 255, 255, 1)", 76 | $secondaryFill: "rgba(255, 255, 255, .5)", 77 | $tertiaryFill: "rgba(255, 255, 255, .3)", 78 | $quaternaryFill: "rgba(255, 255, 255, .1)", 79 | $secondaryBg: "#19191B", 80 | $primaryGray: "rgba(72, 72, 74, 1)", 81 | $secondaryGray: "rgba(44, 44, 46, 1)", 82 | }, 83 | }) 84 | 85 | css.global({ 86 | "html, *": { 87 | boxSizing: "border-box", 88 | }, 89 | body: { 90 | margin: 0, 91 | fontFamily: "$body", 92 | fontSize: "$body", 93 | fontWeight: "$body", 94 | color: "$text", 95 | }, 96 | }) 97 | 98 | export { styled, css, lightTheme, darkTheme } 99 | -------------------------------------------------------------------------------- /src/ui/components/selected-list.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import state from "../state" 3 | import { styled } from "../theme" 4 | import * as Icons from "../assets/icons" 5 | import { NodeInfo } from "../../types" 6 | import { Text } from "../components/shared" 7 | 8 | export default function SelectedList({ items }: { items: NodeInfo[] }) { 9 | return ( 10 | 11 | {items.map((item) => ( 12 | 13 | ))} 14 | 15 | ) 16 | } 17 | 18 | function SelectedItem({ id, name, type }: NodeInfo) { 19 | const Icon = 20 | type === "FRAME" 21 | ? Icons.Frame 22 | : type === "INSTANCE" 23 | ? Icons.Instance 24 | : Icons.Component 25 | 26 | return ( 27 | 28 | state.send("ZOOMED_TO_NODE", id)} 31 | > 32 | state.send("ZOOMED_TO_NODE", id)} /> 33 | 34 | {name} 35 | 36 | 37 | state.send("DESELECTED_NODE", id)} 41 | > 42 | 43 | 44 | 45 | ) 46 | } 47 | 48 | const ListContainer = styled.ul({ 49 | height: "100%", 50 | overflowY: "scroll", 51 | ml: 0, 52 | my: 0, 53 | pl: 0, 54 | py: "$0", 55 | listStyleType: "none", 56 | }) 57 | 58 | const ItemRow = styled.li({ 59 | display: "grid", 60 | gridTemplateColumns: "minmax(0, 1fr) min-content", 61 | alignItems: "center", 62 | gridGap: "$0", 63 | pl: "$2", 64 | pr: "$1", 65 | overflow: "hidden", 66 | "& > *[data-hidey=true]": { 67 | visibility: "hidden", 68 | }, 69 | "&:hover > *[data-hidey=true]": { 70 | visibility: "visible", 71 | }, 72 | }) 73 | 74 | const ZoomButton = styled.button({ 75 | outline: "none", 76 | cursor: "pointer", 77 | bg: "transparent", 78 | border: "none", 79 | display: "flex", 80 | justifyContent: "flex-start", 81 | alignItems: "center", 82 | p: 0, 83 | m: 0, 84 | height: "100%", 85 | ml: "4px", 86 | "& > *:not(:first-child)": { 87 | ml: "$0", 88 | }, 89 | }) 90 | 91 | const IconButton = styled.button({ 92 | outline: "none", 93 | cursor: "pointer", 94 | bg: "transparent", 95 | borderRadius: "$0", 96 | border: "none", 97 | height: 32, 98 | width: 32, 99 | mr: "2px", 100 | "&:hover": { 101 | bg: "$hover", 102 | }, 103 | }) 104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "figma-plugin-perfect-freehand", 4 | "version": "1.0.2", 5 | "description": "Create freehand strokes in Figma.", 6 | "scripts": { 7 | "start": "npm run dev", 8 | "dev": "webpack --watch", 9 | "prebuild": "npm run lint:fix && rimraf dist/*", 10 | "build": "webpack -p", 11 | "lint": "npm run lint:ts && npm run lint:css", 12 | "lint:fix": "npm run lint:ts:fix && npm run lint:css:fix", 13 | "lint:ts": "eslint . --ext .ts,.js", 14 | "lint:ts:fix": "eslint . --ext .ts,.js", 15 | "lint:css": "stylelint 'src/**/*'", 16 | "lint:css:fix": "stylelint 'src/**/*' --fix", 17 | "test:base": "jest --passWithNoTests", 18 | "test:precheck": "test -d dist || npm run build", 19 | "pretest": "npm run test:precheck", 20 | "test": "npm run test:base", 21 | "pretest:watch": "npm run test:precheck", 22 | "test:watch": "npm run test:base -- --watch" 23 | }, 24 | "author": { 25 | "name": "Steve Ruiz", 26 | "email": "steveruizok@gmail.com", 27 | "url": "https://twitter.com/steveruizok" 28 | }, 29 | "license": "MIT", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/steveruizok/figma-plugin-perfect-freehand" 33 | }, 34 | "homepage": "https://github.com/steveruizok/figma-plugin-perfect-freehand", 35 | "devDependencies": { 36 | "@types/figma": "^1.0.3", 37 | "@types/jest": "^25.2.1", 38 | "@types/lz-string": "^1.3.34", 39 | "@types/node": "^13.11.0", 40 | "@types/react": "^17.0.3", 41 | "@types/react-dom": "^17.0.2", 42 | "@typescript-eslint/eslint-plugin": "^2.26.0", 43 | "@typescript-eslint/parser": "^2.26.0", 44 | "css-loader": "^3.4.2", 45 | "eslint": "^6.8.0", 46 | "eslint-config-prettier": "^6.10.1", 47 | "eslint-plugin-prettier": "^3.1.2", 48 | "html-webpack-inline-source-plugin": "0.0.10", 49 | "html-webpack-plugin": "^3.2.0", 50 | "husky": "^4.2.3", 51 | "jest": "^25.2.7", 52 | "lint-staged": "^10.1.2", 53 | "prettier": "^2.0.4", 54 | "rimraf": "^3.0.2", 55 | "style-loader": "^1.1.3", 56 | "stylelint": "^13.3.0", 57 | "stylelint-config-prettier": "^8.0.1", 58 | "stylelint-config-recommended": "^3.0.0", 59 | "stylelint-prettier": "^1.1.2", 60 | "ts-jest": "^25.3.1", 61 | "ts-loader": "^6.2.2", 62 | "typescript": "^3.8.3", 63 | "url-loader": "^3.0.0", 64 | "webpack": "^4.42.1", 65 | "webpack-cli": "^3.3.11" 66 | }, 67 | "prettier": { 68 | "semi": false, 69 | "trailingComma": "es5" 70 | }, 71 | "keywords": [ 72 | "figma", 73 | "plugin", 74 | "figma plugin", 75 | "perfect", 76 | "freehand", 77 | "drawing", 78 | "ink", 79 | "sketching", 80 | "lettering", 81 | "handwriting" 82 | ], 83 | "husky": { 84 | "hooks": { 85 | "pre-commit": "lint-staged" 86 | } 87 | }, 88 | "lint-staged": { 89 | "*.{ts,js}": [ 90 | "git add" 91 | ], 92 | "src/**/*": [ 93 | "git add" 94 | ], 95 | "*.{html,json,md}": [ 96 | "prettier --write", 97 | "git add" 98 | ] 99 | }, 100 | "dependencies": { 101 | "@radix-ui/react-label": "^0.0.6", 102 | "@state-designer/react": "^1.7.1", 103 | "@stitches/react": "^0.0.2", 104 | "lz-string": "^1.4.4", 105 | "perfect-freehand": "^0.4.6", 106 | "react": "^17.0.1", 107 | "react-dom": "^17.0.1" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/ui/state.ts: -------------------------------------------------------------------------------- 1 | import { createState } from "@state-designer/react" 2 | import { UIActionTypes, UIAction, NodeInfo } from "../types" 3 | 4 | const defaultOptions = { 5 | size: 32, 6 | streamline: 0.5, 7 | smoothing: 0.5, 8 | thinning: 0.75, 9 | easing: "linear", 10 | clip: true, 11 | taperStart: 0, 12 | taperEnd: 0, 13 | } 14 | 15 | // This is the UI's global state machine. Events from the UI 16 | // are sent here. Components in the UI subscribe to its changes. 17 | const state = createState({ 18 | data: { 19 | selectedNodes: [] as NodeInfo[], 20 | options: defaultOptions, 21 | }, 22 | on: { CLOSED_PLUGIN: "closePlugin" }, 23 | initial: "selectingNodes", 24 | states: { 25 | selectingNodes: { 26 | on: { 27 | RESET_OPTION: ["setOptionToDefault", "setOption"], 28 | CHANGED_OPTION: "setOption", 29 | OPENED_DOCS: { to: "readingDocs" }, 30 | SELECTED_NODES: "setSelectedNodes", 31 | DESELECTED_NODE: "deselectNode", 32 | ZOOMED_TO_NODE: "zoomToNode", 33 | }, 34 | initial: "hasNodesSelected", 35 | states: { 36 | noNodesSelected: { 37 | on: { 38 | SELECTED_NODES: { 39 | if: "hasSelectedNodes", 40 | to: "hasNodesSelected", 41 | }, 42 | }, 43 | }, 44 | hasNodesSelected: { 45 | on: { 46 | SELECTED_NODES: { 47 | unless: "hasSelectedNodes", 48 | to: "noNodesSelected", 49 | }, 50 | TRANSFORMED_NODES: "transformSelectedNodes", 51 | RESET_NODES: { 52 | if: "hasResetableNodes", 53 | do: "resetSelectedNodes", 54 | }, 55 | }, 56 | }, 57 | }, 58 | }, 59 | readingDocs: { 60 | on: { 61 | RETURNED: { 62 | to: "selectingNodes.restore", 63 | }, 64 | }, 65 | }, 66 | }, 67 | conditions: { 68 | hasSelectedNodes(data) { 69 | return data.selectedNodes.length > 0 70 | }, 71 | hasResetableNodes(data) { 72 | return data.selectedNodes.some((node) => node.canReset) 73 | }, 74 | }, 75 | actions: { 76 | // Slection 77 | setSelectedNodes(data, payload: NodeInfo[]) { 78 | data.selectedNodes = payload 79 | }, 80 | zoomToNode(data, id) { 81 | postMessage({ type: UIActionTypes.ZOOM_TO_NODE, payload: id }) 82 | }, 83 | deselectNode(data, id) { 84 | postMessage({ type: UIActionTypes.DESELECT_NODE, payload: id }) 85 | }, 86 | // Transforms 87 | transformSelectedNodes(data) { 88 | postMessage({ 89 | type: UIActionTypes.TRANSFORM_NODES, 90 | payload: { 91 | options: { ...data.options }, 92 | easing: data.options.easing, 93 | clip: data.options.clip, 94 | }, 95 | }) 96 | }, 97 | resetSelectedNodes() { 98 | postMessage({ 99 | type: UIActionTypes.RESET_NODES, 100 | }) 101 | }, 102 | // Options 103 | setOption(data, payload) { 104 | data.options = { ...data.options, ...payload } 105 | postMessage({ 106 | type: UIActionTypes.UPDATED_OPTIONS, 107 | payload: { 108 | options: { 109 | ...data.options, 110 | start: { taper: data.options.taperStart }, 111 | end: { taper: data.options.taperEnd }, 112 | }, 113 | easing: data.options.easing, 114 | clip: data.options.clip, 115 | }, 116 | }) 117 | }, 118 | setOptionToDefault(data, payload: keyof typeof defaultOptions) { 119 | data.options = { 120 | ...data.options, 121 | [payload]: defaultOptions[payload], 122 | } 123 | }, 124 | // Plugin 125 | closePlugin() { 126 | postMessage({ type: UIActionTypes.CLOSE }) 127 | }, 128 | }, 129 | }) 130 | 131 | function postMessage({ type, payload }: UIAction): void { 132 | parent.postMessage({ pluginMessage: { type, payload } }, "*") 133 | } 134 | 135 | // Forward messages sent from the plugin controller to the state 136 | window.onmessage = (event: any) => { 137 | const { type, payload } = event.data.pluginMessage 138 | state.send(type, payload) 139 | } 140 | 141 | export default state 142 | 143 | // state.onUpdate((d) => console.log(d.active)) 144 | -------------------------------------------------------------------------------- /src/ui/assets/icons.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | export function Component(props: React.SVGProps) { 4 | return ( 5 | 13 | 18 | 19 | ) 20 | } 21 | 22 | export function Frame(props: React.SVGProps) { 23 | return ( 24 | 32 | 39 | 40 | ) 41 | } 42 | 43 | export function Close(props: React.SVGProps) { 44 | return ( 45 | 53 | 60 | 61 | ) 62 | } 63 | 64 | export function Instance(props: React.SVGProps) { 65 | return ( 66 | 73 | 79 | 80 | ) 81 | } 82 | 83 | export function Success(props: React.SVGProps) { 84 | return ( 85 | 86 | 93 | 100 | 101 | ) 102 | } 103 | 104 | export function Link(props: React.SVGProps) { 105 | return ( 106 | 113 | 118 | 119 | ) 120 | } 121 | 122 | export function Alert(props: React.SVGProps) { 123 | return ( 124 | 125 | 134 | 142 | 149 | 150 | ) 151 | } 152 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | // import polygonClipping from "polygon-clipping" 2 | 3 | const { pow } = Math 4 | 5 | export function cubicBezier( 6 | tx: number, 7 | x1: number, 8 | y1: number, 9 | x2: number, 10 | y2: number 11 | ) { 12 | // Inspired by Don Lancaster's two articles 13 | // http://www.tinaja.com/glib/cubemath.pdf 14 | // http://www.tinaja.com/text/bezmath.html 15 | 16 | // Set p0 and p1 point 17 | let x0 = 0, 18 | y0 = 0, 19 | x3 = 1, 20 | y3 = 1, 21 | // Convert the coordinates to equation space 22 | A = x3 - 3 * x2 + 3 * x1 - x0, 23 | B = 3 * x2 - 6 * x1 + 3 * x0, 24 | C = 3 * x1 - 3 * x0, 25 | D = x0, 26 | E = y3 - 3 * y2 + 3 * y1 - y0, 27 | F = 3 * y2 - 6 * y1 + 3 * y0, 28 | G = 3 * y1 - 3 * y0, 29 | H = y0, 30 | // Variables for the loop below 31 | t = tx, 32 | iterations = 5, 33 | i: number, 34 | slope: number, 35 | x: number, 36 | y: number 37 | 38 | // Loop through a few times to get a more accurate time value, according to the Newton-Raphson method 39 | // http://en.wikipedia.org/wiki/Newton's_method 40 | for (i = 0; i < iterations; i++) { 41 | // The curve's x equation for the current time value 42 | x = A * t * t * t + B * t * t + C * t + D 43 | 44 | // The slope we want is the inverse of the derivate of x 45 | slope = 1 / (3 * A * t * t + 2 * B * t + C) 46 | 47 | // Get the next estimated time value, which will be more accurate than the one before 48 | t -= (x - tx) * slope 49 | t = t > 1 ? 1 : t < 0 ? 0 : t 50 | } 51 | 52 | // Find the y value through the curve's y equation, with the now more accurate time value 53 | y = Math.abs(E * t * t * t + F * t * t + G * t * H) 54 | 55 | return y 56 | } 57 | 58 | export function getPointsAlongCubicBezier( 59 | ptCount: number, 60 | pxTolerance: number, 61 | Ax: number, 62 | Ay: number, 63 | Bx: number, 64 | By: number, 65 | Cx: number, 66 | Cy: number, 67 | Dx: number, 68 | Dy: number 69 | ) { 70 | let deltaBAx = Bx - Ax 71 | let deltaCBx = Cx - Bx 72 | let deltaDCx = Dx - Cx 73 | let deltaBAy = By - Ay 74 | let deltaCBy = Cy - By 75 | let deltaDCy = Dy - Cy 76 | let ax, ay, bx, by, cx, cy 77 | let lastX = -10000 78 | let lastY = -10000 79 | let pts = [{ x: Ax, y: Ay }] 80 | for (let i = 1; i < ptCount; i++) { 81 | let t = i / ptCount 82 | ax = Ax + deltaBAx * t 83 | bx = Bx + deltaCBx * t 84 | cx = Cx + deltaDCx * t 85 | ax += (bx - ax) * t 86 | bx += (cx - bx) * t 87 | ay = Ay + deltaBAy * t 88 | by = By + deltaCBy * t 89 | cy = Cy + deltaDCy * t 90 | ay += (by - ay) * t 91 | by += (cy - by) * t 92 | const x = ax + (bx - ax) * t 93 | const y = ay + (by - ay) * t 94 | const dx = x - lastX 95 | const dy = y - lastY 96 | if (dx * dx + dy * dy > pxTolerance) { 97 | pts.push({ x: x, y: y }) 98 | lastX = x 99 | lastY = y 100 | } 101 | } 102 | pts.push({ x: Dx, y: Dy }) 103 | return pts 104 | } 105 | 106 | export function interpolateCubicBezier( 107 | p0: { x: number; y: number }, 108 | c0: { x: number; y: number }, 109 | c1: { x: number; y: number }, 110 | p1: { x: number; y: number } 111 | ) { 112 | // 0 <= t <= 1 113 | return function interpolator(t: number) { 114 | return [ 115 | pow(1 - t, 3) * p0.x + 116 | 3 * pow(1 - t, 2) * t * c0.x + 117 | 3 * (1 - t) * pow(t, 2) * c1.x + 118 | pow(t, 3) * p1.x, 119 | pow(1 - t, 3) * p0.y + 120 | 3 * pow(1 - t, 2) * t * c0.y + 121 | 3 * (1 - t) * pow(t, 2) * c1.y + 122 | pow(t, 3) * p1.y, 123 | ] 124 | } 125 | } 126 | 127 | export function addVectors( 128 | a: { x: number; y: number }, 129 | b?: { x: number; y: number } 130 | ) { 131 | if (!b) return a 132 | return { x: a.x + b.x, y: a.y + b.y } 133 | } 134 | 135 | export function getSvgPathFromStroke(stroke: number[][]) { 136 | if (stroke.length === 0) return "" 137 | const d = [] 138 | let [p0, p1] = stroke 139 | d.push("M", p0[0], p0[1]) 140 | for (let i = 1; i < stroke.length; i++) { 141 | d.push("Q", p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2) 142 | p0 = p1 143 | p1 = stroke[i] 144 | } 145 | d.push("Z") 146 | return d.join(" ") 147 | } 148 | 149 | // export function getFlatSvgPathFromStroke(stroke: number[][]) { 150 | // try { 151 | // const poly = polygonClipping.union([stroke] as any) 152 | 153 | // const d = [] 154 | 155 | // for (let face of poly) { 156 | // for (let points of face) { 157 | // points.push(points[0]) 158 | // d.push(getSvgPathFromStroke(points)) 159 | // } 160 | // } 161 | 162 | // d.push("Z") 163 | 164 | // return d.join(" ") 165 | // } catch (e) { 166 | // console.error("Could not clip path.") 167 | // return getSvgPathFromStroke(stroke) 168 | // } 169 | // } 170 | -------------------------------------------------------------------------------- /src/ui/components/controls.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import Label from "./label" 3 | import state from "../state" 4 | import { Text } from "./shared" 5 | import { styled } from "../theme" 6 | import { useSelector } from "@state-designer/react/" 7 | 8 | export default function Controls() { 9 | const options = useSelector(state, (state) => state.data.options) 10 | 11 | return ( 12 | 13 |
14 | 15 | 16 | {options.size}px 17 | 18 | 25 | state.send("CHANGED_OPTION", { size: Number(value) }) 26 | } 27 | onDoubleClick={() => state.send("RESET_OPTION", "size")} 28 | /> 29 |
30 |
31 | 32 | 33 | {Math.round(options.thinning * 100)}% 34 | 35 | 42 | state.send("CHANGED_OPTION", { thinning: Number(value) }) 43 | } 44 | onDoubleClick={() => state.send("RESET_OPTION", "thinning")} 45 | /> 46 |
47 |
48 | 49 | 50 | {Math.round(options.smoothing * 100)}% 51 | 52 | 59 | state.send("CHANGED_OPTION", { smoothing: Number(value) }) 60 | } 61 | onDoubleClick={() => state.send("RESET_OPTION", "smoothing")} 62 | /> 63 |
64 |
65 | 66 | 67 | {Math.round(options.streamline * 100)}% 68 | 69 | 76 | state.send("CHANGED_OPTION", { streamline: Number(value) }) 77 | } 78 | onDoubleClick={() => state.send("RESET_OPTION", "streamline")} 79 | /> 80 |
81 | 82 |
83 | 84 | 85 | {Math.round(options.taperStart)}px 86 | 87 | 94 | state.send("CHANGED_OPTION", { taperStart: Number(value) }) 95 | } 96 | onDoubleClick={() => state.send("RESET_OPTION", "taperStart")} 97 | /> 98 |
99 |
100 | 101 | 102 | {Math.round(options.taperEnd)}px 103 | 104 | 111 | state.send("CHANGED_OPTION", { taperEnd: Number(value) }) 112 | } 113 | onDoubleClick={() => state.send("RESET_OPTION", "taperEnd")} 114 | /> 115 |
116 |
117 | 118 | 119 | 130 | 131 | {/* 132 | 133 | 134 | 138 | state.send("CHANGED_OPTION", { clip: Boolean(checked) }) 139 | } 140 | /> 141 | 142 | */} 143 |
144 | ) 145 | } 146 | 147 | const ControlsContainer = styled.div({ 148 | display: "grid", 149 | gap: "$0", 150 | input: { width: "100%" }, 151 | "input[type=checkbox]": { 152 | width: "auto", 153 | ml: 10, 154 | }, 155 | "& select": { 156 | ml: 10, 157 | width: "100%", 158 | fontSize: "12px", 159 | fontWeight: 400, 160 | height: "100%", 161 | pt: "3px", 162 | pb: "1px", 163 | border: "1px solid #E5E5E5", 164 | borderRadius: "4px", 165 | fontFamily: "'Inter', system-ui, sans-serif", 166 | }, 167 | borderTop: "1px solid #E5E5E5", 168 | pt: "$1", 169 | px: "$2", 170 | }) 171 | 172 | const LabelContainer = styled.div({ 173 | display: "flex", 174 | justifyContent: "space-between", 175 | alignItems: "center", 176 | }) 177 | 178 | const DoubleRow = styled.div({ 179 | display: "grid", 180 | gridTemplateColumns: "1fr 1fr", 181 | gap: "$1", 182 | alignItems: "center", 183 | }) 184 | -------------------------------------------------------------------------------- /src/ui/components/shared.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "../theme" 2 | 3 | export const Stack = styled.div({ 4 | display: "flex", 5 | flexDirection: "column", 6 | justifyContent: "center", 7 | alignItems: "center", 8 | "& input[disabled] + label": { 9 | opacity: 0.4, 10 | }, 11 | '& [data-fadey="true"]': { 12 | opacity: 0.4, 13 | }, 14 | '&:hover [data-fadey="true"]': { 15 | opacity: 1, 16 | }, 17 | '& [data-hidey="true"]': { 18 | visibility: "hidden", 19 | }, 20 | '&:hover [data-hidey="true"]': { 21 | visibility: "visible", 22 | }, 23 | variants: { 24 | alignment: { 25 | start: { 26 | justifyContent: "flex-start", 27 | }, 28 | end: { 29 | justifyContent: "flex-end", 30 | }, 31 | center: { 32 | justifyContent: "center", 33 | }, 34 | }, 35 | distribution: { 36 | start: { 37 | justifyContent: "flex-start", 38 | }, 39 | end: { 40 | justifyContent: "flex-end", 41 | }, 42 | center: { 43 | justifyContent: "center", 44 | }, 45 | between: { 46 | justifyContent: "space-between", 47 | }, 48 | around: { 49 | justifyContent: "space-around", 50 | }, 51 | }, 52 | gap: { 53 | cozyVertical: { 54 | "& > *:not(:first-child)": { 55 | mt: "$1", 56 | }, 57 | }, 58 | wideVertical: { 59 | "& > *:not(:first-child)": { 60 | mt: "$2", 61 | }, 62 | }, 63 | }, 64 | direction: { 65 | vertical: { 66 | flexDirection: "column", 67 | "& > *:not(:first-child)": { 68 | mt: "$0", 69 | }, 70 | }, 71 | verticalReverse: { 72 | flexDirection: "column-reverse", 73 | "& > *:not(:first-child)": { 74 | mb: "$0", 75 | }, 76 | }, 77 | horizontal: { 78 | flexDirection: "row", 79 | "& > *:not(:first-child)": { 80 | ml: "$0", 81 | }, 82 | }, 83 | horizontalReverse: { 84 | flexDirection: "row-reverse", 85 | "& > *:not(:first-child)": { 86 | mr: "$0", 87 | }, 88 | }, 89 | }, 90 | }, 91 | }) 92 | 93 | export const Instructions = styled.div({ 94 | display: "flex", 95 | alignItems: "center", 96 | justifyContent: "center", 97 | flexDirection: "column", 98 | "& > *:not(:first-child)": { 99 | mt: "$2", 100 | }, 101 | variants: { 102 | variant: { 103 | text: { 104 | alignItems: "flex-start", 105 | justifyContent: "flex-start", 106 | overflowY: "scroll", 107 | }, 108 | }, 109 | }, 110 | }) 111 | 112 | export const Text = styled.p({ 113 | fontSize: "$body", 114 | fontWeight: "$body", 115 | lineHeight: "$body", 116 | m: 0, 117 | p: 0, 118 | variants: { 119 | variant: { 120 | strong: { 121 | fontWeight: "$strong", 122 | }, 123 | detail: { 124 | fontSize: "$detail", 125 | lineHeight: "$ui", 126 | color: "$secondaryFill", 127 | }, 128 | selection: { 129 | maxWidth: "100%", 130 | textAlign: "left", 131 | whiteSpace: "nowrap", 132 | overflow: "hidden", 133 | textOverflow: "ellipsis", 134 | }, 135 | }, 136 | align: { 137 | center: { 138 | textAlign: "center", 139 | }, 140 | left: { 141 | textAlign: "left", 142 | }, 143 | right: { 144 | textAlign: "right", 145 | }, 146 | }, 147 | highlight: { 148 | none: {}, 149 | primary: { 150 | color: "$accent", 151 | }, 152 | secondary: { 153 | color: "$primaryGray", 154 | }, 155 | }, 156 | }, 157 | }) 158 | 159 | export const Button = styled.button({ 160 | cursor: "pointer", 161 | display: "block", 162 | py: "$1", 163 | textAlign: "center", 164 | fontSize: "$body", 165 | fontWeight: "$strong", 166 | lineHeight: "$ui", 167 | borderRadius: "$0", 168 | bg: "$accent", 169 | color: "$bg", 170 | outline: "none", 171 | border: "none", 172 | "& > *:not(:first-child)": { 173 | ml: "$0", 174 | }, 175 | "&:active": { 176 | filter: "brightness(.95)", 177 | }, 178 | "&:disabled": { 179 | filter: "saturate(0%) opacity(40%)", 180 | }, 181 | variants: { 182 | variant: { 183 | detailHl: { 184 | bg: "transparent", 185 | py: "$0", 186 | my: "-$0", 187 | px: "$0", 188 | mx: "-$0", 189 | fontSize: "$detail", 190 | lineHeight: "$ui", 191 | color: "$accent", 192 | "&:hover": { 193 | bg: "$hover", 194 | color: "$text", 195 | }, 196 | }, 197 | detail: { 198 | bg: "transparent", 199 | py: "$0", 200 | my: "-$0", 201 | px: "$0", 202 | mx: "-$0", 203 | fontSize: "$detail", 204 | lineHeight: "$ui", 205 | color: "$secondaryFill", 206 | "&:hover": { 207 | bg: "$hover", 208 | color: "$text", 209 | }, 210 | }, 211 | row: { 212 | bg: "transparent", 213 | py: 0, 214 | px: 0, 215 | opacity: 0.8, 216 | "&:hover": { 217 | opacity: 1, 218 | }, 219 | }, 220 | }, 221 | }, 222 | }) 223 | 224 | export const Logo = styled.img({ 225 | height: 32, 226 | mb: "$0", 227 | }) 228 | 229 | export const Icon = styled.div({ 230 | display: "flex", 231 | alignItems: "center", 232 | justifyContent: "center", 233 | size: 24, 234 | }) 235 | 236 | export const Label = styled.label({ 237 | cursor: "pointer", 238 | }) 239 | 240 | export const Input = styled.input({ 241 | cursor: "pointer", 242 | display: "block", 243 | py: "$1", 244 | px: "$1", 245 | fontSize: "$body", 246 | fontWeight: "$body", 247 | lineHeight: "$ui", 248 | borderRadius: "$0", 249 | bg: "$quaternaryFill", 250 | outline: "none", 251 | border: "none", 252 | color: "$text", 253 | "&:active": { 254 | filter: "brightness(.95)", 255 | }, 256 | "&:disabled": { 257 | filter: "saturate(0%) opacity(40%)", 258 | }, 259 | }) 260 | -------------------------------------------------------------------------------- /src/ui/routes/docs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import state from "../state" 3 | import { styled } from "../theme" 4 | import { Button } from "../components/shared" 5 | 6 | const VERSION = "9" 7 | 8 | export default function Docs() { 9 | return ( 10 | 11 | 12 |

About this Plugin

13 |

14 | You can use this plugin to turn vector lines into freehand strokes. 15 |

16 | 30 |

Quickstart

31 |
    32 |
  1. 33 | Select the Pencil Tool (Shift + P). 34 |
  2. 35 |
  3. Draw or write something on the canvas.
  4. 36 |
  5. Select your pencil lines.
  6. 37 |
  7. 38 | In this plugin, click the Apply button. 39 |
  8. 40 |
41 |

42 | To revert a stroke to its original shape, click the Reset{" "} 43 | button. 44 |

45 |

Options

46 |

47 | You can use the plugin's options to change the appearance of a mark. 48 | See the Options section below for more information on each 49 | option. 50 |

51 |
52 |
Size
53 |
Sets the base width for the stroke.
54 |
Thinning
55 |
Sets the effect of pressure on the stroke's width.
56 |
Smoothing
57 |
58 | Reduces the overall number of points. A higher value will produce a 59 | smoother stroke. 60 |
61 |
Streamline
62 |
Increases the stability of the stroke.
63 |
Taper Start
64 |
Tapers the beginning of the stroke.
65 |
Taper End
66 |
Tapers the end of the stroke.
67 |
Easing
68 |
Applies an easing curve to the line's simulated pressure.
69 | {/*
Clip
70 |
Will flatten the stroke into an outline polygon.
*/} 71 |
72 |

Tips

73 |

74 | You can continue adjusting a stroke's options after applying the 75 | effect. 76 |

77 |

78 | Setting a negative Thinning value will cause the stroke to 79 | become thicker at minimum pressure. 80 |

81 |

To create a "flat" stroke, intersect the stroke with a rectangle.

82 |

83 | In general, areas with more vector nodes will result in greater 84 | pressure and so a thicker stroke, while areas with less detail will 85 | result in less simulated pressure and a thinner stroke. To force a 86 | mark to be thicker, try adding extra nodes yourself. 87 |

88 |

89 | If you'd like a better drawing experience—including real stylus 90 | pressure as well as better simulated pressure—try{" "} 91 | 96 | this link 97 | 98 | , a demo for the{" "} 99 | 104 | perfect-freehand 105 | {" "} 106 | library used by this plugin. You can copy your drawing from there and 107 | paste it into Figma. 108 |

109 |

Feedback & Contribution

110 |

111 | If you would like to reach the author, you can tweet me at{" "} 112 | 117 | @steveruizok 118 | 119 | . 120 |

121 |

122 | The source code for this plugin is available{" "} 123 | 128 | on Github 129 | 130 | . If you would like to contribute to the project's code, that's the 131 | best place to start. 132 |

133 |

134 | If you think you've found a bug in the plugin, please create an issue{" "} 135 | 140 | here 141 | 142 | . 143 |

144 |

145 | If you have ideas about how to make the plugin better, or for any 146 | other concern not mentioned above, post on the{" "} 147 | 152 | Discussions board 153 | 154 | . 155 |

156 |

Verison {VERSION}

157 | 158 |
159 | 160 | 161 | 170 | 179 | 180 |
181 | ) 182 | } 183 | 184 | const Layout = styled.div({ 185 | display: "grid", 186 | gridTemplateRows: "1fr auto auto auto", 187 | pb: "$2", 188 | height: "100%", 189 | maxHeight: "100%", 190 | gridGap: 0, 191 | "& > button": { 192 | mx: "$2", 193 | mt: "$1", 194 | mb: "$1", 195 | }, 196 | }) 197 | 198 | const Instructions = styled.div({ 199 | position: "relative", 200 | display: "grid", 201 | alignItems: "center", 202 | justifyContent: "center", 203 | gridRow: "span 2", 204 | height: "100%", 205 | gap: "$1", 206 | gridAutoRows: "min-content", 207 | pt: "$2", 208 | px: "$2", 209 | fontSize: "$2", 210 | lineHeight: 1.3, 211 | overflowY: "auto", 212 | "& h2": { py: 0, my: 0, scrollMarginTop: "16px" }, 213 | "& h3": { py: 0, my: 0 }, 214 | "& p": { py: 0, my: 0 }, 215 | "& ul": { py: 0, my: 0, pl: "$2" }, 216 | "& ol": { py: 0, my: 0, pl: "$2" }, 217 | "& li": { pb: "$0", my: 0, pl: 0 }, 218 | "& dl": { py: 0, my: 0 }, 219 | "& dt": { fontWeight: "bold" }, 220 | "& dd": { pt: 2, pb: "$1", pl: 0, ml: 0 }, 221 | "& a": { color: "$accent", fontWeight: 500 }, 222 | variants: { 223 | variant: { 224 | text: { 225 | alignItems: "flex-start", 226 | justifyContent: "flex-start", 227 | }, 228 | }, 229 | }, 230 | "& input[type=range]::-webkit-slider-runnable-track": { 231 | background: "red", 232 | }, 233 | }) 234 | 235 | const Scrim = styled.div({ 236 | position: "sticky", 237 | bottom: 0, 238 | left: 0, 239 | height: 32, 240 | width: "100%", 241 | background: 242 | "linear-gradient(0deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%)", 243 | }) 244 | 245 | const FooterContainer = styled.div({ 246 | pt: "$0", 247 | px: "$2", 248 | display: "flex", 249 | justifyContent: "space-between", 250 | a: { 251 | textDecoration: "none", 252 | }, 253 | }) 254 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UIActionTypes, 3 | UIAction, 4 | WorkerActionTypes, 5 | WorkerAction, 6 | NodeInfo, 7 | } from "../types" 8 | import { 9 | getSvgPathFromStroke, 10 | addVectors, 11 | interpolateCubicBezier, 12 | } from "../utils" 13 | import getStroke, { StrokeOptions } from "perfect-freehand" 14 | import { compressToUTF16, decompressFromUTF16 } from "lz-string" 15 | 16 | /* ----------------------- Comms ----------------------- */ 17 | 18 | // Sends a message to the plugin UI 19 | function postMessage({ type, payload }: WorkerAction): void { 20 | figma.ui.postMessage({ type, payload }) 21 | } 22 | 23 | /* ------------------- Original Nodes ------------------ */ 24 | 25 | // We need to store copies of original nodes (their vector networks and vertices) 26 | // so that we can restore the line after applying the effect. In order to stay 27 | // within memory limits, we compress the data before saving it as pluginData. 28 | 29 | interface OriginalNode { 30 | vectorNetwork: VectorNetwork 31 | vectorPaths: VectorPaths 32 | center: { x: number; y: number } 33 | } 34 | 35 | // Save some information about the node to its plugin data. 36 | function setOriginalNode(node: VectorNode): OriginalNode { 37 | const originalNode: OriginalNode = { 38 | center: getCenter(node), 39 | vectorNetwork: { ...node.vectorNetwork }, 40 | vectorPaths: node.vectorPaths, 41 | } 42 | 43 | node.setPluginData( 44 | "perfect_freehand", 45 | compressToUTF16(JSON.stringify(originalNode)) 46 | ) 47 | 48 | return originalNode 49 | } 50 | 51 | function decompressPluginData(pluginData: string) { 52 | // Decompress the saved data and parse out the original node. 53 | const decompressed = decompressFromUTF16(pluginData) 54 | 55 | if (!decompressed) { 56 | throw Error( 57 | "Found saved data for original node but could not decompress it: " + 58 | decompressed 59 | ) 60 | } 61 | 62 | return JSON.parse(decompressed) as OriginalNode 63 | } 64 | 65 | // Get an original node from a node's plugin data. 66 | function getOriginalNode(id: string): OriginalNode | undefined { 67 | let node = figma.getNodeById(id) as VectorNode 68 | 69 | if (!node) throw Error("Could not find that node: " + id) 70 | 71 | const pluginData = node.getPluginData("perfect_freehand") 72 | 73 | // Nothing on the node — we haven't modified it. 74 | if (!pluginData) return undefined 75 | 76 | return decompressPluginData(pluginData) 77 | } 78 | 79 | /* ---------------------- Nodes --------------------- */ 80 | 81 | // Get the currently selected Vector nodes for the UI. 82 | function getSelectedNodes(updateCenter = false): NodeInfo[] { 83 | return (figma.currentPage.selection.filter( 84 | ({ type }) => type === "VECTOR" 85 | ) as VectorNode[]).map((node: VectorNode) => { 86 | const pluginData = node.getPluginData("perfect_freehand") 87 | 88 | if (pluginData && updateCenter) { 89 | const center = getCenter(node) 90 | const originalNode = decompressPluginData(pluginData) 91 | if ( 92 | !( 93 | center.x === originalNode.center.x && 94 | center.y === originalNode.center.y 95 | ) 96 | ) { 97 | originalNode.center = center 98 | 99 | node.setPluginData( 100 | "perfect_freehand", 101 | compressToUTF16(JSON.stringify(originalNode)) 102 | ) 103 | } 104 | } 105 | 106 | return { 107 | id: node.id, 108 | name: node.name, 109 | type: node.type, 110 | canReset: !!pluginData, 111 | } 112 | }) 113 | } 114 | 115 | // Getthe currently selected Vector nodes as an array of Ids. 116 | function getSelectedNodeIds() { 117 | return (figma.currentPage.selection.filter( 118 | ({ type }) => type === "VECTOR" 119 | ) as VectorNode[]).map(({ id }) => id) 120 | } 121 | 122 | // Find the center of a node. 123 | function getCenter(node: VectorNode) { 124 | let { x, y, width, height } = node 125 | return { x: x + width / 2, y: y + height / 2 } 126 | } 127 | 128 | // Move a node to a center. 129 | function moveNodeToCenter(node: VectorNode, center: { x: number; y: number }) { 130 | const { x: x0, y: y0 } = getCenter(node) 131 | const { x: x1, y: y1 } = center 132 | 133 | node.x = node.x + x1 - x0 134 | node.y = node.y + y1 - y0 135 | } 136 | 137 | // Zoom the Figma viewport to a node. 138 | function zoomToNode(id: string) { 139 | const node = figma.getNodeById(id) 140 | 141 | if (!node) { 142 | console.error("Could not find that node: " + id) 143 | return 144 | } 145 | 146 | figma.viewport.scrollAndZoomIntoView([node]) 147 | } 148 | 149 | /* -------------------- Selection ------------------- */ 150 | 151 | // Deselect a Figma node. 152 | function deselectNode(id: string) { 153 | const selection = figma.currentPage.selection 154 | figma.currentPage.selection = selection.filter((node) => node.id !== id) 155 | } 156 | 157 | // Send the current selection to the UI state. 158 | function sendSelectedNodes(updateCenter = true) { 159 | const selectedNodes = getSelectedNodes(updateCenter) 160 | 161 | postMessage({ 162 | type: WorkerActionTypes.SELECTED_NODES, 163 | payload: selectedNodes, 164 | }) 165 | } 166 | 167 | /* -------------- Changing VectorNodes -------------- */ 168 | 169 | // Number of new nodes to insert 170 | const SPLIT = 5 171 | 172 | // Some basic easing functions 173 | const EASINGS = { 174 | linear: (t: number) => t, 175 | easeIn: (t: number) => t * t, 176 | easeOut: (t: number) => t * (2 - t), 177 | easeInOut: (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t), 178 | } 179 | 180 | // Compute a stroke based on the vector and apply it to the vector's path data. 181 | function applyPerfectFreehandToVectorNodes( 182 | nodeIds: string[], 183 | { 184 | options, 185 | easing = "linear", 186 | clip, 187 | }: { 188 | options: StrokeOptions 189 | easing: keyof typeof EASINGS 190 | clip: boolean 191 | }, 192 | restrictToKnownNodes = false 193 | ) { 194 | for (let id of nodeIds) { 195 | // Get the node that we want to change 196 | const nodeToChange = figma.getNodeById(id) as VectorNode 197 | 198 | if (!nodeToChange) { 199 | throw Error("Could not find that node: " + id) 200 | } 201 | 202 | // Get the original node 203 | let originalNode = getOriginalNode(nodeToChange.id) 204 | 205 | // If we don't know this node... 206 | if (!originalNode) { 207 | // Bail if we're updating nodes 208 | if (restrictToKnownNodes) continue 209 | // Create a new original node and continue 210 | originalNode = setOriginalNode(nodeToChange) 211 | } 212 | 213 | // Interpolate new points along the vector's curve 214 | const pts: number[][] = [] 215 | 216 | for (let segment of originalNode.vectorNetwork.segments) { 217 | const p0 = originalNode.vectorNetwork.vertices[segment.start] 218 | const p3 = originalNode.vectorNetwork.vertices[segment.end] 219 | 220 | const p1 = addVectors(p0, segment.tangentStart) 221 | const p2 = addVectors(p3, segment.tangentEnd) 222 | 223 | const interpolator = interpolateCubicBezier(p0, p1, p2, p3) 224 | 225 | for (let i = 0; i < SPLIT; i++) { 226 | pts.push(interpolator(i / SPLIT)) 227 | } 228 | } 229 | 230 | // Create a new stroke using perfect-freehand 231 | 232 | const stroke = getStroke(pts, { 233 | ...options, 234 | easing: EASINGS[easing], 235 | last: true, 236 | }) 237 | 238 | try { 239 | // Set stroke to vector paths 240 | nodeToChange.vectorPaths = [ 241 | { 242 | windingRule: "NONZERO", 243 | data: getSvgPathFromStroke(stroke), 244 | }, 245 | ] 246 | } catch (e) { 247 | console.error("Could not apply stroke", e.message) 248 | continue 249 | } 250 | 251 | // Adjust the position of the node so that its center does not change 252 | moveNodeToCenter(nodeToChange, originalNode.center) 253 | } 254 | 255 | sendSelectedNodes(false) 256 | } 257 | 258 | // Reset the node to its original path data, using data from our cache and then delete the node. 259 | function resetVectorNodes() { 260 | for (let id of getSelectedNodeIds()) { 261 | const originalNode = getOriginalNode(id) 262 | 263 | // We haven't modified this node. 264 | if (!originalNode) continue 265 | 266 | const currentNode = figma.getNodeById(id) as VectorNode 267 | 268 | if (!currentNode) { 269 | console.error("Could not find that node: " + id) 270 | continue 271 | } 272 | 273 | currentNode.vectorPaths = originalNode.vectorPaths 274 | 275 | currentNode.setPluginData("perfect_freehand", "") 276 | 277 | sendSelectedNodes(false) 278 | } 279 | } 280 | 281 | /* --------------------- Kickoff -------------------- */ 282 | 283 | // Listen to messages received from the plugin UI 284 | figma.ui.onmessage = function ({ type, payload }: UIAction): void { 285 | switch (type) { 286 | case UIActionTypes.CLOSE: 287 | figma.closePlugin() 288 | break 289 | case UIActionTypes.ZOOM_TO_NODE: 290 | zoomToNode(payload) 291 | break 292 | case UIActionTypes.DESELECT_NODE: 293 | deselectNode(payload) 294 | break 295 | case UIActionTypes.RESET_NODES: 296 | resetVectorNodes() 297 | break 298 | case UIActionTypes.TRANSFORM_NODES: 299 | applyPerfectFreehandToVectorNodes(getSelectedNodeIds(), payload, false) 300 | break 301 | case UIActionTypes.UPDATED_OPTIONS: 302 | applyPerfectFreehandToVectorNodes(getSelectedNodeIds(), payload, true) 303 | break 304 | } 305 | } 306 | 307 | // Listen for selection changes 308 | figma.on("selectionchange", sendSelectedNodes) 309 | 310 | // Show the plugin interface 311 | figma.showUI(__html__, { width: 320, height: 480 }) 312 | 313 | // Send the current selection to the UI 314 | sendSelectedNodes() 315 | -------------------------------------------------------------------------------- /dist/plugin.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 40 | /******/ } 41 | /******/ }; 42 | /******/ 43 | /******/ // define __esModule on exports 44 | /******/ __webpack_require__.r = function(exports) { 45 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 46 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 47 | /******/ } 48 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 49 | /******/ }; 50 | /******/ 51 | /******/ // create a fake namespace object 52 | /******/ // mode & 1: value is a module id, require it 53 | /******/ // mode & 2: merge all properties of value into the ns 54 | /******/ // mode & 4: return value when already ns object 55 | /******/ // mode & 8|1: behave like require 56 | /******/ __webpack_require__.t = function(value, mode) { 57 | /******/ if(mode & 1) value = __webpack_require__(value); 58 | /******/ if(mode & 8) return value; 59 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 60 | /******/ var ns = Object.create(null); 61 | /******/ __webpack_require__.r(ns); 62 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 63 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 64 | /******/ return ns; 65 | /******/ }; 66 | /******/ 67 | /******/ // getDefaultExport function for compatibility with non-harmony modules 68 | /******/ __webpack_require__.n = function(module) { 69 | /******/ var getter = module && module.__esModule ? 70 | /******/ function getDefault() { return module['default']; } : 71 | /******/ function getModuleExports() { return module; }; 72 | /******/ __webpack_require__.d(getter, 'a', getter); 73 | /******/ return getter; 74 | /******/ }; 75 | /******/ 76 | /******/ // Object.prototype.hasOwnProperty.call 77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 78 | /******/ 79 | /******/ // __webpack_public_path__ 80 | /******/ __webpack_require__.p = ""; 81 | /******/ 82 | /******/ 83 | /******/ // Load entry module and return exports 84 | /******/ return __webpack_require__(__webpack_require__.s = "./src/main/index.ts"); 85 | /******/ }) 86 | /************************************************************************/ 87 | /******/ ({ 88 | 89 | /***/ "./node_modules/lz-string/libs/lz-string.js": 90 | /*!**************************************************!*\ 91 | !*** ./node_modules/lz-string/libs/lz-string.js ***! 92 | \**************************************************/ 93 | /*! no static exports found */ 94 | /***/ (function(module, exports, __webpack_require__) { 95 | 96 | var __WEBPACK_AMD_DEFINE_RESULT__;// Copyright (c) 2013 Pieroxy 97 | // This work is free. You can redistribute it and/or modify it 98 | // under the terms of the WTFPL, Version 2 99 | // For more information see LICENSE.txt or http://www.wtfpl.net/ 100 | // 101 | // For more information, the home page: 102 | // http://pieroxy.net/blog/pages/lz-string/testing.html 103 | // 104 | // LZ-based compression algorithm, version 1.4.4 105 | var LZString = (function() { 106 | 107 | // private property 108 | var f = String.fromCharCode; 109 | var keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 110 | var keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; 111 | var baseReverseDic = {}; 112 | 113 | function getBaseValue(alphabet, character) { 114 | if (!baseReverseDic[alphabet]) { 115 | baseReverseDic[alphabet] = {}; 116 | for (var i=0 ; i>> 8; 161 | buf[i*2+1] = current_value % 256; 162 | } 163 | return buf; 164 | }, 165 | 166 | //decompress from uint8array (UCS-2 big endian format) 167 | decompressFromUint8Array:function (compressed) { 168 | if (compressed===null || compressed===undefined){ 169 | return LZString.decompress(compressed); 170 | } else { 171 | var buf=new Array(compressed.length/2); // 2 bytes per character 172 | for (var i=0, TotalLen=buf.length; i> 1; 254 | } 255 | } else { 256 | value = 1; 257 | for (i=0 ; i> 1; 279 | } 280 | } 281 | context_enlargeIn--; 282 | if (context_enlargeIn == 0) { 283 | context_enlargeIn = Math.pow(2, context_numBits); 284 | context_numBits++; 285 | } 286 | delete context_dictionaryToCreate[context_w]; 287 | } else { 288 | value = context_dictionary[context_w]; 289 | for (i=0 ; i> 1; 299 | } 300 | 301 | 302 | } 303 | context_enlargeIn--; 304 | if (context_enlargeIn == 0) { 305 | context_enlargeIn = Math.pow(2, context_numBits); 306 | context_numBits++; 307 | } 308 | // Add wc to the dictionary. 309 | context_dictionary[context_wc] = context_dictSize++; 310 | context_w = String(context_c); 311 | } 312 | } 313 | 314 | // Output the code for w. 315 | if (context_w !== "") { 316 | if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate,context_w)) { 317 | if (context_w.charCodeAt(0)<256) { 318 | for (i=0 ; i> 1; 339 | } 340 | } else { 341 | value = 1; 342 | for (i=0 ; i> 1; 364 | } 365 | } 366 | context_enlargeIn--; 367 | if (context_enlargeIn == 0) { 368 | context_enlargeIn = Math.pow(2, context_numBits); 369 | context_numBits++; 370 | } 371 | delete context_dictionaryToCreate[context_w]; 372 | } else { 373 | value = context_dictionary[context_w]; 374 | for (i=0 ; i> 1; 384 | } 385 | 386 | 387 | } 388 | context_enlargeIn--; 389 | if (context_enlargeIn == 0) { 390 | context_enlargeIn = Math.pow(2, context_numBits); 391 | context_numBits++; 392 | } 393 | } 394 | 395 | // Mark the end of the stream 396 | value = 2; 397 | for (i=0 ; i> 1; 407 | } 408 | 409 | // Flush the last char 410 | while (true) { 411 | context_data_val = (context_data_val << 1); 412 | if (context_data_position == bitsPerChar-1) { 413 | context_data.push(getCharFromInt(context_data_val)); 414 | break; 415 | } 416 | else context_data_position++; 417 | } 418 | return context_data.join(''); 419 | }, 420 | 421 | decompress: function (compressed) { 422 | if (compressed == null) return ""; 423 | if (compressed == "") return null; 424 | return LZString._decompress(compressed.length, 32768, function(index) { return compressed.charCodeAt(index); }); 425 | }, 426 | 427 | _decompress: function (length, resetValue, getNextValue) { 428 | var dictionary = [], 429 | next, 430 | enlargeIn = 4, 431 | dictSize = 4, 432 | numBits = 3, 433 | entry = "", 434 | result = [], 435 | i, 436 | w, 437 | bits, resb, maxpower, power, 438 | c, 439 | data = {val:getNextValue(0), position:resetValue, index:1}; 440 | 441 | for (i = 0; i < 3; i += 1) { 442 | dictionary[i] = i; 443 | } 444 | 445 | bits = 0; 446 | maxpower = Math.pow(2,2); 447 | power=1; 448 | while (power!=maxpower) { 449 | resb = data.val & data.position; 450 | data.position >>= 1; 451 | if (data.position == 0) { 452 | data.position = resetValue; 453 | data.val = getNextValue(data.index++); 454 | } 455 | bits |= (resb>0 ? 1 : 0) * power; 456 | power <<= 1; 457 | } 458 | 459 | switch (next = bits) { 460 | case 0: 461 | bits = 0; 462 | maxpower = Math.pow(2,8); 463 | power=1; 464 | while (power!=maxpower) { 465 | resb = data.val & data.position; 466 | data.position >>= 1; 467 | if (data.position == 0) { 468 | data.position = resetValue; 469 | data.val = getNextValue(data.index++); 470 | } 471 | bits |= (resb>0 ? 1 : 0) * power; 472 | power <<= 1; 473 | } 474 | c = f(bits); 475 | break; 476 | case 1: 477 | bits = 0; 478 | maxpower = Math.pow(2,16); 479 | power=1; 480 | while (power!=maxpower) { 481 | resb = data.val & data.position; 482 | data.position >>= 1; 483 | if (data.position == 0) { 484 | data.position = resetValue; 485 | data.val = getNextValue(data.index++); 486 | } 487 | bits |= (resb>0 ? 1 : 0) * power; 488 | power <<= 1; 489 | } 490 | c = f(bits); 491 | break; 492 | case 2: 493 | return ""; 494 | } 495 | dictionary[3] = c; 496 | w = c; 497 | result.push(c); 498 | while (true) { 499 | if (data.index > length) { 500 | return ""; 501 | } 502 | 503 | bits = 0; 504 | maxpower = Math.pow(2,numBits); 505 | power=1; 506 | while (power!=maxpower) { 507 | resb = data.val & data.position; 508 | data.position >>= 1; 509 | if (data.position == 0) { 510 | data.position = resetValue; 511 | data.val = getNextValue(data.index++); 512 | } 513 | bits |= (resb>0 ? 1 : 0) * power; 514 | power <<= 1; 515 | } 516 | 517 | switch (c = bits) { 518 | case 0: 519 | bits = 0; 520 | maxpower = Math.pow(2,8); 521 | power=1; 522 | while (power!=maxpower) { 523 | resb = data.val & data.position; 524 | data.position >>= 1; 525 | if (data.position == 0) { 526 | data.position = resetValue; 527 | data.val = getNextValue(data.index++); 528 | } 529 | bits |= (resb>0 ? 1 : 0) * power; 530 | power <<= 1; 531 | } 532 | 533 | dictionary[dictSize++] = f(bits); 534 | c = dictSize-1; 535 | enlargeIn--; 536 | break; 537 | case 1: 538 | bits = 0; 539 | maxpower = Math.pow(2,16); 540 | power=1; 541 | while (power!=maxpower) { 542 | resb = data.val & data.position; 543 | data.position >>= 1; 544 | if (data.position == 0) { 545 | data.position = resetValue; 546 | data.val = getNextValue(data.index++); 547 | } 548 | bits |= (resb>0 ? 1 : 0) * power; 549 | power <<= 1; 550 | } 551 | dictionary[dictSize++] = f(bits); 552 | c = dictSize-1; 553 | enlargeIn--; 554 | break; 555 | case 2: 556 | return result.join(''); 557 | } 558 | 559 | if (enlargeIn == 0) { 560 | enlargeIn = Math.pow(2, numBits); 561 | numBits++; 562 | } 563 | 564 | if (dictionary[c]) { 565 | entry = dictionary[c]; 566 | } else { 567 | if (c === dictSize) { 568 | entry = w + w.charAt(0); 569 | } else { 570 | return null; 571 | } 572 | } 573 | result.push(entry); 574 | 575 | // Add w+entry[0] to the dictionary. 576 | dictionary[dictSize++] = w + entry.charAt(0); 577 | enlargeIn--; 578 | 579 | w = entry; 580 | 581 | if (enlargeIn == 0) { 582 | enlargeIn = Math.pow(2, numBits); 583 | numBits++; 584 | } 585 | 586 | } 587 | } 588 | }; 589 | return LZString; 590 | })(); 591 | 592 | if (true) { 593 | !(__WEBPACK_AMD_DEFINE_RESULT__ = (function () { return LZString; }).call(exports, __webpack_require__, exports, module), 594 | __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 595 | } else {} 596 | 597 | 598 | /***/ }), 599 | 600 | /***/ "./node_modules/perfect-freehand/dist/perfect-freehand.esm.js": 601 | /*!********************************************************************!*\ 602 | !*** ./node_modules/perfect-freehand/dist/perfect-freehand.esm.js ***! 603 | \********************************************************************/ 604 | /*! exports provided: default, getStrokeOutlinePoints, getStrokePoints */ 605 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 606 | 607 | "use strict"; 608 | __webpack_require__.r(__webpack_exports__); 609 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getStrokeOutlinePoints", function() { return getStrokeOutlinePoints; }); 610 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getStrokePoints", function() { return getStrokePoints; }); 611 | function lerp(y1, y2, mu) { 612 | return y1 * (1 - mu) + y2 * mu; 613 | } 614 | function clamp(n, a, b) { 615 | return Math.max(a, Math.min(b, n)); 616 | } 617 | /** 618 | * Convert an array of points to the correct format ([x, y, radius]) 619 | * @param points 620 | * @returns 621 | */ 622 | 623 | function toPointsArray(points) { 624 | if (Array.isArray(points[0])) { 625 | return points.map(function (_ref) { 626 | var x = _ref[0], 627 | y = _ref[1], 628 | _ref$ = _ref[2], 629 | pressure = _ref$ === void 0 ? 0.5 : _ref$; 630 | return [x, y, pressure]; 631 | }); 632 | } else { 633 | return points.map(function (_ref2) { 634 | var x = _ref2.x, 635 | y = _ref2.y, 636 | _ref2$pressure = _ref2.pressure, 637 | pressure = _ref2$pressure === void 0 ? 0.5 : _ref2$pressure; 638 | return [x, y, pressure]; 639 | }); 640 | } 641 | } 642 | /** 643 | * Compute a radius based on the pressure. 644 | * @param size 645 | * @param thinning 646 | * @param easing 647 | * @param pressure 648 | * @returns 649 | */ 650 | 651 | function getStrokeRadius(size, thinning, easing, pressure) { 652 | if (pressure === void 0) { 653 | pressure = 0.5; 654 | } 655 | 656 | if (!thinning) return size / 2; 657 | pressure = clamp(easing(pressure), 0, 1); 658 | return (thinning < 0 ? lerp(size, size + size * clamp(thinning, -0.95, -0.05), pressure) : lerp(size - size * clamp(thinning, 0.05, 0.95), size, pressure)) / 2; 659 | } 660 | 661 | /** 662 | * Negate a vector. 663 | * @param A 664 | */ 665 | /** 666 | * Add vectors. 667 | * @param A 668 | * @param B 669 | */ 670 | 671 | function add(A, B) { 672 | return [A[0] + B[0], A[1] + B[1]]; 673 | } 674 | /** 675 | * Subtract vectors. 676 | * @param A 677 | * @param B 678 | */ 679 | 680 | function sub(A, B) { 681 | return [A[0] - B[0], A[1] - B[1]]; 682 | } 683 | /** 684 | * Get the vector from vectors A to B. 685 | * @param A 686 | * @param B 687 | */ 688 | 689 | function vec(A, B) { 690 | // A, B as vectors get the vector from A to B 691 | return [B[0] - A[0], B[1] - A[1]]; 692 | } 693 | /** 694 | * Vector multiplication by scalar 695 | * @param A 696 | * @param n 697 | */ 698 | 699 | function mul(A, n) { 700 | return [A[0] * n, A[1] * n]; 701 | } 702 | /** 703 | * Vector division by scalar. 704 | * @param A 705 | * @param n 706 | */ 707 | 708 | function div(A, n) { 709 | return [A[0] / n, A[1] / n]; 710 | } 711 | /** 712 | * Perpendicular rotation of a vector A 713 | * @param A 714 | */ 715 | 716 | function per(A) { 717 | return [A[1], -A[0]]; 718 | } 719 | /** 720 | * Dot product 721 | * @param A 722 | * @param B 723 | */ 724 | 725 | function dpr(A, B) { 726 | return A[0] * B[0] + A[1] * B[1]; 727 | } 728 | /** 729 | * Length of the vector 730 | * @param A 731 | */ 732 | 733 | function len(A) { 734 | return Math.hypot(A[0], A[1]); 735 | } 736 | /** 737 | * Get normalized / unit vector. 738 | * @param A 739 | */ 740 | 741 | function uni(A) { 742 | return div(A, len(A)); 743 | } 744 | /** 745 | * Dist length from A to B 746 | * @param A 747 | * @param B 748 | */ 749 | 750 | function dist(A, B) { 751 | return Math.hypot(A[1] - B[1], A[0] - B[0]); 752 | } 753 | /** 754 | * Rotate a vector around another vector by r (radians) 755 | * @param A vector 756 | * @param C center 757 | * @param r rotation in radians 758 | */ 759 | 760 | function rotAround(A, C, r) { 761 | var s = Math.sin(r); 762 | var c = Math.cos(r); 763 | var px = A[0] - C[0]; 764 | var py = A[1] - C[1]; 765 | var nx = px * c - py * s; 766 | var ny = px * s + py * c; 767 | return [nx + C[0], ny + C[1]]; 768 | } 769 | /** 770 | * Interpolate vector A to B with a scalar t 771 | * @param A 772 | * @param B 773 | * @param t scalar 774 | */ 775 | 776 | function lrp(A, B, t) { 777 | return add(A, mul(vec(A, B), t)); 778 | } 779 | 780 | var min = Math.min, 781 | PI = Math.PI; 782 | /** 783 | * ## getStrokePoints 784 | * @description Get points for a stroke. 785 | * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. 786 | * @param streamline How much to streamline the stroke. 787 | * @param size The stroke's size. 788 | */ 789 | 790 | function getStrokePoints(points, options) { 791 | var _options$simulatePres = options.simulatePressure, 792 | simulatePressure = _options$simulatePres === void 0 ? true : _options$simulatePres, 793 | _options$streamline = options.streamline, 794 | streamline = _options$streamline === void 0 ? 0.5 : _options$streamline, 795 | _options$size = options.size, 796 | size = _options$size === void 0 ? 8 : _options$size; 797 | streamline /= 2; 798 | 799 | if (!simulatePressure) { 800 | streamline /= 2; 801 | } 802 | 803 | var pts = toPointsArray(points); 804 | var len = pts.length; 805 | if (len === 0) return []; 806 | if (len === 1) pts.push(add(pts[0], [1, 0])); 807 | var strokePoints = [{ 808 | point: [pts[0][0], pts[0][1]], 809 | pressure: pts[0][2], 810 | vector: [0, 0], 811 | distance: 0, 812 | runningLength: 0 813 | }]; 814 | 815 | for (var i = 1, curr = pts[i], prev = strokePoints[0]; i < pts.length; i++, curr = pts[i], prev = strokePoints[i - 1]) { 816 | var point = lrp(prev.point, curr, 1 - streamline), 817 | pressure = curr[2], 818 | vector = uni(vec(point, prev.point)), 819 | distance = dist(point, prev.point), 820 | runningLength = prev.runningLength + distance; 821 | strokePoints.push({ 822 | point: point, 823 | pressure: pressure, 824 | vector: vector, 825 | distance: distance, 826 | runningLength: runningLength 827 | }); 828 | } 829 | /* 830 | Align vectors at the end of the line 831 | Starting from the last point, work back until we've traveled more than 832 | half of the line's size (width). Take the current point's vector and then 833 | work forward, setting all remaining points' vectors to this vector. This 834 | removes the "noise" at the end of the line and allows for a better-facing 835 | end cap. 836 | */ 837 | 838 | 839 | var totalLength = strokePoints[len - 1].runningLength; 840 | 841 | for (var _i = len - 2; _i > 1; _i--) { 842 | var _strokePoints$_i = strokePoints[_i], 843 | _runningLength = _strokePoints$_i.runningLength, 844 | _vector = _strokePoints$_i.vector; 845 | 846 | if (totalLength - _runningLength > size / 2 || dpr(strokePoints[_i - 1].vector, strokePoints[_i].vector) < 0.8) { 847 | for (var j = _i; j < len; j++) { 848 | strokePoints[j].vector = _vector; 849 | } 850 | 851 | break; 852 | } 853 | } 854 | 855 | return strokePoints; 856 | } 857 | /** 858 | * ## getStrokeOutlinePoints 859 | * @description Get an array of points (as `[x, y]`) representing the outline of a stroke. 860 | * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. 861 | * @param options An (optional) object with options. 862 | * @param options.size The base size (diameter) of the stroke. 863 | * @param options.thinning The effect of pressure on the stroke's size. 864 | * @param options.smoothing How much to soften the stroke's edges. 865 | * @param options.easing An easing function to apply to each point's pressure. 866 | * @param options.simulatePressure Whether to simulate pressure based on velocity. 867 | * @param options.start Tapering and easing function for the start of the line. 868 | * @param options.end Tapering and easing function for the end of the line. 869 | * @param options.last Whether to handle the points as a completed stroke. 870 | */ 871 | 872 | function getStrokeOutlinePoints(points, options) { 873 | if (options === void 0) { 874 | options = {}; 875 | } 876 | 877 | var _options = options, 878 | _options$size2 = _options.size, 879 | size = _options$size2 === void 0 ? 8 : _options$size2, 880 | _options$thinning = _options.thinning, 881 | thinning = _options$thinning === void 0 ? 0.5 : _options$thinning, 882 | _options$smoothing = _options.smoothing, 883 | smoothing = _options$smoothing === void 0 ? 0.5 : _options$smoothing, 884 | _options$simulatePres2 = _options.simulatePressure, 885 | simulatePressure = _options$simulatePres2 === void 0 ? true : _options$simulatePres2, 886 | _options$easing = _options.easing, 887 | easing = _options$easing === void 0 ? function (t) { 888 | return t; 889 | } : _options$easing, 890 | _options$start = _options.start, 891 | start = _options$start === void 0 ? {} : _options$start, 892 | _options$end = _options.end, 893 | end = _options$end === void 0 ? {} : _options$end, 894 | _options$last = _options.last, 895 | isComplete = _options$last === void 0 ? false : _options$last; 896 | var _options2 = options, 897 | _options2$streamline = _options2.streamline, 898 | streamline = _options2$streamline === void 0 ? 0.5 : _options2$streamline; 899 | streamline /= 2; 900 | var _start$taper = start.taper, 901 | taperStart = _start$taper === void 0 ? 0 : _start$taper, 902 | _start$easing = start.easing, 903 | taperStartEase = _start$easing === void 0 ? function (t) { 904 | return t * (2 - t); 905 | } : _start$easing; 906 | var _end$taper = end.taper, 907 | taperEnd = _end$taper === void 0 ? 0 : _end$taper, 908 | _end$easing = end.easing, 909 | taperEndEase = _end$easing === void 0 ? function (t) { 910 | return --t * t * t + 1; 911 | } : _end$easing; // The number of points in the array 912 | 913 | var len = points.length; // We can't do anything with an empty array. 914 | 915 | if (len === 0) return []; // The total length of the line 916 | 917 | var totalLength = points[len - 1].runningLength; // Our collected left and right points 918 | 919 | var leftPts = []; 920 | var rightPts = []; // Previous pressure (start with average of first five pressures) 921 | 922 | var prevPressure = points.slice(0, 5).reduce(function (acc, cur) { 923 | return (acc + cur.pressure) / 2; 924 | }, points[0].pressure); // The current radius 925 | 926 | var radius = getStrokeRadius(size, thinning, easing, points[len - 1].pressure); // Previous vector 927 | 928 | var prevVector = points[0].vector; // Previous left and right points 929 | 930 | var pl = points[0].point; 931 | var pr = pl; // Temporary left and right points 932 | 933 | var tl = pl; 934 | var tr = pr; 935 | /* 936 | Find the outline's left and right points 937 | Iterating through the points and populate the rightPts and leftPts arrays, 938 | skipping the first and last pointsm, which will get caps later on. 939 | */ 940 | 941 | for (var i = 1; i < len - 1; i++) { 942 | var _points$i = points[i], 943 | point = _points$i.point, 944 | pressure = _points$i.pressure, 945 | vector = _points$i.vector, 946 | distance = _points$i.distance, 947 | runningLength = _points$i.runningLength; 948 | /* 949 | Calculate the radius 950 | If not thinning, the current point's radius will be half the size; or 951 | otherwise, the size will be based on the current (real or simulated) 952 | pressure. 953 | */ 954 | 955 | if (thinning) { 956 | if (simulatePressure) { 957 | var rp = min(1, 1 - distance / size); 958 | var sp = min(1, distance / size); 959 | pressure = min(1, prevPressure + (rp - prevPressure) * (sp / 2)); 960 | } 961 | 962 | radius = getStrokeRadius(size, thinning, easing, pressure); 963 | } else { 964 | radius = size / 2; 965 | } 966 | /* 967 | Apply tapering 968 | If the current length is within the taper distance at either the 969 | start or the end, calculate the taper strengths. Apply the smaller 970 | of the two taper strengths to the radius. 971 | */ 972 | 973 | 974 | var ts = runningLength < taperStart ? taperStartEase(runningLength / taperStart) : 1; 975 | var te = totalLength - runningLength < taperEnd ? taperEndEase((totalLength - runningLength) / taperEnd) : 1; 976 | radius *= Math.min(ts, te); 977 | /* 978 | Handle sharp corners 979 | Find the difference (dot product) between the current and next vector. 980 | If the next vector is at more than a right angle to the current vector, 981 | draw a cap at the current point. 982 | */ 983 | 984 | var nextVector = points[i + 1].vector; 985 | var dpr$1 = dpr(vector, nextVector); 986 | 987 | if (dpr$1 < 0) { 988 | var _offset = mul(per(prevVector), radius); 989 | 990 | var la = add(point, _offset); 991 | var ra = sub(point, _offset); 992 | 993 | for (var t = 0.2; t < 1; t += 0.2) { 994 | tr = rotAround(la, point, PI * -t); 995 | tl = rotAround(ra, point, PI * t); 996 | rightPts.push(tr); 997 | leftPts.push(tl); 998 | } 999 | 1000 | pl = tl; 1001 | pr = tr; 1002 | continue; 1003 | } 1004 | /* 1005 | Add regular points 1006 | Project points to either side of the current point, using the 1007 | calculated size as a distance. If a point's distance to the 1008 | previous point on that side greater than the minimum distance 1009 | (or if the corner is kinda sharp), add the points to the side's 1010 | points array. 1011 | */ 1012 | 1013 | 1014 | var offset = mul(per(lrp(nextVector, vector, dpr$1)), radius); 1015 | tl = sub(point, offset); 1016 | tr = add(point, offset); 1017 | var alwaysAdd = i === 1 || dpr$1 < 0.25; 1018 | var minDistance = (runningLength > size ? size : size / 2) * smoothing; 1019 | 1020 | if (alwaysAdd || dist(pl, tl) > minDistance) { 1021 | leftPts.push(lrp(pl, tl, streamline)); 1022 | pl = tl; 1023 | } 1024 | 1025 | if (alwaysAdd || dist(pr, tr) > minDistance) { 1026 | rightPts.push(lrp(pr, tr, streamline)); 1027 | pr = tr; 1028 | } // Set variables for next iteration 1029 | 1030 | 1031 | prevPressure = pressure; 1032 | prevVector = vector; 1033 | } 1034 | /* 1035 | Drawing caps 1036 | 1037 | Now that we have our points on either side of the line, we need to 1038 | draw caps at the start and end. Tapered lines don't have caps, but 1039 | may have dots for very short lines. 1040 | */ 1041 | 1042 | 1043 | var firstPoint = points[0]; 1044 | var lastPoint = points[len - 1]; 1045 | var isVeryShort = rightPts.length < 2 || leftPts.length < 2; 1046 | /* 1047 | Draw a dot for very short or completed strokes 1048 | 1049 | If the line is too short to gather left or right points and if the line is 1050 | not tapered on either side, draw a dot. If the line is tapered, then only 1051 | draw a dot if the line is both very short and complete. If we draw a dot, 1052 | we can just return those points. 1053 | */ 1054 | 1055 | if (isVeryShort && (!(taperStart || taperEnd) || isComplete)) { 1056 | var ir = 0; 1057 | 1058 | for (var _i2 = 0; _i2 < len; _i2++) { 1059 | var _points$_i = points[_i2], 1060 | _pressure = _points$_i.pressure, 1061 | _runningLength2 = _points$_i.runningLength; 1062 | 1063 | if (_runningLength2 > size) { 1064 | ir = getStrokeRadius(size, thinning, easing, _pressure); 1065 | break; 1066 | } 1067 | } 1068 | 1069 | var _start = sub(firstPoint.point, mul(per(uni(vec(lastPoint.point, firstPoint.point))), ir || radius)); 1070 | 1071 | var dotPts = []; 1072 | 1073 | for (var _t = 0, step = 0.1; _t <= 1; _t += step) { 1074 | dotPts.push(rotAround(_start, firstPoint.point, PI * 2 * _t)); 1075 | } 1076 | 1077 | return dotPts; 1078 | } 1079 | /* 1080 | Draw a start cap 1081 | Unless the line has a tapered start, or unless the line has a tapered end 1082 | and the line is very short, draw a start cap around the first point. Use 1083 | the distance between the second left and right point for the cap's radius. 1084 | Finally remove the first left and right points. :psyduck: 1085 | */ 1086 | 1087 | 1088 | var startCap = []; 1089 | 1090 | if (!taperStart && !(taperEnd && isVeryShort)) { 1091 | tr = rightPts[1]; 1092 | tl = leftPts[1]; 1093 | 1094 | var _start2 = sub(firstPoint.point, mul(uni(vec(tr, tl)), dist(tr, tl) / 2)); 1095 | 1096 | for (var _t2 = 0, _step = 0.2; _t2 <= 1; _t2 += _step) { 1097 | startCap.push(rotAround(_start2, firstPoint.point, PI * _t2)); 1098 | } 1099 | 1100 | leftPts.shift(); 1101 | rightPts.shift(); 1102 | } 1103 | /* 1104 | Draw an end cap 1105 | If the line does not have a tapered end, and unless the line has a tapered 1106 | start and the line is very short, draw a cap around the last point. Finally, 1107 | remove the last left and right points. Otherwise, add the last point. Note 1108 | that This cap is a full-turn-and-a-half: this prevents incorrect caps on 1109 | sharp end turns. 1110 | */ 1111 | 1112 | 1113 | var endCap = []; 1114 | 1115 | if (!taperEnd && !(taperStart && isVeryShort)) { 1116 | var _start3 = sub(lastPoint.point, mul(per(lastPoint.vector), radius)); 1117 | 1118 | for (var _t3 = 0, _step2 = 0.1; _t3 <= 1; _t3 += _step2) { 1119 | endCap.push(rotAround(_start3, lastPoint.point, PI * 3 * _t3)); 1120 | } 1121 | } else { 1122 | endCap.push(lastPoint.point); 1123 | } 1124 | /* 1125 | Return the points in the correct windind order: begin on the left side, then 1126 | continue around the end cap, then come back along the right side, and finally 1127 | complete the start cap. 1128 | */ 1129 | 1130 | 1131 | return leftPts.concat(endCap, rightPts.reverse(), startCap); 1132 | } 1133 | /** 1134 | * ## getStroke 1135 | * @description Returns a stroke as an array of outline points. 1136 | * @param points An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional. 1137 | * @param options An (optional) object with options. 1138 | * @param options.size The base size (diameter) of the stroke. 1139 | * @param options.thinning The effect of pressure on the stroke's size. 1140 | * @param options.smoothing How much to soften the stroke's edges. 1141 | * @param options.easing An easing function to apply to each point's pressure. 1142 | * @param options.simulatePressure Whether to simulate pressure based on velocity. 1143 | * @param options.start Tapering and easing function for the start of the line. 1144 | * @param options.end Tapering and easing function for the end of the line. 1145 | * @param options.last Whether to handle the points as a completed stroke. 1146 | */ 1147 | 1148 | function getStroke(points, options) { 1149 | if (options === void 0) { 1150 | options = {}; 1151 | } 1152 | 1153 | return getStrokeOutlinePoints(getStrokePoints(points, options), options); 1154 | } 1155 | 1156 | /* harmony default export */ __webpack_exports__["default"] = (getStroke); 1157 | 1158 | //# sourceMappingURL=perfect-freehand.esm.js.map 1159 | 1160 | 1161 | /***/ }), 1162 | 1163 | /***/ "./src/main/index.ts": 1164 | /*!***************************!*\ 1165 | !*** ./src/main/index.ts ***! 1166 | \***************************/ 1167 | /*! no exports provided */ 1168 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 1169 | 1170 | "use strict"; 1171 | __webpack_require__.r(__webpack_exports__); 1172 | /* harmony import */ var _types__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../types */ "./src/types.ts"); 1173 | /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./src/utils.ts"); 1174 | /* harmony import */ var perfect_freehand__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! perfect-freehand */ "./node_modules/perfect-freehand/dist/perfect-freehand.esm.js"); 1175 | /* harmony import */ var lz_string__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! lz-string */ "./node_modules/lz-string/libs/lz-string.js"); 1176 | /* harmony import */ var lz_string__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(lz_string__WEBPACK_IMPORTED_MODULE_3__); 1177 | 1178 | 1179 | 1180 | 1181 | /* ----------------------- Comms ----------------------- */ 1182 | // Sends a message to the plugin UI 1183 | function postMessage({ type, payload }) { 1184 | figma.ui.postMessage({ type, payload }); 1185 | } 1186 | // Save some information about the node to its plugin data. 1187 | function setOriginalNode(node) { 1188 | const originalNode = { 1189 | center: getCenter(node), 1190 | vectorNetwork: Object.assign({}, node.vectorNetwork), 1191 | vectorPaths: node.vectorPaths, 1192 | }; 1193 | node.setPluginData("perfect_freehand", Object(lz_string__WEBPACK_IMPORTED_MODULE_3__["compressToUTF16"])(JSON.stringify(originalNode))); 1194 | return originalNode; 1195 | } 1196 | function decompressPluginData(pluginData) { 1197 | // Decompress the saved data and parse out the original node. 1198 | const decompressed = Object(lz_string__WEBPACK_IMPORTED_MODULE_3__["decompressFromUTF16"])(pluginData); 1199 | if (!decompressed) { 1200 | throw Error("Found saved data for original node but could not decompress it: " + 1201 | decompressed); 1202 | } 1203 | return JSON.parse(decompressed); 1204 | } 1205 | // Get an original node from a node's plugin data. 1206 | function getOriginalNode(id) { 1207 | let node = figma.getNodeById(id); 1208 | if (!node) 1209 | throw Error("Could not find that node: " + id); 1210 | const pluginData = node.getPluginData("perfect_freehand"); 1211 | // Nothing on the node — we haven't modified it. 1212 | if (!pluginData) 1213 | return undefined; 1214 | return decompressPluginData(pluginData); 1215 | } 1216 | /* ---------------------- Nodes --------------------- */ 1217 | // Get the currently selected Vector nodes for the UI. 1218 | function getSelectedNodes(updateCenter = false) { 1219 | return figma.currentPage.selection.filter(({ type }) => type === "VECTOR").map((node) => { 1220 | const pluginData = node.getPluginData("perfect_freehand"); 1221 | if (pluginData && updateCenter) { 1222 | const center = getCenter(node); 1223 | const originalNode = decompressPluginData(pluginData); 1224 | if (!(center.x === originalNode.center.x && 1225 | center.y === originalNode.center.y)) { 1226 | originalNode.center = center; 1227 | node.setPluginData("perfect_freehand", Object(lz_string__WEBPACK_IMPORTED_MODULE_3__["compressToUTF16"])(JSON.stringify(originalNode))); 1228 | } 1229 | } 1230 | return { 1231 | id: node.id, 1232 | name: node.name, 1233 | type: node.type, 1234 | canReset: !!pluginData, 1235 | }; 1236 | }); 1237 | } 1238 | // Getthe currently selected Vector nodes as an array of Ids. 1239 | function getSelectedNodeIds() { 1240 | return figma.currentPage.selection.filter(({ type }) => type === "VECTOR").map(({ id }) => id); 1241 | } 1242 | // Find the center of a node. 1243 | function getCenter(node) { 1244 | let { x, y, width, height } = node; 1245 | return { x: x + width / 2, y: y + height / 2 }; 1246 | } 1247 | // Move a node to a center. 1248 | function moveNodeToCenter(node, center) { 1249 | const { x: x0, y: y0 } = getCenter(node); 1250 | const { x: x1, y: y1 } = center; 1251 | node.x = node.x + x1 - x0; 1252 | node.y = node.y + y1 - y0; 1253 | } 1254 | // Zoom the Figma viewport to a node. 1255 | function zoomToNode(id) { 1256 | const node = figma.getNodeById(id); 1257 | if (!node) { 1258 | console.error("Could not find that node: " + id); 1259 | return; 1260 | } 1261 | figma.viewport.scrollAndZoomIntoView([node]); 1262 | } 1263 | /* -------------------- Selection ------------------- */ 1264 | // Deselect a Figma node. 1265 | function deselectNode(id) { 1266 | const selection = figma.currentPage.selection; 1267 | figma.currentPage.selection = selection.filter((node) => node.id !== id); 1268 | } 1269 | // Send the current selection to the UI state. 1270 | function sendSelectedNodes(updateCenter = true) { 1271 | const selectedNodes = getSelectedNodes(updateCenter); 1272 | postMessage({ 1273 | type: _types__WEBPACK_IMPORTED_MODULE_0__["WorkerActionTypes"].SELECTED_NODES, 1274 | payload: selectedNodes, 1275 | }); 1276 | } 1277 | /* -------------- Changing VectorNodes -------------- */ 1278 | // Number of new nodes to insert 1279 | const SPLIT = 5; 1280 | // Some basic easing functions 1281 | const EASINGS = { 1282 | linear: (t) => t, 1283 | easeIn: (t) => t * t, 1284 | easeOut: (t) => t * (2 - t), 1285 | easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t), 1286 | }; 1287 | // Compute a stroke based on the vector and apply it to the vector's path data. 1288 | function applyPerfectFreehandToVectorNodes(nodeIds, { options, easing = "linear", clip, }, restrictToKnownNodes = false) { 1289 | for (let id of nodeIds) { 1290 | // Get the node that we want to change 1291 | const nodeToChange = figma.getNodeById(id); 1292 | if (!nodeToChange) { 1293 | throw Error("Could not find that node: " + id); 1294 | } 1295 | // Get the original node 1296 | let originalNode = getOriginalNode(nodeToChange.id); 1297 | // If we don't know this node... 1298 | if (!originalNode) { 1299 | // Bail if we're updating nodes 1300 | if (restrictToKnownNodes) 1301 | continue; 1302 | // Create a new original node and continue 1303 | originalNode = setOriginalNode(nodeToChange); 1304 | } 1305 | // Interpolate new points along the vector's curve 1306 | const pts = []; 1307 | for (let segment of originalNode.vectorNetwork.segments) { 1308 | const p0 = originalNode.vectorNetwork.vertices[segment.start]; 1309 | const p3 = originalNode.vectorNetwork.vertices[segment.end]; 1310 | const p1 = Object(_utils__WEBPACK_IMPORTED_MODULE_1__["addVectors"])(p0, segment.tangentStart); 1311 | const p2 = Object(_utils__WEBPACK_IMPORTED_MODULE_1__["addVectors"])(p3, segment.tangentEnd); 1312 | const interpolator = Object(_utils__WEBPACK_IMPORTED_MODULE_1__["interpolateCubicBezier"])(p0, p1, p2, p3); 1313 | for (let i = 0; i < SPLIT; i++) { 1314 | pts.push(interpolator(i / SPLIT)); 1315 | } 1316 | } 1317 | // Create a new stroke using perfect-freehand 1318 | const stroke = Object(perfect_freehand__WEBPACK_IMPORTED_MODULE_2__["default"])(pts, Object.assign(Object.assign({}, options), { easing: EASINGS[easing], last: true })); 1319 | try { 1320 | // Set stroke to vector paths 1321 | nodeToChange.vectorPaths = [ 1322 | { 1323 | windingRule: "NONZERO", 1324 | data: Object(_utils__WEBPACK_IMPORTED_MODULE_1__["getSvgPathFromStroke"])(stroke), 1325 | }, 1326 | ]; 1327 | } 1328 | catch (e) { 1329 | console.error("Could not apply stroke", e.message); 1330 | continue; 1331 | } 1332 | // Adjust the position of the node so that its center does not change 1333 | moveNodeToCenter(nodeToChange, originalNode.center); 1334 | } 1335 | sendSelectedNodes(false); 1336 | } 1337 | // Reset the node to its original path data, using data from our cache and then delete the node. 1338 | function resetVectorNodes() { 1339 | for (let id of getSelectedNodeIds()) { 1340 | const originalNode = getOriginalNode(id); 1341 | // We haven't modified this node. 1342 | if (!originalNode) 1343 | continue; 1344 | const currentNode = figma.getNodeById(id); 1345 | if (!currentNode) { 1346 | console.error("Could not find that node: " + id); 1347 | continue; 1348 | } 1349 | currentNode.vectorPaths = originalNode.vectorPaths; 1350 | currentNode.setPluginData("perfect_freehand", ""); 1351 | sendSelectedNodes(false); 1352 | } 1353 | } 1354 | /* --------------------- Kickoff -------------------- */ 1355 | // Listen to messages received from the plugin UI 1356 | figma.ui.onmessage = function ({ type, payload }) { 1357 | switch (type) { 1358 | case _types__WEBPACK_IMPORTED_MODULE_0__["UIActionTypes"].CLOSE: 1359 | figma.closePlugin(); 1360 | break; 1361 | case _types__WEBPACK_IMPORTED_MODULE_0__["UIActionTypes"].ZOOM_TO_NODE: 1362 | zoomToNode(payload); 1363 | break; 1364 | case _types__WEBPACK_IMPORTED_MODULE_0__["UIActionTypes"].DESELECT_NODE: 1365 | deselectNode(payload); 1366 | break; 1367 | case _types__WEBPACK_IMPORTED_MODULE_0__["UIActionTypes"].RESET_NODES: 1368 | resetVectorNodes(); 1369 | break; 1370 | case _types__WEBPACK_IMPORTED_MODULE_0__["UIActionTypes"].TRANSFORM_NODES: 1371 | applyPerfectFreehandToVectorNodes(getSelectedNodeIds(), payload, false); 1372 | break; 1373 | case _types__WEBPACK_IMPORTED_MODULE_0__["UIActionTypes"].UPDATED_OPTIONS: 1374 | applyPerfectFreehandToVectorNodes(getSelectedNodeIds(), payload, true); 1375 | break; 1376 | } 1377 | }; 1378 | // Listen for selection changes 1379 | figma.on("selectionchange", sendSelectedNodes); 1380 | // Show the plugin interface 1381 | figma.showUI(__html__, { width: 320, height: 480 }); 1382 | // Send the current selection to the UI 1383 | sendSelectedNodes(); 1384 | 1385 | 1386 | /***/ }), 1387 | 1388 | /***/ "./src/types.ts": 1389 | /*!**********************!*\ 1390 | !*** ./src/types.ts ***! 1391 | \**********************/ 1392 | /*! exports provided: UIActionTypes, WorkerActionTypes */ 1393 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 1394 | 1395 | "use strict"; 1396 | __webpack_require__.r(__webpack_exports__); 1397 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "UIActionTypes", function() { return UIActionTypes; }); 1398 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WorkerActionTypes", function() { return WorkerActionTypes; }); 1399 | // UI actions 1400 | var UIActionTypes; 1401 | (function (UIActionTypes) { 1402 | UIActionTypes["CLOSE"] = "CLOSE"; 1403 | UIActionTypes["ZOOM_TO_NODE"] = "ZOOM_TO_NODE"; 1404 | UIActionTypes["DESELECT_NODE"] = "DESELECT_NODE"; 1405 | UIActionTypes["TRANSFORM_NODES"] = "TRANSFORM_NODES"; 1406 | UIActionTypes["RESET_NODES"] = "RESET_NODES"; 1407 | UIActionTypes["UPDATED_OPTIONS"] = "UPDATED_OPTIONS"; 1408 | })(UIActionTypes || (UIActionTypes = {})); 1409 | // Worker actions 1410 | var WorkerActionTypes; 1411 | (function (WorkerActionTypes) { 1412 | WorkerActionTypes["SELECTED_NODES"] = "SELECTED_NODES"; 1413 | WorkerActionTypes["FOUND_SELECTED_NODES"] = "FOUND_SELECTED_NODES"; 1414 | })(WorkerActionTypes || (WorkerActionTypes = {})); 1415 | 1416 | 1417 | /***/ }), 1418 | 1419 | /***/ "./src/utils.ts": 1420 | /*!**********************!*\ 1421 | !*** ./src/utils.ts ***! 1422 | \**********************/ 1423 | /*! exports provided: cubicBezier, getPointsAlongCubicBezier, interpolateCubicBezier, addVectors, getSvgPathFromStroke */ 1424 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 1425 | 1426 | "use strict"; 1427 | __webpack_require__.r(__webpack_exports__); 1428 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "cubicBezier", function() { return cubicBezier; }); 1429 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getPointsAlongCubicBezier", function() { return getPointsAlongCubicBezier; }); 1430 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "interpolateCubicBezier", function() { return interpolateCubicBezier; }); 1431 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "addVectors", function() { return addVectors; }); 1432 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getSvgPathFromStroke", function() { return getSvgPathFromStroke; }); 1433 | // import polygonClipping from "polygon-clipping" 1434 | const { pow } = Math; 1435 | function cubicBezier(tx, x1, y1, x2, y2) { 1436 | // Inspired by Don Lancaster's two articles 1437 | // http://www.tinaja.com/glib/cubemath.pdf 1438 | // http://www.tinaja.com/text/bezmath.html 1439 | // Set p0 and p1 point 1440 | let x0 = 0, y0 = 0, x3 = 1, y3 = 1, 1441 | // Convert the coordinates to equation space 1442 | A = x3 - 3 * x2 + 3 * x1 - x0, B = 3 * x2 - 6 * x1 + 3 * x0, C = 3 * x1 - 3 * x0, D = x0, E = y3 - 3 * y2 + 3 * y1 - y0, F = 3 * y2 - 6 * y1 + 3 * y0, G = 3 * y1 - 3 * y0, H = y0, 1443 | // Variables for the loop below 1444 | t = tx, iterations = 5, i, slope, x, y; 1445 | // Loop through a few times to get a more accurate time value, according to the Newton-Raphson method 1446 | // http://en.wikipedia.org/wiki/Newton's_method 1447 | for (i = 0; i < iterations; i++) { 1448 | // The curve's x equation for the current time value 1449 | x = A * t * t * t + B * t * t + C * t + D; 1450 | // The slope we want is the inverse of the derivate of x 1451 | slope = 1 / (3 * A * t * t + 2 * B * t + C); 1452 | // Get the next estimated time value, which will be more accurate than the one before 1453 | t -= (x - tx) * slope; 1454 | t = t > 1 ? 1 : t < 0 ? 0 : t; 1455 | } 1456 | // Find the y value through the curve's y equation, with the now more accurate time value 1457 | y = Math.abs(E * t * t * t + F * t * t + G * t * H); 1458 | return y; 1459 | } 1460 | function getPointsAlongCubicBezier(ptCount, pxTolerance, Ax, Ay, Bx, By, Cx, Cy, Dx, Dy) { 1461 | let deltaBAx = Bx - Ax; 1462 | let deltaCBx = Cx - Bx; 1463 | let deltaDCx = Dx - Cx; 1464 | let deltaBAy = By - Ay; 1465 | let deltaCBy = Cy - By; 1466 | let deltaDCy = Dy - Cy; 1467 | let ax, ay, bx, by, cx, cy; 1468 | let lastX = -10000; 1469 | let lastY = -10000; 1470 | let pts = [{ x: Ax, y: Ay }]; 1471 | for (let i = 1; i < ptCount; i++) { 1472 | let t = i / ptCount; 1473 | ax = Ax + deltaBAx * t; 1474 | bx = Bx + deltaCBx * t; 1475 | cx = Cx + deltaDCx * t; 1476 | ax += (bx - ax) * t; 1477 | bx += (cx - bx) * t; 1478 | ay = Ay + deltaBAy * t; 1479 | by = By + deltaCBy * t; 1480 | cy = Cy + deltaDCy * t; 1481 | ay += (by - ay) * t; 1482 | by += (cy - by) * t; 1483 | const x = ax + (bx - ax) * t; 1484 | const y = ay + (by - ay) * t; 1485 | const dx = x - lastX; 1486 | const dy = y - lastY; 1487 | if (dx * dx + dy * dy > pxTolerance) { 1488 | pts.push({ x: x, y: y }); 1489 | lastX = x; 1490 | lastY = y; 1491 | } 1492 | } 1493 | pts.push({ x: Dx, y: Dy }); 1494 | return pts; 1495 | } 1496 | function interpolateCubicBezier(p0, c0, c1, p1) { 1497 | // 0 <= t <= 1 1498 | return function interpolator(t) { 1499 | return [ 1500 | pow(1 - t, 3) * p0.x + 1501 | 3 * pow(1 - t, 2) * t * c0.x + 1502 | 3 * (1 - t) * pow(t, 2) * c1.x + 1503 | pow(t, 3) * p1.x, 1504 | pow(1 - t, 3) * p0.y + 1505 | 3 * pow(1 - t, 2) * t * c0.y + 1506 | 3 * (1 - t) * pow(t, 2) * c1.y + 1507 | pow(t, 3) * p1.y, 1508 | ]; 1509 | }; 1510 | } 1511 | function addVectors(a, b) { 1512 | if (!b) 1513 | return a; 1514 | return { x: a.x + b.x, y: a.y + b.y }; 1515 | } 1516 | function getSvgPathFromStroke(stroke) { 1517 | if (stroke.length === 0) 1518 | return ""; 1519 | const d = []; 1520 | let [p0, p1] = stroke; 1521 | d.push("M", p0[0], p0[1]); 1522 | for (let i = 1; i < stroke.length; i++) { 1523 | d.push("Q", p0[0], p0[1], (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2); 1524 | p0 = p1; 1525 | p1 = stroke[i]; 1526 | } 1527 | d.push("Z"); 1528 | return d.join(" "); 1529 | } 1530 | // export function getFlatSvgPathFromStroke(stroke: number[][]) { 1531 | // try { 1532 | // const poly = polygonClipping.union([stroke] as any) 1533 | // const d = [] 1534 | // for (let face of poly) { 1535 | // for (let points of face) { 1536 | // points.push(points[0]) 1537 | // d.push(getSvgPathFromStroke(points)) 1538 | // } 1539 | // } 1540 | // d.push("Z") 1541 | // return d.join(" ") 1542 | // } catch (e) { 1543 | // console.error("Could not clip path.") 1544 | // return getSvgPathFromStroke(stroke) 1545 | // } 1546 | // } 1547 | 1548 | 1549 | /***/ }) 1550 | 1551 | /******/ }); 1552 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2x6LXN0cmluZy9saWJzL2x6LXN0cmluZy5qcyIsIndlYnBhY2s6Ly8vLi9ub2RlX21vZHVsZXMvcGVyZmVjdC1mcmVlaGFuZC9kaXN0L3BlcmZlY3QtZnJlZWhhbmQuZXNtLmpzIiwid2VicGFjazovLy8uL3NyYy9tYWluL2luZGV4LnRzIiwid2VicGFjazovLy8uL3NyYy90eXBlcy50cyIsIndlYnBhY2s6Ly8vLi9zcmMvdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtRQUFBO1FBQ0E7O1FBRUE7UUFDQTs7UUFFQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTs7UUFFQTtRQUNBOztRQUVBO1FBQ0E7O1FBRUE7UUFDQTtRQUNBOzs7UUFHQTtRQUNBOztRQUVBO1FBQ0E7O1FBRUE7UUFDQTtRQUNBO1FBQ0EsMENBQTBDLGdDQUFnQztRQUMxRTtRQUNBOztRQUVBO1FBQ0E7UUFDQTtRQUNBLHdEQUF3RCxrQkFBa0I7UUFDMUU7UUFDQSxpREFBaUQsY0FBYztRQUMvRDs7UUFFQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0E7UUFDQTtRQUNBO1FBQ0EseUNBQXlDLGlDQUFpQztRQUMxRSxnSEFBZ0gsbUJBQW1CLEVBQUU7UUFDckk7UUFDQTs7UUFFQTtRQUNBO1FBQ0E7UUFDQSwyQkFBMkIsMEJBQTBCLEVBQUU7UUFDdkQsaUNBQWlDLGVBQWU7UUFDaEQ7UUFDQTtRQUNBOztRQUVBO1FBQ0Esc0RBQXNELCtEQUErRDs7UUFFckg7UUFDQTs7O1FBR0E7UUFDQTs7Ozs7Ozs7Ozs7O0FDbEZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0Esa0JBQWtCLG9CQUFvQjtBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHVEQUF1RCwrQkFBK0I7QUFDdEYsNkJBQTZCO0FBQzdCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0EsbUVBQW1FLHdEQUF3RCxFQUFFO0FBQzdILEdBQUc7O0FBRUg7QUFDQTtBQUNBLHFEQUFxRCxnQkFBZ0I7QUFDckUsR0FBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQSwyRUFBMkUsMENBQTBDLEVBQUU7QUFDdkgsR0FBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQSxnREFBZ0Q7O0FBRWhELDZDQUE2QyxZQUFZO0FBQ3pEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBSztBQUNMLCtDQUErQztBQUMvQywwQ0FBMEMsWUFBWTtBQUN0RDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDs7QUFFQTs7QUFFQSxHQUFHOzs7QUFHSDtBQUNBO0FBQ0E7QUFDQSxvREFBb0QsZ0NBQWdDO0FBQ3BGLEdBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1FQUFtRSx5REFBeUQsRUFBRTtBQUM5SCxHQUFHOztBQUVIO0FBQ0EsNERBQTRELGFBQWE7QUFDekUsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBLDhCQUE4QjtBQUM5QixzQ0FBc0M7QUFDdEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUEsZ0JBQWdCLDBCQUEwQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0Esc0JBQXNCLG9CQUFvQjtBQUMxQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZUFBZTtBQUNmO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLE1BQU07QUFDNUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVc7QUFDWDtBQUNBLHNCQUFzQixvQkFBb0I7QUFDMUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXNCLE9BQU87QUFDN0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWU7QUFDZjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0Esb0JBQW9CLG9CQUFvQjtBQUN4QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLG9CQUFvQjtBQUN4QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYTtBQUNiO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLE1BQU07QUFDMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFNBQVM7QUFDVDtBQUNBLG9CQUFvQixvQkFBb0I7QUFDeEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW9CLE9BQU87QUFDM0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsT0FBTztBQUNQO0FBQ0Esa0JBQWtCLG9CQUFvQjtBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVztBQUNYO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsY0FBYyxvQkFBb0I7QUFDbEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0EsMkVBQTJFLHFDQUFxQyxFQUFFO0FBQ2xILEdBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCOztBQUVoQixlQUFlLE9BQU87QUFDdEI7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLE9BQU87QUFDUDtBQUNBO0FBQ0EsU0FBUztBQUNUO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQsSUFBSSxJQUEwQztBQUM5QyxFQUFFLG1DQUFPLGFBQWEsaUJBQWlCLEVBQUU7QUFBQSxvR0FBQztBQUMxQyxDQUFDLE1BQU0sRUFFTjs7Ozs7Ozs7Ozs7OztBQ3BmRDtBQUFBO0FBQUE7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0wsR0FBRztBQUNIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0VBQWdFLGVBQWU7QUFDL0U7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRzs7QUFFSCx3REFBd0QsZ0JBQWdCO0FBQ3hFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTs7QUFFQSx3QkFBd0IsUUFBUTtBQUNoQztBQUNBO0FBQ0E7O0FBRUE7QUFDQSxzQkFBc0IsU0FBUztBQUMvQjtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0VBQWdFLGVBQWU7QUFDL0U7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsR0FBRztBQUNIO0FBQ0EsNENBQTRDO0FBQzVDO0FBQ0Esd0NBQXdDO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUcsZUFBZTs7QUFFbEIsMEJBQTBCOztBQUUxQiwyQkFBMkI7O0FBRTNCLGtEQUFrRDs7QUFFbEQ7QUFDQSxvQkFBb0I7O0FBRXBCO0FBQ0E7QUFDQSxHQUFHLHNCQUFzQjs7QUFFekIsaUZBQWlGOztBQUVqRixvQ0FBb0M7O0FBRXBDO0FBQ0EsY0FBYzs7QUFFZDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxpQkFBaUIsYUFBYTtBQUM5QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNkVBQTZFO0FBQzdFO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBLHVCQUF1QixPQUFPO0FBQzlCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLEtBQUs7OztBQUdMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBLHFCQUFxQixXQUFXO0FBQ2hDO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBOztBQUVBLGdDQUFnQyxTQUFTO0FBQ3pDO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUEsa0NBQWtDLFVBQVU7QUFDNUM7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTs7QUFFQTtBQUNBOztBQUVBLG1DQUFtQyxVQUFVO0FBQzdDO0FBQ0E7QUFDQSxHQUFHO0FBQ0g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnRUFBZ0UsZUFBZTtBQUMvRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVlLHdFQUFTLEVBQUM7QUFDMEI7QUFDbkQ7Ozs7Ozs7Ozs7Ozs7QUNuaUJBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUE2RDtBQUN3QjtBQUM1QztBQUN3QjtBQUNqRTtBQUNBO0FBQ0Esc0JBQXNCLGdCQUFnQjtBQUN0QywwQkFBMEIsZ0JBQWdCO0FBQzFDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBdUM7QUFDdkM7QUFDQTtBQUNBLDJDQUEyQyxpRUFBZTtBQUMxRDtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlCQUF5QixxRUFBbUI7QUFDNUM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdEQUFnRCxPQUFPO0FBQ3ZEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdURBQXVELGlFQUFlO0FBQ3RFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxLQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0EsZ0RBQWdELE9BQU8sOEJBQThCLEtBQUs7QUFDMUY7QUFDQTtBQUNBO0FBQ0EsU0FBUyxzQkFBc0I7QUFDL0IsWUFBWTtBQUNaO0FBQ0E7QUFDQTtBQUNBLFdBQVcsZUFBZTtBQUMxQixXQUFXLGVBQWU7QUFDMUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFjLHdEQUFpQjtBQUMvQjtBQUNBLEtBQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxREFBcUQsb0NBQW9DO0FBQ3pGO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVCQUF1Qix5REFBVTtBQUNqQyx1QkFBdUIseURBQVU7QUFDakMsaUNBQWlDLHFFQUFzQjtBQUN2RCwyQkFBMkIsV0FBVztBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBLHVCQUF1QixnRUFBUyxvQ0FBb0MsYUFBYSxzQ0FBc0M7QUFDdkg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBCQUEwQixtRUFBb0I7QUFDOUMsaUJBQWlCO0FBQ2pCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUFnQyxnQkFBZ0I7QUFDaEQ7QUFDQSxhQUFhLG9EQUFhO0FBQzFCO0FBQ0E7QUFDQSxhQUFhLG9EQUFhO0FBQzFCO0FBQ0E7QUFDQSxhQUFhLG9EQUFhO0FBQzFCO0FBQ0E7QUFDQSxhQUFhLG9EQUFhO0FBQzFCO0FBQ0E7QUFDQSxhQUFhLG9EQUFhO0FBQzFCO0FBQ0E7QUFDQSxhQUFhLG9EQUFhO0FBQzFCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0JBQXdCLDBCQUEwQjtBQUNsRDtBQUNBOzs7Ozs7Ozs7Ozs7O0FDOU1BO0FBQUE7QUFBQTtBQUFBO0FBQ087QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLENBQUMsc0NBQXNDO0FBQ3ZDO0FBQ087QUFDUDtBQUNBO0FBQ0E7QUFDQSxDQUFDLDhDQUE4Qzs7Ozs7Ozs7Ozs7OztBQ2YvQztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUNBLE9BQU8sTUFBTTtBQUNOO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGVBQWUsZ0JBQWdCO0FBQy9CO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0JBQWdCLGVBQWU7QUFDL0IsbUJBQW1CLGFBQWE7QUFDaEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBc0IsYUFBYTtBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWMsZUFBZTtBQUM3QjtBQUNBO0FBQ087QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ087QUFDUDtBQUNBO0FBQ0EsWUFBWTtBQUNaO0FBQ087QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQW1CLG1CQUFtQjtBQUN0QztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQU07QUFDTjtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJwbHVnaW4uanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHsgZW51bWVyYWJsZTogdHJ1ZSwgZ2V0OiBnZXR0ZXIgfSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0aWYodHlwZW9mIFN5bWJvbCAhPT0gJ3VuZGVmaW5lZCcgJiYgU3ltYm9sLnRvU3RyaW5nVGFnKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIFN5bWJvbC50b1N0cmluZ1RhZywgeyB2YWx1ZTogJ01vZHVsZScgfSk7XG4gXHRcdH1cbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGNyZWF0ZSBhIGZha2UgbmFtZXNwYWNlIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDE6IHZhbHVlIGlzIGEgbW9kdWxlIGlkLCByZXF1aXJlIGl0XG4gXHQvLyBtb2RlICYgMjogbWVyZ2UgYWxsIHByb3BlcnRpZXMgb2YgdmFsdWUgaW50byB0aGUgbnNcbiBcdC8vIG1vZGUgJiA0OiByZXR1cm4gdmFsdWUgd2hlbiBhbHJlYWR5IG5zIG9iamVjdFxuIFx0Ly8gbW9kZSAmIDh8MTogYmVoYXZlIGxpa2UgcmVxdWlyZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy50ID0gZnVuY3Rpb24odmFsdWUsIG1vZGUpIHtcbiBcdFx0aWYobW9kZSAmIDEpIHZhbHVlID0gX193ZWJwYWNrX3JlcXVpcmVfXyh2YWx1ZSk7XG4gXHRcdGlmKG1vZGUgJiA4KSByZXR1cm4gdmFsdWU7XG4gXHRcdGlmKChtb2RlICYgNCkgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0JyAmJiB2YWx1ZSAmJiB2YWx1ZS5fX2VzTW9kdWxlKSByZXR1cm4gdmFsdWU7XG4gXHRcdHZhciBucyA9IE9iamVjdC5jcmVhdGUobnVsbCk7XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18ucihucyk7XG4gXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShucywgJ2RlZmF1bHQnLCB7IGVudW1lcmFibGU6IHRydWUsIHZhbHVlOiB2YWx1ZSB9KTtcbiBcdFx0aWYobW9kZSAmIDIgJiYgdHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSBmb3IodmFyIGtleSBpbiB2YWx1ZSkgX193ZWJwYWNrX3JlcXVpcmVfXy5kKG5zLCBrZXksIGZ1bmN0aW9uKGtleSkgeyByZXR1cm4gdmFsdWVba2V5XTsgfS5iaW5kKG51bGwsIGtleSkpO1xuIFx0XHRyZXR1cm4gbnM7XG4gXHR9O1xuXG4gXHQvLyBnZXREZWZhdWx0RXhwb3J0IGZ1bmN0aW9uIGZvciBjb21wYXRpYmlsaXR5IHdpdGggbm9uLWhhcm1vbnkgbW9kdWxlc1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5uID0gZnVuY3Rpb24obW9kdWxlKSB7XG4gXHRcdHZhciBnZXR0ZXIgPSBtb2R1bGUgJiYgbW9kdWxlLl9fZXNNb2R1bGUgP1xuIFx0XHRcdGZ1bmN0aW9uIGdldERlZmF1bHQoKSB7IHJldHVybiBtb2R1bGVbJ2RlZmF1bHQnXTsgfSA6XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0TW9kdWxlRXhwb3J0cygpIHsgcmV0dXJuIG1vZHVsZTsgfTtcbiBcdFx0X193ZWJwYWNrX3JlcXVpcmVfXy5kKGdldHRlciwgJ2EnLCBnZXR0ZXIpO1xuIFx0XHRyZXR1cm4gZ2V0dGVyO1xuIFx0fTtcblxuIFx0Ly8gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSBmdW5jdGlvbihvYmplY3QsIHByb3BlcnR5KSB7IHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqZWN0LCBwcm9wZXJ0eSk7IH07XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oX193ZWJwYWNrX3JlcXVpcmVfXy5zID0gXCIuL3NyYy9tYWluL2luZGV4LnRzXCIpO1xuIiwiLy8gQ29weXJpZ2h0IChjKSAyMDEzIFBpZXJveHkgPHBpZXJveHlAcGllcm94eS5uZXQ+XG4vLyBUaGlzIHdvcmsgaXMgZnJlZS4gWW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdFxuLy8gdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBXVEZQTCwgVmVyc2lvbiAyXG4vLyBGb3IgbW9yZSBpbmZvcm1hdGlvbiBzZWUgTElDRU5TRS50eHQgb3IgaHR0cDovL3d3dy53dGZwbC5uZXQvXG4vL1xuLy8gRm9yIG1vcmUgaW5mb3JtYXRpb24sIHRoZSBob21lIHBhZ2U6XG4vLyBodHRwOi8vcGllcm94eS5uZXQvYmxvZy9wYWdlcy9sei1zdHJpbmcvdGVzdGluZy5odG1sXG4vL1xuLy8gTFotYmFzZWQgY29tcHJlc3Npb24gYWxnb3JpdGhtLCB2ZXJzaW9uIDEuNC40XG52YXIgTFpTdHJpbmcgPSAoZnVuY3Rpb24oKSB7XG5cbi8vIHByaXZhdGUgcHJvcGVydHlcbnZhciBmID0gU3RyaW5nLmZyb21DaGFyQ29kZTtcbnZhciBrZXlTdHJCYXNlNjQgPSBcIkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky89XCI7XG52YXIga2V5U3RyVXJpU2FmZSA9IFwiQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLSRcIjtcbnZhciBiYXNlUmV2ZXJzZURpYyA9IHt9O1xuXG5mdW5jdGlvbiBnZXRCYXNlVmFsdWUoYWxwaGFiZXQsIGNoYXJhY3Rlcikge1xuICBpZiAoIWJhc2VSZXZlcnNlRGljW2FscGhhYmV0XSkge1xuICAgIGJhc2VSZXZlcnNlRGljW2FscGhhYmV0XSA9IHt9O1xuICAgIGZvciAodmFyIGk9MCA7IGk8YWxwaGFiZXQubGVuZ3RoIDsgaSsrKSB7XG4gICAgICBiYXNlUmV2ZXJzZURpY1thbHBoYWJldF1bYWxwaGFiZXQuY2hhckF0KGkpXSA9IGk7XG4gICAgfVxuICB9XG4gIHJldHVybiBiYXNlUmV2ZXJzZURpY1thbHBoYWJldF1bY2hhcmFjdGVyXTtcbn1cblxudmFyIExaU3RyaW5nID0ge1xuICBjb21wcmVzc1RvQmFzZTY0IDogZnVuY3Rpb24gKGlucHV0KSB7XG4gICAgaWYgKGlucHV0ID09IG51bGwpIHJldHVybiBcIlwiO1xuICAgIHZhciByZXMgPSBMWlN0cmluZy5fY29tcHJlc3MoaW5wdXQsIDYsIGZ1bmN0aW9uKGEpe3JldHVybiBrZXlTdHJCYXNlNjQuY2hhckF0KGEpO30pO1xuICAgIHN3aXRjaCAocmVzLmxlbmd0aCAlIDQpIHsgLy8gVG8gcHJvZHVjZSB2YWxpZCBCYXNlNjRcbiAgICBkZWZhdWx0OiAvLyBXaGVuIGNvdWxkIHRoaXMgaGFwcGVuID9cbiAgICBjYXNlIDAgOiByZXR1cm4gcmVzO1xuICAgIGNhc2UgMSA6IHJldHVybiByZXMrXCI9PT1cIjtcbiAgICBjYXNlIDIgOiByZXR1cm4gcmVzK1wiPT1cIjtcbiAgICBjYXNlIDMgOiByZXR1cm4gcmVzK1wiPVwiO1xuICAgIH1cbiAgfSxcblxuICBkZWNvbXByZXNzRnJvbUJhc2U2NCA6IGZ1bmN0aW9uIChpbnB1dCkge1xuICAgIGlmIChpbnB1dCA9PSBudWxsKSByZXR1cm4gXCJcIjtcbiAgICBpZiAoaW5wdXQgPT0gXCJcIikgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIExaU3RyaW5nLl9kZWNvbXByZXNzKGlucHV0Lmxlbmd0aCwgMzIsIGZ1bmN0aW9uKGluZGV4KSB7IHJldHVybiBnZXRCYXNlVmFsdWUoa2V5U3RyQmFzZTY0LCBpbnB1dC5jaGFyQXQoaW5kZXgpKTsgfSk7XG4gIH0sXG5cbiAgY29tcHJlc3NUb1VURjE2IDogZnVuY3Rpb24gKGlucHV0KSB7XG4gICAgaWYgKGlucHV0ID09IG51bGwpIHJldHVybiBcIlwiO1xuICAgIHJldHVybiBMWlN0cmluZy5fY29tcHJlc3MoaW5wdXQsIDE1LCBmdW5jdGlvbihhKXtyZXR1cm4gZihhKzMyKTt9KSArIFwiIFwiO1xuICB9LFxuXG4gIGRlY29tcHJlc3NGcm9tVVRGMTY6IGZ1bmN0aW9uIChjb21wcmVzc2VkKSB7XG4gICAgaWYgKGNvbXByZXNzZWQgPT0gbnVsbCkgcmV0dXJuIFwiXCI7XG4gICAgaWYgKGNvbXByZXNzZWQgPT0gXCJcIikgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIExaU3RyaW5nLl9kZWNvbXByZXNzKGNvbXByZXNzZWQubGVuZ3RoLCAxNjM4NCwgZnVuY3Rpb24oaW5kZXgpIHsgcmV0dXJuIGNvbXByZXNzZWQuY2hhckNvZGVBdChpbmRleCkgLSAzMjsgfSk7XG4gIH0sXG5cbiAgLy9jb21wcmVzcyBpbnRvIHVpbnQ4YXJyYXkgKFVDUy0yIGJpZyBlbmRpYW4gZm9ybWF0KVxuICBjb21wcmVzc1RvVWludDhBcnJheTogZnVuY3Rpb24gKHVuY29tcHJlc3NlZCkge1xuICAgIHZhciBjb21wcmVzc2VkID0gTFpTdHJpbmcuY29tcHJlc3ModW5jb21wcmVzc2VkKTtcbiAgICB2YXIgYnVmPW5ldyBVaW50OEFycmF5KGNvbXByZXNzZWQubGVuZ3RoKjIpOyAvLyAyIGJ5dGVzIHBlciBjaGFyYWN0ZXJcblxuICAgIGZvciAodmFyIGk9MCwgVG90YWxMZW49Y29tcHJlc3NlZC5sZW5ndGg7IGk8VG90YWxMZW47IGkrKykge1xuICAgICAgdmFyIGN1cnJlbnRfdmFsdWUgPSBjb21wcmVzc2VkLmNoYXJDb2RlQXQoaSk7XG4gICAgICBidWZbaSoyXSA9IGN1cnJlbnRfdmFsdWUgPj4+IDg7XG4gICAgICBidWZbaSoyKzFdID0gY3VycmVudF92YWx1ZSAlIDI1NjtcbiAgICB9XG4gICAgcmV0dXJuIGJ1ZjtcbiAgfSxcblxuICAvL2RlY29tcHJlc3MgZnJvbSB1aW50OGFycmF5IChVQ1MtMiBiaWcgZW5kaWFuIGZvcm1hdClcbiAgZGVjb21wcmVzc0Zyb21VaW50OEFycmF5OmZ1bmN0aW9uIChjb21wcmVzc2VkKSB7XG4gICAgaWYgKGNvbXByZXNzZWQ9PT1udWxsIHx8IGNvbXByZXNzZWQ9PT11bmRlZmluZWQpe1xuICAgICAgICByZXR1cm4gTFpTdHJpbmcuZGVjb21wcmVzcyhjb21wcmVzc2VkKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB2YXIgYnVmPW5ldyBBcnJheShjb21wcmVzc2VkLmxlbmd0aC8yKTsgLy8gMiBieXRlcyBwZXIgY2hhcmFjdGVyXG4gICAgICAgIGZvciAodmFyIGk9MCwgVG90YWxMZW49YnVmLmxlbmd0aDsgaTxUb3RhbExlbjsgaSsrKSB7XG4gICAgICAgICAgYnVmW2ldPWNvbXByZXNzZWRbaSoyXSoyNTYrY29tcHJlc3NlZFtpKjIrMV07XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgcmVzdWx0ID0gW107XG4gICAgICAgIGJ1Zi5mb3JFYWNoKGZ1bmN0aW9uIChjKSB7XG4gICAgICAgICAgcmVzdWx0LnB1c2goZihjKSk7XG4gICAgICAgIH0pO1xuICAgICAgICByZXR1cm4gTFpTdHJpbmcuZGVjb21wcmVzcyhyZXN1bHQuam9pbignJykpO1xuXG4gICAgfVxuXG4gIH0sXG5cblxuICAvL2NvbXByZXNzIGludG8gYSBzdHJpbmcgdGhhdCBpcyBhbHJlYWR5IFVSSSBlbmNvZGVkXG4gIGNvbXByZXNzVG9FbmNvZGVkVVJJQ29tcG9uZW50OiBmdW5jdGlvbiAoaW5wdXQpIHtcbiAgICBpZiAoaW5wdXQgPT0gbnVsbCkgcmV0dXJuIFwiXCI7XG4gICAgcmV0dXJuIExaU3RyaW5nLl9jb21wcmVzcyhpbnB1dCwgNiwgZnVuY3Rpb24oYSl7cmV0dXJuIGtleVN0clVyaVNhZmUuY2hhckF0KGEpO30pO1xuICB9LFxuXG4gIC8vZGVjb21wcmVzcyBmcm9tIGFuIG91dHB1dCBvZiBjb21wcmVzc1RvRW5jb2RlZFVSSUNvbXBvbmVudFxuICBkZWNvbXByZXNzRnJvbUVuY29kZWRVUklDb21wb25lbnQ6ZnVuY3Rpb24gKGlucHV0KSB7XG4gICAgaWYgKGlucHV0ID09IG51bGwpIHJldHVybiBcIlwiO1xuICAgIGlmIChpbnB1dCA9PSBcIlwiKSByZXR1cm4gbnVsbDtcbiAgICBpbnB1dCA9IGlucHV0LnJlcGxhY2UoLyAvZywgXCIrXCIpO1xuICAgIHJldHVybiBMWlN0cmluZy5fZGVjb21wcmVzcyhpbnB1dC5sZW5ndGgsIDMyLCBmdW5jdGlvbihpbmRleCkgeyByZXR1cm4gZ2V0QmFzZVZhbHVlKGtleVN0clVyaVNhZmUsIGlucHV0LmNoYXJBdChpbmRleCkpOyB9KTtcbiAgfSxcblxuICBjb21wcmVzczogZnVuY3Rpb24gKHVuY29tcHJlc3NlZCkge1xuICAgIHJldHVybiBMWlN0cmluZy5fY29tcHJlc3ModW5jb21wcmVzc2VkLCAxNiwgZnVuY3Rpb24oYSl7cmV0dXJuIGYoYSk7fSk7XG4gIH0sXG4gIF9jb21wcmVzczogZnVuY3Rpb24gKHVuY29tcHJlc3NlZCwgYml0c1BlckNoYXIsIGdldENoYXJGcm9tSW50KSB7XG4gICAgaWYgKHVuY29tcHJlc3NlZCA9PSBudWxsKSByZXR1cm4gXCJcIjtcbiAgICB2YXIgaSwgdmFsdWUsXG4gICAgICAgIGNvbnRleHRfZGljdGlvbmFyeT0ge30sXG4gICAgICAgIGNvbnRleHRfZGljdGlvbmFyeVRvQ3JlYXRlPSB7fSxcbiAgICAgICAgY29udGV4dF9jPVwiXCIsXG4gICAgICAgIGNvbnRleHRfd2M9XCJcIixcbiAgICAgICAgY29udGV4dF93PVwiXCIsXG4gICAgICAgIGNvbnRleHRfZW5sYXJnZUluPSAyLCAvLyBDb21wZW5zYXRlIGZvciB0aGUgZmlyc3QgZW50cnkgd2hpY2ggc2hvdWxkIG5vdCBjb3VudFxuICAgICAgICBjb250ZXh0X2RpY3RTaXplPSAzLFxuICAgICAgICBjb250ZXh0X251bUJpdHM9IDIsXG4gICAgICAgIGNvbnRleHRfZGF0YT1bXSxcbiAgICAgICAgY29udGV4dF9kYXRhX3ZhbD0wLFxuICAgICAgICBjb250ZXh0X2RhdGFfcG9zaXRpb249MCxcbiAgICAgICAgaWk7XG5cbiAgICBmb3IgKGlpID0gMDsgaWkgPCB1bmNvbXByZXNzZWQubGVuZ3RoOyBpaSArPSAxKSB7XG4gICAgICBjb250ZXh0X2MgPSB1bmNvbXByZXNzZWQuY2hhckF0KGlpKTtcbiAgICAgIGlmICghT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGNvbnRleHRfZGljdGlvbmFyeSxjb250ZXh0X2MpKSB7XG4gICAgICAgIGNvbnRleHRfZGljdGlvbmFyeVtjb250ZXh0X2NdID0gY29udGV4dF9kaWN0U2l6ZSsrO1xuICAgICAgICBjb250ZXh0X2RpY3Rpb25hcnlUb0NyZWF0ZVtjb250ZXh0X2NdID0gdHJ1ZTtcbiAgICAgIH1cblxuICAgICAgY29udGV4dF93YyA9IGNvbnRleHRfdyArIGNvbnRleHRfYztcbiAgICAgIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoY29udGV4dF9kaWN0aW9uYXJ5LGNvbnRleHRfd2MpKSB7XG4gICAgICAgIGNvbnRleHRfdyA9IGNvbnRleHRfd2M7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGNvbnRleHRfZGljdGlvbmFyeVRvQ3JlYXRlLGNvbnRleHRfdykpIHtcbiAgICAgICAgICBpZiAoY29udGV4dF93LmNoYXJDb2RlQXQoMCk8MjU2KSB7XG4gICAgICAgICAgICBmb3IgKGk9MCA7IGk8Y29udGV4dF9udW1CaXRzIDsgaSsrKSB7XG4gICAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAoY29udGV4dF9kYXRhX3ZhbCA8PCAxKTtcbiAgICAgICAgICAgICAgaWYgKGNvbnRleHRfZGF0YV9wb3NpdGlvbiA9PSBiaXRzUGVyQ2hhci0xKSB7XG4gICAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uID0gMDtcbiAgICAgICAgICAgICAgICBjb250ZXh0X2RhdGEucHVzaChnZXRDaGFyRnJvbUludChjb250ZXh0X2RhdGFfdmFsKSk7XG4gICAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3ZhbCA9IDA7XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uKys7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHZhbHVlID0gY29udGV4dF93LmNoYXJDb2RlQXQoMCk7XG4gICAgICAgICAgICBmb3IgKGk9MCA7IGk8OCA7IGkrKykge1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gKGNvbnRleHRfZGF0YV92YWwgPDwgMSkgfCAodmFsdWUmMSk7XG4gICAgICAgICAgICAgIGlmIChjb250ZXh0X2RhdGFfcG9zaXRpb24gPT0gYml0c1BlckNoYXItMSkge1xuICAgICAgICAgICAgICAgIGNvbnRleHRfZGF0YV9wb3NpdGlvbiA9IDA7XG4gICAgICAgICAgICAgICAgY29udGV4dF9kYXRhLnB1c2goZ2V0Q2hhckZyb21JbnQoY29udGV4dF9kYXRhX3ZhbCkpO1xuICAgICAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAwO1xuICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnRleHRfZGF0YV9wb3NpdGlvbisrO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIHZhbHVlID0gdmFsdWUgPj4gMTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdmFsdWUgPSAxO1xuICAgICAgICAgICAgZm9yIChpPTAgOyBpPGNvbnRleHRfbnVtQml0cyA7IGkrKykge1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gKGNvbnRleHRfZGF0YV92YWwgPDwgMSkgfCB2YWx1ZTtcbiAgICAgICAgICAgICAgaWYgKGNvbnRleHRfZGF0YV9wb3NpdGlvbiA9PWJpdHNQZXJDaGFyLTEpIHtcbiAgICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfcG9zaXRpb24gPSAwO1xuICAgICAgICAgICAgICAgIGNvbnRleHRfZGF0YS5wdXNoKGdldENoYXJGcm9tSW50KGNvbnRleHRfZGF0YV92YWwpKTtcbiAgICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gMDtcbiAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfcG9zaXRpb24rKztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB2YWx1ZSA9IDA7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB2YWx1ZSA9IGNvbnRleHRfdy5jaGFyQ29kZUF0KDApO1xuICAgICAgICAgICAgZm9yIChpPTAgOyBpPDE2IDsgaSsrKSB7XG4gICAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAoY29udGV4dF9kYXRhX3ZhbCA8PCAxKSB8ICh2YWx1ZSYxKTtcbiAgICAgICAgICAgICAgaWYgKGNvbnRleHRfZGF0YV9wb3NpdGlvbiA9PSBiaXRzUGVyQ2hhci0xKSB7XG4gICAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uID0gMDtcbiAgICAgICAgICAgICAgICBjb250ZXh0X2RhdGEucHVzaChnZXRDaGFyRnJvbUludChjb250ZXh0X2RhdGFfdmFsKSk7XG4gICAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3ZhbCA9IDA7XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uKys7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgdmFsdWUgPSB2YWx1ZSA+PiAxO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICBjb250ZXh0X2VubGFyZ2VJbi0tO1xuICAgICAgICAgIGlmIChjb250ZXh0X2VubGFyZ2VJbiA9PSAwKSB7XG4gICAgICAgICAgICBjb250ZXh0X2VubGFyZ2VJbiA9IE1hdGgucG93KDIsIGNvbnRleHRfbnVtQml0cyk7XG4gICAgICAgICAgICBjb250ZXh0X251bUJpdHMrKztcbiAgICAgICAgICB9XG4gICAgICAgICAgZGVsZXRlIGNvbnRleHRfZGljdGlvbmFyeVRvQ3JlYXRlW2NvbnRleHRfd107XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdmFsdWUgPSBjb250ZXh0X2RpY3Rpb25hcnlbY29udGV4dF93XTtcbiAgICAgICAgICBmb3IgKGk9MCA7IGk8Y29udGV4dF9udW1CaXRzIDsgaSsrKSB7XG4gICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gKGNvbnRleHRfZGF0YV92YWwgPDwgMSkgfCAodmFsdWUmMSk7XG4gICAgICAgICAgICBpZiAoY29udGV4dF9kYXRhX3Bvc2l0aW9uID09IGJpdHNQZXJDaGFyLTEpIHtcbiAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uID0gMDtcbiAgICAgICAgICAgICAgY29udGV4dF9kYXRhLnB1c2goZ2V0Q2hhckZyb21JbnQoY29udGV4dF9kYXRhX3ZhbCkpO1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gMDtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIGNvbnRleHRfZGF0YV9wb3NpdGlvbisrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdmFsdWUgPSB2YWx1ZSA+PiAxO1xuICAgICAgICAgIH1cblxuXG4gICAgICAgIH1cbiAgICAgICAgY29udGV4dF9lbmxhcmdlSW4tLTtcbiAgICAgICAgaWYgKGNvbnRleHRfZW5sYXJnZUluID09IDApIHtcbiAgICAgICAgICBjb250ZXh0X2VubGFyZ2VJbiA9IE1hdGgucG93KDIsIGNvbnRleHRfbnVtQml0cyk7XG4gICAgICAgICAgY29udGV4dF9udW1CaXRzKys7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQWRkIHdjIHRvIHRoZSBkaWN0aW9uYXJ5LlxuICAgICAgICBjb250ZXh0X2RpY3Rpb25hcnlbY29udGV4dF93Y10gPSBjb250ZXh0X2RpY3RTaXplKys7XG4gICAgICAgIGNvbnRleHRfdyA9IFN0cmluZyhjb250ZXh0X2MpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIE91dHB1dCB0aGUgY29kZSBmb3Igdy5cbiAgICBpZiAoY29udGV4dF93ICE9PSBcIlwiKSB7XG4gICAgICBpZiAoT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKGNvbnRleHRfZGljdGlvbmFyeVRvQ3JlYXRlLGNvbnRleHRfdykpIHtcbiAgICAgICAgaWYgKGNvbnRleHRfdy5jaGFyQ29kZUF0KDApPDI1Nikge1xuICAgICAgICAgIGZvciAoaT0wIDsgaTxjb250ZXh0X251bUJpdHMgOyBpKyspIHtcbiAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAoY29udGV4dF9kYXRhX3ZhbCA8PCAxKTtcbiAgICAgICAgICAgIGlmIChjb250ZXh0X2RhdGFfcG9zaXRpb24gPT0gYml0c1BlckNoYXItMSkge1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfcG9zaXRpb24gPSAwO1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGEucHVzaChnZXRDaGFyRnJvbUludChjb250ZXh0X2RhdGFfdmFsKSk7XG4gICAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAwO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uKys7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIHZhbHVlID0gY29udGV4dF93LmNoYXJDb2RlQXQoMCk7XG4gICAgICAgICAgZm9yIChpPTAgOyBpPDggOyBpKyspIHtcbiAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAoY29udGV4dF9kYXRhX3ZhbCA8PCAxKSB8ICh2YWx1ZSYxKTtcbiAgICAgICAgICAgIGlmIChjb250ZXh0X2RhdGFfcG9zaXRpb24gPT0gYml0c1BlckNoYXItMSkge1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfcG9zaXRpb24gPSAwO1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGEucHVzaChnZXRDaGFyRnJvbUludChjb250ZXh0X2RhdGFfdmFsKSk7XG4gICAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAwO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uKys7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB2YWx1ZSA9IHZhbHVlID4+IDE7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHZhbHVlID0gMTtcbiAgICAgICAgICBmb3IgKGk9MCA7IGk8Y29udGV4dF9udW1CaXRzIDsgaSsrKSB7XG4gICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gKGNvbnRleHRfZGF0YV92YWwgPDwgMSkgfCB2YWx1ZTtcbiAgICAgICAgICAgIGlmIChjb250ZXh0X2RhdGFfcG9zaXRpb24gPT0gYml0c1BlckNoYXItMSkge1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfcG9zaXRpb24gPSAwO1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGEucHVzaChnZXRDaGFyRnJvbUludChjb250ZXh0X2RhdGFfdmFsKSk7XG4gICAgICAgICAgICAgIGNvbnRleHRfZGF0YV92YWwgPSAwO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uKys7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB2YWx1ZSA9IDA7XG4gICAgICAgICAgfVxuICAgICAgICAgIHZhbHVlID0gY29udGV4dF93LmNoYXJDb2RlQXQoMCk7XG4gICAgICAgICAgZm9yIChpPTAgOyBpPDE2IDsgaSsrKSB7XG4gICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gKGNvbnRleHRfZGF0YV92YWwgPDwgMSkgfCAodmFsdWUmMSk7XG4gICAgICAgICAgICBpZiAoY29udGV4dF9kYXRhX3Bvc2l0aW9uID09IGJpdHNQZXJDaGFyLTEpIHtcbiAgICAgICAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uID0gMDtcbiAgICAgICAgICAgICAgY29udGV4dF9kYXRhLnB1c2goZ2V0Q2hhckZyb21JbnQoY29udGV4dF9kYXRhX3ZhbCkpO1xuICAgICAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gMDtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIGNvbnRleHRfZGF0YV9wb3NpdGlvbisrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdmFsdWUgPSB2YWx1ZSA+PiAxO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBjb250ZXh0X2VubGFyZ2VJbi0tO1xuICAgICAgICBpZiAoY29udGV4dF9lbmxhcmdlSW4gPT0gMCkge1xuICAgICAgICAgIGNvbnRleHRfZW5sYXJnZUluID0gTWF0aC5wb3coMiwgY29udGV4dF9udW1CaXRzKTtcbiAgICAgICAgICBjb250ZXh0X251bUJpdHMrKztcbiAgICAgICAgfVxuICAgICAgICBkZWxldGUgY29udGV4dF9kaWN0aW9uYXJ5VG9DcmVhdGVbY29udGV4dF93XTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHZhbHVlID0gY29udGV4dF9kaWN0aW9uYXJ5W2NvbnRleHRfd107XG4gICAgICAgIGZvciAoaT0wIDsgaTxjb250ZXh0X251bUJpdHMgOyBpKyspIHtcbiAgICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gKGNvbnRleHRfZGF0YV92YWwgPDwgMSkgfCAodmFsdWUmMSk7XG4gICAgICAgICAgaWYgKGNvbnRleHRfZGF0YV9wb3NpdGlvbiA9PSBiaXRzUGVyQ2hhci0xKSB7XG4gICAgICAgICAgICBjb250ZXh0X2RhdGFfcG9zaXRpb24gPSAwO1xuICAgICAgICAgICAgY29udGV4dF9kYXRhLnB1c2goZ2V0Q2hhckZyb21JbnQoY29udGV4dF9kYXRhX3ZhbCkpO1xuICAgICAgICAgICAgY29udGV4dF9kYXRhX3ZhbCA9IDA7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnRleHRfZGF0YV9wb3NpdGlvbisrO1xuICAgICAgICAgIH1cbiAgICAgICAgICB2YWx1ZSA9IHZhbHVlID4+IDE7XG4gICAgICAgIH1cblxuXG4gICAgICB9XG4gICAgICBjb250ZXh0X2VubGFyZ2VJbi0tO1xuICAgICAgaWYgKGNvbnRleHRfZW5sYXJnZUluID09IDApIHtcbiAgICAgICAgY29udGV4dF9lbmxhcmdlSW4gPSBNYXRoLnBvdygyLCBjb250ZXh0X251bUJpdHMpO1xuICAgICAgICBjb250ZXh0X251bUJpdHMrKztcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBNYXJrIHRoZSBlbmQgb2YgdGhlIHN0cmVhbVxuICAgIHZhbHVlID0gMjtcbiAgICBmb3IgKGk9MCA7IGk8Y29udGV4dF9udW1CaXRzIDsgaSsrKSB7XG4gICAgICBjb250ZXh0X2RhdGFfdmFsID0gKGNvbnRleHRfZGF0YV92YWwgPDwgMSkgfCAodmFsdWUmMSk7XG4gICAgICBpZiAoY29udGV4dF9kYXRhX3Bvc2l0aW9uID09IGJpdHNQZXJDaGFyLTEpIHtcbiAgICAgICAgY29udGV4dF9kYXRhX3Bvc2l0aW9uID0gMDtcbiAgICAgICAgY29udGV4dF9kYXRhLnB1c2goZ2V0Q2hhckZyb21JbnQoY29udGV4dF9kYXRhX3ZhbCkpO1xuICAgICAgICBjb250ZXh0X2RhdGFfdmFsID0gMDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnRleHRfZGF0YV9wb3NpdGlvbisrO1xuICAgICAgfVxuICAgICAgdmFsdWUgPSB2YWx1ZSA+PiAxO1xuICAgIH1cblxuICAgIC8vIEZsdXNoIHRoZSBsYXN0IGNoYXJcbiAgICB3aGlsZSAodHJ1ZSkge1xuICAgICAgY29udGV4dF9kYXRhX3ZhbCA9IChjb250ZXh0X2RhdGFfdmFsIDw8IDEpO1xuICAgICAgaWYgKGNvbnRleHRfZGF0YV9wb3NpdGlvbiA9PSBiaXRzUGVyQ2hhci0xKSB7XG4gICAgICAgIGNvbnRleHRfZGF0YS5wdXNoKGdldENoYXJGcm9tSW50KGNvbnRleHRfZGF0YV92YWwpKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBlbHNlIGNvbnRleHRfZGF0YV9wb3NpdGlvbisrO1xuICAgIH1cbiAgICByZXR1cm4gY29udGV4dF9kYXRhLmpvaW4oJycpO1xuICB9LFxuXG4gIGRlY29tcHJlc3M6IGZ1bmN0aW9uIChjb21wcmVzc2VkKSB7XG4gICAgaWYgKGNvbXByZXNzZWQgPT0gbnVsbCkgcmV0dXJuIFwiXCI7XG4gICAgaWYgKGNvbXByZXNzZWQgPT0gXCJcIikgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIExaU3RyaW5nLl9kZWNvbXByZXNzKGNvbXByZXNzZWQubGVuZ3RoLCAzMjc2OCwgZnVuY3Rpb24oaW5kZXgpIHsgcmV0dXJuIGNvbXByZXNzZWQuY2hhckNvZGVBdChpbmRleCk7IH0pO1xuICB9LFxuXG4gIF9kZWNvbXByZXNzOiBmdW5jdGlvbiAobGVuZ3RoLCByZXNldFZhbHVlLCBnZXROZXh0VmFsdWUpIHtcbiAgICB2YXIgZGljdGlvbmFyeSA9IFtdLFxuICAgICAgICBuZXh0LFxuICAgICAgICBlbmxhcmdlSW4gPSA0LFxuICAgICAgICBkaWN0U2l6ZSA9IDQsXG4gICAgICAgIG51bUJpdHMgPSAzLFxuICAgICAgICBlbnRyeSA9IFwiXCIsXG4gICAgICAgIHJlc3VsdCA9IFtdLFxuICAgICAgICBpLFxuICAgICAgICB3LFxuICAgICAgICBiaXRzLCByZXNiLCBtYXhwb3dlciwgcG93ZXIsXG4gICAgICAgIGMsXG4gICAgICAgIGRhdGEgPSB7dmFsOmdldE5leHRWYWx1ZSgwKSwgcG9zaXRpb246cmVzZXRWYWx1ZSwgaW5kZXg6MX07XG5cbiAgICBmb3IgKGkgPSAwOyBpIDwgMzsgaSArPSAxKSB7XG4gICAgICBkaWN0aW9uYXJ5W2ldID0gaTtcbiAgICB9XG5cbiAgICBiaXRzID0gMDtcbiAgICBtYXhwb3dlciA9IE1hdGgucG93KDIsMik7XG4gICAgcG93ZXI9MTtcbiAgICB3aGlsZSAocG93ZXIhPW1heHBvd2VyKSB7XG4gICAgICByZXNiID0gZGF0YS52YWwgJiBkYXRhLnBvc2l0aW9uO1xuICAgICAgZGF0YS5wb3NpdGlvbiA+Pj0gMTtcbiAgICAgIGlmIChkYXRhLnBvc2l0aW9uID09IDApIHtcbiAgICAgICAgZGF0YS5wb3NpdGlvbiA9IHJlc2V0VmFsdWU7XG4gICAgICAgIGRhdGEudmFsID0gZ2V0TmV4dFZhbHVlKGRhdGEuaW5kZXgrKyk7XG4gICAgICB9XG4gICAgICBiaXRzIHw9IChyZXNiPjAgPyAxIDogMCkgKiBwb3dlcjtcbiAgICAgIHBvd2VyIDw8PSAxO1xuICAgIH1cblxuICAgIHN3aXRjaCAobmV4dCA9IGJpdHMpIHtcbiAgICAgIGNhc2UgMDpcbiAgICAgICAgICBiaXRzID0gMDtcbiAgICAgICAgICBtYXhwb3dlciA9IE1hdGgucG93KDIsOCk7XG4gICAgICAgICAgcG93ZXI9MTtcbiAgICAgICAgICB3aGlsZSAocG93ZXIhPW1heHBvd2VyKSB7XG4gICAgICAgICAgICByZXNiID0gZGF0YS52YWwgJiBkYXRhLnBvc2l0aW9uO1xuICAgICAgICAgICAgZGF0YS5wb3NpdGlvbiA+Pj0gMTtcbiAgICAgICAgICAgIGlmIChkYXRhLnBvc2l0aW9uID09IDApIHtcbiAgICAgICAgICAgICAgZGF0YS5wb3NpdGlvbiA9IHJlc2V0VmFsdWU7XG4gICAgICAgICAgICAgIGRhdGEudmFsID0gZ2V0TmV4dFZhbHVlKGRhdGEuaW5kZXgrKyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBiaXRzIHw9IChyZXNiPjAgPyAxIDogMCkgKiBwb3dlcjtcbiAgICAgICAgICAgIHBvd2VyIDw8PSAxO1xuICAgICAgICAgIH1cbiAgICAgICAgYyA9IGYoYml0cyk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAxOlxuICAgICAgICAgIGJpdHMgPSAwO1xuICAgICAgICAgIG1heHBvd2VyID0gTWF0aC5wb3coMiwxNik7XG4gICAgICAgICAgcG93ZXI9MTtcbiAgICAgICAgICB3aGlsZSAocG93ZXIhPW1heHBvd2VyKSB7XG4gICAgICAgICAgICByZXNiID0gZGF0YS52YWwgJiBkYXRhLnBvc2l0aW9uO1xuICAgICAgICAgICAgZGF0YS5wb3NpdGlvbiA+Pj0gMTtcbiAgICAgICAgICAgIGlmIChkYXRhLnBvc2l0aW9uID09IDApIHtcbiAgICAgICAgICAgICAgZGF0YS5wb3NpdGlvbiA9IHJlc2V0VmFsdWU7XG4gICAgICAgICAgICAgIGRhdGEudmFsID0gZ2V0TmV4dFZhbHVlKGRhdGEuaW5kZXgrKyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBiaXRzIHw9IChyZXNiPjAgPyAxIDogMCkgKiBwb3dlcjtcbiAgICAgICAgICAgIHBvd2VyIDw8PSAxO1xuICAgICAgICAgIH1cbiAgICAgICAgYyA9IGYoYml0cyk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAyOlxuICAgICAgICByZXR1cm4gXCJcIjtcbiAgICB9XG4gICAgZGljdGlvbmFyeVszXSA9IGM7XG4gICAgdyA9IGM7XG4gICAgcmVzdWx0LnB1c2goYyk7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIGlmIChkYXRhLmluZGV4ID4gbGVuZ3RoKSB7XG4gICAgICAgIHJldHVybiBcIlwiO1xuICAgICAgfVxuXG4gICAgICBiaXRzID0gMDtcbiAgICAgIG1heHBvd2VyID0gTWF0aC5wb3coMixudW1CaXRzKTtcbiAgICAgIHBvd2VyPTE7XG4gICAgICB3aGlsZSAocG93ZXIhPW1heHBvd2VyKSB7XG4gICAgICAgIHJlc2IgPSBkYXRhLnZhbCAmIGRhdGEucG9zaXRpb247XG4gICAgICAgIGRhdGEucG9zaXRpb24gPj49IDE7XG4gICAgICAgIGlmIChkYXRhLnBvc2l0aW9uID09IDApIHtcbiAgICAgICAgICBkYXRhLnBvc2l0aW9uID0gcmVzZXRWYWx1ZTtcbiAgICAgICAgICBkYXRhLnZhbCA9IGdldE5leHRWYWx1ZShkYXRhLmluZGV4KyspO1xuICAgICAgICB9XG4gICAgICAgIGJpdHMgfD0gKHJlc2I+MCA/IDEgOiAwKSAqIHBvd2VyO1xuICAgICAgICBwb3dlciA8PD0gMTtcbiAgICAgIH1cblxuICAgICAgc3dpdGNoIChjID0gYml0cykge1xuICAgICAgICBjYXNlIDA6XG4gICAgICAgICAgYml0cyA9IDA7XG4gICAgICAgICAgbWF4cG93ZXIgPSBNYXRoLnBvdygyLDgpO1xuICAgICAgICAgIHBvd2VyPTE7XG4gICAgICAgICAgd2hpbGUgKHBvd2VyIT1tYXhwb3dlcikge1xuICAgICAgICAgICAgcmVzYiA9IGRhdGEudmFsICYgZGF0YS5wb3NpdGlvbjtcbiAgICAgICAgICAgIGRhdGEucG9zaXRpb24gPj49IDE7XG4gICAgICAgICAgICBpZiAoZGF0YS5wb3NpdGlvbiA9PSAwKSB7XG4gICAgICAgICAgICAgIGRhdGEucG9zaXRpb24gPSByZXNldFZhbHVlO1xuICAgICAgICAgICAgICBkYXRhLnZhbCA9IGdldE5leHRWYWx1ZShkYXRhLmluZGV4KyspO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYml0cyB8PSAocmVzYj4wID8gMSA6IDApICogcG93ZXI7XG4gICAgICAgICAgICBwb3dlciA8PD0gMTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBkaWN0aW9uYXJ5W2RpY3RTaXplKytdID0gZihiaXRzKTtcbiAgICAgICAgICBjID0gZGljdFNpemUtMTtcbiAgICAgICAgICBlbmxhcmdlSW4tLTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAxOlxuICAgICAgICAgIGJpdHMgPSAwO1xuICAgICAgICAgIG1heHBvd2VyID0gTWF0aC5wb3coMiwxNik7XG4gICAgICAgICAgcG93ZXI9MTtcbiAgICAgICAgICB3aGlsZSAocG93ZXIhPW1heHBvd2VyKSB7XG4gICAgICAgICAgICByZXNiID0gZGF0YS52YWwgJiBkYXRhLnBvc2l0aW9uO1xuICAgICAgICAgICAgZGF0YS5wb3NpdGlvbiA+Pj0gMTtcbiAgICAgICAgICAgIGlmIChkYXRhLnBvc2l0aW9uID09IDApIHtcbiAgICAgICAgICAgICAgZGF0YS5wb3NpdGlvbiA9IHJlc2V0VmFsdWU7XG4gICAgICAgICAgICAgIGRhdGEudmFsID0gZ2V0TmV4dFZhbHVlKGRhdGEuaW5kZXgrKyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBiaXRzIHw9IChyZXNiPjAgPyAxIDogMCkgKiBwb3dlcjtcbiAgICAgICAgICAgIHBvd2VyIDw8PSAxO1xuICAgICAgICAgIH1cbiAgICAgICAgICBkaWN0aW9uYXJ5W2RpY3RTaXplKytdID0gZihiaXRzKTtcbiAgICAgICAgICBjID0gZGljdFNpemUtMTtcbiAgICAgICAgICBlbmxhcmdlSW4tLTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAyOlxuICAgICAgICAgIHJldHVybiByZXN1bHQuam9pbignJyk7XG4gICAgICB9XG5cbiAgICAgIGlmIChlbmxhcmdlSW4gPT0gMCkge1xuICAgICAgICBlbmxhcmdlSW4gPSBNYXRoLnBvdygyLCBudW1CaXRzKTtcbiAgICAgICAgbnVtQml0cysrO1xuICAgICAgfVxuXG4gICAgICBpZiAoZGljdGlvbmFyeVtjXSkge1xuICAgICAgICBlbnRyeSA9IGRpY3Rpb25hcnlbY107XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAoYyA9PT0gZGljdFNpemUpIHtcbiAgICAgICAgICBlbnRyeSA9IHcgKyB3LmNoYXJBdCgwKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmVzdWx0LnB1c2goZW50cnkpO1xuXG4gICAgICAvLyBBZGQgdytlbnRyeVswXSB0byB0aGUgZGljdGlvbmFyeS5cbiAgICAgIGRpY3Rpb25hcnlbZGljdFNpemUrK10gPSB3ICsgZW50cnkuY2hhckF0KDApO1xuICAgICAgZW5sYXJnZUluLS07XG5cbiAgICAgIHcgPSBlbnRyeTtcblxuICAgICAgaWYgKGVubGFyZ2VJbiA9PSAwKSB7XG4gICAgICAgIGVubGFyZ2VJbiA9IE1hdGgucG93KDIsIG51bUJpdHMpO1xuICAgICAgICBudW1CaXRzKys7XG4gICAgICB9XG5cbiAgICB9XG4gIH1cbn07XG4gIHJldHVybiBMWlN0cmluZztcbn0pKCk7XG5cbmlmICh0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQpIHtcbiAgZGVmaW5lKGZ1bmN0aW9uICgpIHsgcmV0dXJuIExaU3RyaW5nOyB9KTtcbn0gZWxzZSBpZiggdHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbW9kdWxlICE9IG51bGwgKSB7XG4gIG1vZHVsZS5leHBvcnRzID0gTFpTdHJpbmdcbn1cbiIsImZ1bmN0aW9uIGxlcnAoeTEsIHkyLCBtdSkge1xuICByZXR1cm4geTEgKiAoMSAtIG11KSArIHkyICogbXU7XG59XG5mdW5jdGlvbiBjbGFtcChuLCBhLCBiKSB7XG4gIHJldHVybiBNYXRoLm1heChhLCBNYXRoLm1pbihiLCBuKSk7XG59XG4vKipcclxuICogQ29udmVydCBhbiBhcnJheSBvZiBwb2ludHMgdG8gdGhlIGNvcnJlY3QgZm9ybWF0IChbeCwgeSwgcmFkaXVzXSlcclxuICogQHBhcmFtIHBvaW50c1xyXG4gKiBAcmV0dXJuc1xyXG4gKi9cblxuZnVuY3Rpb24gdG9Qb2ludHNBcnJheShwb2ludHMpIHtcbiAgaWYgKEFycmF5LmlzQXJyYXkocG9pbnRzWzBdKSkge1xuICAgIHJldHVybiBwb2ludHMubWFwKGZ1bmN0aW9uIChfcmVmKSB7XG4gICAgICB2YXIgeCA9IF9yZWZbMF0sXG4gICAgICAgICAgeSA9IF9yZWZbMV0sXG4gICAgICAgICAgX3JlZiQgPSBfcmVmWzJdLFxuICAgICAgICAgIHByZXNzdXJlID0gX3JlZiQgPT09IHZvaWQgMCA/IDAuNSA6IF9yZWYkO1xuICAgICAgcmV0dXJuIFt4LCB5LCBwcmVzc3VyZV07XG4gICAgfSk7XG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIHBvaW50cy5tYXAoZnVuY3Rpb24gKF9yZWYyKSB7XG4gICAgICB2YXIgeCA9IF9yZWYyLngsXG4gICAgICAgICAgeSA9IF9yZWYyLnksXG4gICAgICAgICAgX3JlZjIkcHJlc3N1cmUgPSBfcmVmMi5wcmVzc3VyZSxcbiAgICAgICAgICBwcmVzc3VyZSA9IF9yZWYyJHByZXNzdXJlID09PSB2b2lkIDAgPyAwLjUgOiBfcmVmMiRwcmVzc3VyZTtcbiAgICAgIHJldHVybiBbeCwgeSwgcHJlc3N1cmVdO1xuICAgIH0pO1xuICB9XG59XG4vKipcclxuICogQ29tcHV0ZSBhIHJhZGl1cyBiYXNlZCBvbiB0aGUgcHJlc3N1cmUuXHJcbiAqIEBwYXJhbSBzaXplXHJcbiAqIEBwYXJhbSB0aGlubmluZ1xyXG4gKiBAcGFyYW0gZWFzaW5nXHJcbiAqIEBwYXJhbSBwcmVzc3VyZVxyXG4gKiBAcmV0dXJuc1xyXG4gKi9cblxuZnVuY3Rpb24gZ2V0U3Ryb2tlUmFkaXVzKHNpemUsIHRoaW5uaW5nLCBlYXNpbmcsIHByZXNzdXJlKSB7XG4gIGlmIChwcmVzc3VyZSA9PT0gdm9pZCAwKSB7XG4gICAgcHJlc3N1cmUgPSAwLjU7XG4gIH1cblxuICBpZiAoIXRoaW5uaW5nKSByZXR1cm4gc2l6ZSAvIDI7XG4gIHByZXNzdXJlID0gY2xhbXAoZWFzaW5nKHByZXNzdXJlKSwgMCwgMSk7XG4gIHJldHVybiAodGhpbm5pbmcgPCAwID8gbGVycChzaXplLCBzaXplICsgc2l6ZSAqIGNsYW1wKHRoaW5uaW5nLCAtMC45NSwgLTAuMDUpLCBwcmVzc3VyZSkgOiBsZXJwKHNpemUgLSBzaXplICogY2xhbXAodGhpbm5pbmcsIDAuMDUsIDAuOTUpLCBzaXplLCBwcmVzc3VyZSkpIC8gMjtcbn1cblxuLyoqXHJcbiAqIE5lZ2F0ZSBhIHZlY3Rvci5cclxuICogQHBhcmFtIEFcclxuICovXG4vKipcclxuICogQWRkIHZlY3RvcnMuXHJcbiAqIEBwYXJhbSBBXHJcbiAqIEBwYXJhbSBCXHJcbiAqL1xuXG5mdW5jdGlvbiBhZGQoQSwgQikge1xuICByZXR1cm4gW0FbMF0gKyBCWzBdLCBBWzFdICsgQlsxXV07XG59XG4vKipcclxuICogU3VidHJhY3QgdmVjdG9ycy5cclxuICogQHBhcmFtIEFcclxuICogQHBhcmFtIEJcclxuICovXG5cbmZ1bmN0aW9uIHN1YihBLCBCKSB7XG4gIHJldHVybiBbQVswXSAtIEJbMF0sIEFbMV0gLSBCWzFdXTtcbn1cbi8qKlxyXG4gKiBHZXQgdGhlIHZlY3RvciBmcm9tIHZlY3RvcnMgQSB0byBCLlxyXG4gKiBAcGFyYW0gQVxyXG4gKiBAcGFyYW0gQlxyXG4gKi9cblxuZnVuY3Rpb24gdmVjKEEsIEIpIHtcbiAgLy8gQSwgQiBhcyB2ZWN0b3JzIGdldCB0aGUgdmVjdG9yIGZyb20gQSB0byBCXG4gIHJldHVybiBbQlswXSAtIEFbMF0sIEJbMV0gLSBBWzFdXTtcbn1cbi8qKlxyXG4gKiBWZWN0b3IgbXVsdGlwbGljYXRpb24gYnkgc2NhbGFyXHJcbiAqIEBwYXJhbSBBXHJcbiAqIEBwYXJhbSBuXHJcbiAqL1xuXG5mdW5jdGlvbiBtdWwoQSwgbikge1xuICByZXR1cm4gW0FbMF0gKiBuLCBBWzFdICogbl07XG59XG4vKipcclxuICogVmVjdG9yIGRpdmlzaW9uIGJ5IHNjYWxhci5cclxuICogQHBhcmFtIEFcclxuICogQHBhcmFtIG5cclxuICovXG5cbmZ1bmN0aW9uIGRpdihBLCBuKSB7XG4gIHJldHVybiBbQVswXSAvIG4sIEFbMV0gLyBuXTtcbn1cbi8qKlxyXG4gKiBQZXJwZW5kaWN1bGFyIHJvdGF0aW9uIG9mIGEgdmVjdG9yIEFcclxuICogQHBhcmFtIEFcclxuICovXG5cbmZ1bmN0aW9uIHBlcihBKSB7XG4gIHJldHVybiBbQVsxXSwgLUFbMF1dO1xufVxuLyoqXHJcbiAqIERvdCBwcm9kdWN0XHJcbiAqIEBwYXJhbSBBXHJcbiAqIEBwYXJhbSBCXHJcbiAqL1xuXG5mdW5jdGlvbiBkcHIoQSwgQikge1xuICByZXR1cm4gQVswXSAqIEJbMF0gKyBBWzFdICogQlsxXTtcbn1cbi8qKlxyXG4gKiBMZW5ndGggb2YgdGhlIHZlY3RvclxyXG4gKiBAcGFyYW0gQVxyXG4gKi9cblxuZnVuY3Rpb24gbGVuKEEpIHtcbiAgcmV0dXJuIE1hdGguaHlwb3QoQVswXSwgQVsxXSk7XG59XG4vKipcclxuICogR2V0IG5vcm1hbGl6ZWQgLyB1bml0IHZlY3Rvci5cclxuICogQHBhcmFtIEFcclxuICovXG5cbmZ1bmN0aW9uIHVuaShBKSB7XG4gIHJldHVybiBkaXYoQSwgbGVuKEEpKTtcbn1cbi8qKlxyXG4gKiBEaXN0IGxlbmd0aCBmcm9tIEEgdG8gQlxyXG4gKiBAcGFyYW0gQVxyXG4gKiBAcGFyYW0gQlxyXG4gKi9cblxuZnVuY3Rpb24gZGlzdChBLCBCKSB7XG4gIHJldHVybiBNYXRoLmh5cG90KEFbMV0gLSBCWzFdLCBBWzBdIC0gQlswXSk7XG59XG4vKipcclxuICogUm90YXRlIGEgdmVjdG9yIGFyb3VuZCBhbm90aGVyIHZlY3RvciBieSByIChyYWRpYW5zKVxyXG4gKiBAcGFyYW0gQSB2ZWN0b3JcclxuICogQHBhcmFtIEMgY2VudGVyXHJcbiAqIEBwYXJhbSByIHJvdGF0aW9uIGluIHJhZGlhbnNcclxuICovXG5cbmZ1bmN0aW9uIHJvdEFyb3VuZChBLCBDLCByKSB7XG4gIHZhciBzID0gTWF0aC5zaW4ocik7XG4gIHZhciBjID0gTWF0aC5jb3Mocik7XG4gIHZhciBweCA9IEFbMF0gLSBDWzBdO1xuICB2YXIgcHkgPSBBWzFdIC0gQ1sxXTtcbiAgdmFyIG54ID0gcHggKiBjIC0gcHkgKiBzO1xuICB2YXIgbnkgPSBweCAqIHMgKyBweSAqIGM7XG4gIHJldHVybiBbbnggKyBDWzBdLCBueSArIENbMV1dO1xufVxuLyoqXHJcbiAqIEludGVycG9sYXRlIHZlY3RvciBBIHRvIEIgd2l0aCBhIHNjYWxhciB0XHJcbiAqIEBwYXJhbSBBXHJcbiAqIEBwYXJhbSBCXHJcbiAqIEBwYXJhbSB0IHNjYWxhclxyXG4gKi9cblxuZnVuY3Rpb24gbHJwKEEsIEIsIHQpIHtcbiAgcmV0dXJuIGFkZChBLCBtdWwodmVjKEEsIEIpLCB0KSk7XG59XG5cbnZhciBtaW4gPSBNYXRoLm1pbixcbiAgICBQSSA9IE1hdGguUEk7XG4vKipcclxuICogIyMgZ2V0U3Ryb2tlUG9pbnRzXHJcbiAqIEBkZXNjcmlwdGlvbiBHZXQgcG9pbnRzIGZvciBhIHN0cm9rZS5cclxuICogQHBhcmFtIHBvaW50cyBBbiBhcnJheSBvZiBwb2ludHMgKGFzIGBbeCwgeSwgcHJlc3N1cmVdYCBvciBge3gsIHksIHByZXNzdXJlfWApLiBQcmVzc3VyZSBpcyBvcHRpb25hbC5cclxuICogQHBhcmFtIHN0cmVhbWxpbmUgSG93IG11Y2ggdG8gc3RyZWFtbGluZSB0aGUgc3Ryb2tlLlxyXG4gKiBAcGFyYW0gc2l6ZSBUaGUgc3Ryb2tlJ3Mgc2l6ZS5cclxuICovXG5cbmZ1bmN0aW9uIGdldFN0cm9rZVBvaW50cyhwb2ludHMsIG9wdGlvbnMpIHtcbiAgdmFyIF9vcHRpb25zJHNpbXVsYXRlUHJlcyA9IG9wdGlvbnMuc2ltdWxhdGVQcmVzc3VyZSxcbiAgICAgIHNpbXVsYXRlUHJlc3N1cmUgPSBfb3B0aW9ucyRzaW11bGF0ZVByZXMgPT09IHZvaWQgMCA/IHRydWUgOiBfb3B0aW9ucyRzaW11bGF0ZVByZXMsXG4gICAgICBfb3B0aW9ucyRzdHJlYW1saW5lID0gb3B0aW9ucy5zdHJlYW1saW5lLFxuICAgICAgc3RyZWFtbGluZSA9IF9vcHRpb25zJHN0cmVhbWxpbmUgPT09IHZvaWQgMCA/IDAuNSA6IF9vcHRpb25zJHN0cmVhbWxpbmUsXG4gICAgICBfb3B0aW9ucyRzaXplID0gb3B0aW9ucy5zaXplLFxuICAgICAgc2l6ZSA9IF9vcHRpb25zJHNpemUgPT09IHZvaWQgMCA/IDggOiBfb3B0aW9ucyRzaXplO1xuICBzdHJlYW1saW5lIC89IDI7XG5cbiAgaWYgKCFzaW11bGF0ZVByZXNzdXJlKSB7XG4gICAgc3RyZWFtbGluZSAvPSAyO1xuICB9XG5cbiAgdmFyIHB0cyA9IHRvUG9pbnRzQXJyYXkocG9pbnRzKTtcbiAgdmFyIGxlbiA9IHB0cy5sZW5ndGg7XG4gIGlmIChsZW4gPT09IDApIHJldHVybiBbXTtcbiAgaWYgKGxlbiA9PT0gMSkgcHRzLnB1c2goYWRkKHB0c1swXSwgWzEsIDBdKSk7XG4gIHZhciBzdHJva2VQb2ludHMgPSBbe1xuICAgIHBvaW50OiBbcHRzWzBdWzBdLCBwdHNbMF1bMV1dLFxuICAgIHByZXNzdXJlOiBwdHNbMF1bMl0sXG4gICAgdmVjdG9yOiBbMCwgMF0sXG4gICAgZGlzdGFuY2U6IDAsXG4gICAgcnVubmluZ0xlbmd0aDogMFxuICB9XTtcblxuICBmb3IgKHZhciBpID0gMSwgY3VyciA9IHB0c1tpXSwgcHJldiA9IHN0cm9rZVBvaW50c1swXTsgaSA8IHB0cy5sZW5ndGg7IGkrKywgY3VyciA9IHB0c1tpXSwgcHJldiA9IHN0cm9rZVBvaW50c1tpIC0gMV0pIHtcbiAgICB2YXIgcG9pbnQgPSBscnAocHJldi5wb2ludCwgY3VyciwgMSAtIHN0cmVhbWxpbmUpLFxuICAgICAgICBwcmVzc3VyZSA9IGN1cnJbMl0sXG4gICAgICAgIHZlY3RvciA9IHVuaSh2ZWMocG9pbnQsIHByZXYucG9pbnQpKSxcbiAgICAgICAgZGlzdGFuY2UgPSBkaXN0KHBvaW50LCBwcmV2LnBvaW50KSxcbiAgICAgICAgcnVubmluZ0xlbmd0aCA9IHByZXYucnVubmluZ0xlbmd0aCArIGRpc3RhbmNlO1xuICAgIHN0cm9rZVBvaW50cy5wdXNoKHtcbiAgICAgIHBvaW50OiBwb2ludCxcbiAgICAgIHByZXNzdXJlOiBwcmVzc3VyZSxcbiAgICAgIHZlY3RvcjogdmVjdG9yLFxuICAgICAgZGlzdGFuY2U6IGRpc3RhbmNlLFxuICAgICAgcnVubmluZ0xlbmd0aDogcnVubmluZ0xlbmd0aFxuICAgIH0pO1xuICB9XG4gIC8qXHJcbiAgICBBbGlnbiB2ZWN0b3JzIGF0IHRoZSBlbmQgb2YgdGhlIGxpbmVcclxuICAgICAgIFN0YXJ0aW5nIGZyb20gdGhlIGxhc3QgcG9pbnQsIHdvcmsgYmFjayB1bnRpbCB3ZSd2ZSB0cmF2ZWxlZCBtb3JlIHRoYW5cclxuICAgIGhhbGYgb2YgdGhlIGxpbmUncyBzaXplICh3aWR0aCkuIFRha2UgdGhlIGN1cnJlbnQgcG9pbnQncyB2ZWN0b3IgYW5kIHRoZW5cclxuICAgIHdvcmsgZm9yd2FyZCwgc2V0dGluZyBhbGwgcmVtYWluaW5nIHBvaW50cycgdmVjdG9ycyB0byB0aGlzIHZlY3Rvci4gVGhpc1xyXG4gICAgcmVtb3ZlcyB0aGUgXCJub2lzZVwiIGF0IHRoZSBlbmQgb2YgdGhlIGxpbmUgYW5kIGFsbG93cyBmb3IgYSBiZXR0ZXItZmFjaW5nXHJcbiAgICBlbmQgY2FwLlxyXG4gICovXG5cblxuICB2YXIgdG90YWxMZW5ndGggPSBzdHJva2VQb2ludHNbbGVuIC0gMV0ucnVubmluZ0xlbmd0aDtcblxuICBmb3IgKHZhciBfaSA9IGxlbiAtIDI7IF9pID4gMTsgX2ktLSkge1xuICAgIHZhciBfc3Ryb2tlUG9pbnRzJF9pID0gc3Ryb2tlUG9pbnRzW19pXSxcbiAgICAgICAgX3J1bm5pbmdMZW5ndGggPSBfc3Ryb2tlUG9pbnRzJF9pLnJ1bm5pbmdMZW5ndGgsXG4gICAgICAgIF92ZWN0b3IgPSBfc3Ryb2tlUG9pbnRzJF9pLnZlY3RvcjtcblxuICAgIGlmICh0b3RhbExlbmd0aCAtIF9ydW5uaW5nTGVuZ3RoID4gc2l6ZSAvIDIgfHwgZHByKHN0cm9rZVBvaW50c1tfaSAtIDFdLnZlY3Rvciwgc3Ryb2tlUG9pbnRzW19pXS52ZWN0b3IpIDwgMC44KSB7XG4gICAgICBmb3IgKHZhciBqID0gX2k7IGogPCBsZW47IGorKykge1xuICAgICAgICBzdHJva2VQb2ludHNbal0udmVjdG9yID0gX3ZlY3RvcjtcbiAgICAgIH1cblxuICAgICAgYnJlYWs7XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHN0cm9rZVBvaW50cztcbn1cbi8qKlxyXG4gKiAjIyBnZXRTdHJva2VPdXRsaW5lUG9pbnRzXHJcbiAqIEBkZXNjcmlwdGlvbiBHZXQgYW4gYXJyYXkgb2YgcG9pbnRzIChhcyBgW3gsIHldYCkgcmVwcmVzZW50aW5nIHRoZSBvdXRsaW5lIG9mIGEgc3Ryb2tlLlxyXG4gKiBAcGFyYW0gcG9pbnRzIEFuIGFycmF5IG9mIHBvaW50cyAoYXMgYFt4LCB5LCBwcmVzc3VyZV1gIG9yIGB7eCwgeSwgcHJlc3N1cmV9YCkuIFByZXNzdXJlIGlzIG9wdGlvbmFsLlxyXG4gKiBAcGFyYW0gb3B0aW9ucyBBbiAob3B0aW9uYWwpIG9iamVjdCB3aXRoIG9wdGlvbnMuXHJcbiAqIEBwYXJhbSBvcHRpb25zLnNpemVcdFRoZSBiYXNlIHNpemUgKGRpYW1ldGVyKSBvZiB0aGUgc3Ryb2tlLlxyXG4gKiBAcGFyYW0gb3B0aW9ucy50aGlubmluZyBUaGUgZWZmZWN0IG9mIHByZXNzdXJlIG9uIHRoZSBzdHJva2UncyBzaXplLlxyXG4gKiBAcGFyYW0gb3B0aW9ucy5zbW9vdGhpbmdcdEhvdyBtdWNoIHRvIHNvZnRlbiB0aGUgc3Ryb2tlJ3MgZWRnZXMuXHJcbiAqIEBwYXJhbSBvcHRpb25zLmVhc2luZ1x0QW4gZWFzaW5nIGZ1bmN0aW9uIHRvIGFwcGx5IHRvIGVhY2ggcG9pbnQncyBwcmVzc3VyZS5cclxuICogQHBhcmFtIG9wdGlvbnMuc2ltdWxhdGVQcmVzc3VyZSBXaGV0aGVyIHRvIHNpbXVsYXRlIHByZXNzdXJlIGJhc2VkIG9uIHZlbG9jaXR5LlxyXG4gKiBAcGFyYW0gb3B0aW9ucy5zdGFydCBUYXBlcmluZyBhbmQgZWFzaW5nIGZ1bmN0aW9uIGZvciB0aGUgc3RhcnQgb2YgdGhlIGxpbmUuXHJcbiAqIEBwYXJhbSBvcHRpb25zLmVuZCBUYXBlcmluZyBhbmQgZWFzaW5nIGZ1bmN0aW9uIGZvciB0aGUgZW5kIG9mIHRoZSBsaW5lLlxyXG4gKiBAcGFyYW0gb3B0aW9ucy5sYXN0IFdoZXRoZXIgdG8gaGFuZGxlIHRoZSBwb2ludHMgYXMgYSBjb21wbGV0ZWQgc3Ryb2tlLlxyXG4gKi9cblxuZnVuY3Rpb24gZ2V0U3Ryb2tlT3V0bGluZVBvaW50cyhwb2ludHMsIG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuXG4gIHZhciBfb3B0aW9ucyA9IG9wdGlvbnMsXG4gICAgICBfb3B0aW9ucyRzaXplMiA9IF9vcHRpb25zLnNpemUsXG4gICAgICBzaXplID0gX29wdGlvbnMkc2l6ZTIgPT09IHZvaWQgMCA/IDggOiBfb3B0aW9ucyRzaXplMixcbiAgICAgIF9vcHRpb25zJHRoaW5uaW5nID0gX29wdGlvbnMudGhpbm5pbmcsXG4gICAgICB0aGlubmluZyA9IF9vcHRpb25zJHRoaW5uaW5nID09PSB2b2lkIDAgPyAwLjUgOiBfb3B0aW9ucyR0aGlubmluZyxcbiAgICAgIF9vcHRpb25zJHNtb290aGluZyA9IF9vcHRpb25zLnNtb290aGluZyxcbiAgICAgIHNtb290aGluZyA9IF9vcHRpb25zJHNtb290aGluZyA9PT0gdm9pZCAwID8gMC41IDogX29wdGlvbnMkc21vb3RoaW5nLFxuICAgICAgX29wdGlvbnMkc2ltdWxhdGVQcmVzMiA9IF9vcHRpb25zLnNpbXVsYXRlUHJlc3N1cmUsXG4gICAgICBzaW11bGF0ZVByZXNzdXJlID0gX29wdGlvbnMkc2ltdWxhdGVQcmVzMiA9PT0gdm9pZCAwID8gdHJ1ZSA6IF9vcHRpb25zJHNpbXVsYXRlUHJlczIsXG4gICAgICBfb3B0aW9ucyRlYXNpbmcgPSBfb3B0aW9ucy5lYXNpbmcsXG4gICAgICBlYXNpbmcgPSBfb3B0aW9ucyRlYXNpbmcgPT09IHZvaWQgMCA/IGZ1bmN0aW9uICh0KSB7XG4gICAgcmV0dXJuIHQ7XG4gIH0gOiBfb3B0aW9ucyRlYXNpbmcsXG4gICAgICBfb3B0aW9ucyRzdGFydCA9IF9vcHRpb25zLnN0YXJ0LFxuICAgICAgc3RhcnQgPSBfb3B0aW9ucyRzdGFydCA9PT0gdm9pZCAwID8ge30gOiBfb3B0aW9ucyRzdGFydCxcbiAgICAgIF9vcHRpb25zJGVuZCA9IF9vcHRpb25zLmVuZCxcbiAgICAgIGVuZCA9IF9vcHRpb25zJGVuZCA9PT0gdm9pZCAwID8ge30gOiBfb3B0aW9ucyRlbmQsXG4gICAgICBfb3B0aW9ucyRsYXN0ID0gX29wdGlvbnMubGFzdCxcbiAgICAgIGlzQ29tcGxldGUgPSBfb3B0aW9ucyRsYXN0ID09PSB2b2lkIDAgPyBmYWxzZSA6IF9vcHRpb25zJGxhc3Q7XG4gIHZhciBfb3B0aW9uczIgPSBvcHRpb25zLFxuICAgICAgX29wdGlvbnMyJHN0cmVhbWxpbmUgPSBfb3B0aW9uczIuc3RyZWFtbGluZSxcbiAgICAgIHN0cmVhbWxpbmUgPSBfb3B0aW9uczIkc3RyZWFtbGluZSA9PT0gdm9pZCAwID8gMC41IDogX29wdGlvbnMyJHN0cmVhbWxpbmU7XG4gIHN0cmVhbWxpbmUgLz0gMjtcbiAgdmFyIF9zdGFydCR0YXBlciA9IHN0YXJ0LnRhcGVyLFxuICAgICAgdGFwZXJTdGFydCA9IF9zdGFydCR0YXBlciA9PT0gdm9pZCAwID8gMCA6IF9zdGFydCR0YXBlcixcbiAgICAgIF9zdGFydCRlYXNpbmcgPSBzdGFydC5lYXNpbmcsXG4gICAgICB0YXBlclN0YXJ0RWFzZSA9IF9zdGFydCRlYXNpbmcgPT09IHZvaWQgMCA/IGZ1bmN0aW9uICh0KSB7XG4gICAgcmV0dXJuIHQgKiAoMiAtIHQpO1xuICB9IDogX3N0YXJ0JGVhc2luZztcbiAgdmFyIF9lbmQkdGFwZXIgPSBlbmQudGFwZXIsXG4gICAgICB0YXBlckVuZCA9IF9lbmQkdGFwZXIgPT09IHZvaWQgMCA/IDAgOiBfZW5kJHRhcGVyLFxuICAgICAgX2VuZCRlYXNpbmcgPSBlbmQuZWFzaW5nLFxuICAgICAgdGFwZXJFbmRFYXNlID0gX2VuZCRlYXNpbmcgPT09IHZvaWQgMCA/IGZ1bmN0aW9uICh0KSB7XG4gICAgcmV0dXJuIC0tdCAqIHQgKiB0ICsgMTtcbiAgfSA6IF9lbmQkZWFzaW5nOyAvLyBUaGUgbnVtYmVyIG9mIHBvaW50cyBpbiB0aGUgYXJyYXlcblxuICB2YXIgbGVuID0gcG9pbnRzLmxlbmd0aDsgLy8gV2UgY2FuJ3QgZG8gYW55dGhpbmcgd2l0aCBhbiBlbXB0eSBhcnJheS5cblxuICBpZiAobGVuID09PSAwKSByZXR1cm4gW107IC8vIFRoZSB0b3RhbCBsZW5ndGggb2YgdGhlIGxpbmVcblxuICB2YXIgdG90YWxMZW5ndGggPSBwb2ludHNbbGVuIC0gMV0ucnVubmluZ0xlbmd0aDsgLy8gT3VyIGNvbGxlY3RlZCBsZWZ0IGFuZCByaWdodCBwb2ludHNcblxuICB2YXIgbGVmdFB0cyA9IFtdO1xuICB2YXIgcmlnaHRQdHMgPSBbXTsgLy8gUHJldmlvdXMgcHJlc3N1cmUgKHN0YXJ0IHdpdGggYXZlcmFnZSBvZiBmaXJzdCBmaXZlIHByZXNzdXJlcylcblxuICB2YXIgcHJldlByZXNzdXJlID0gcG9pbnRzLnNsaWNlKDAsIDUpLnJlZHVjZShmdW5jdGlvbiAoYWNjLCBjdXIpIHtcbiAgICByZXR1cm4gKGFjYyArIGN1ci5wcmVzc3VyZSkgLyAyO1xuICB9LCBwb2ludHNbMF0ucHJlc3N1cmUpOyAvLyBUaGUgY3VycmVudCByYWRpdXNcblxuICB2YXIgcmFkaXVzID0gZ2V0U3Ryb2tlUmFkaXVzKHNpemUsIHRoaW5uaW5nLCBlYXNpbmcsIHBvaW50c1tsZW4gLSAxXS5wcmVzc3VyZSk7IC8vIFByZXZpb3VzIHZlY3RvclxuXG4gIHZhciBwcmV2VmVjdG9yID0gcG9pbnRzWzBdLnZlY3RvcjsgLy8gUHJldmlvdXMgbGVmdCBhbmQgcmlnaHQgcG9pbnRzXG5cbiAgdmFyIHBsID0gcG9pbnRzWzBdLnBvaW50O1xuICB2YXIgcHIgPSBwbDsgLy8gVGVtcG9yYXJ5IGxlZnQgYW5kIHJpZ2h0IHBvaW50c1xuXG4gIHZhciB0bCA9IHBsO1xuICB2YXIgdHIgPSBwcjtcbiAgLypcclxuICAgIEZpbmQgdGhlIG91dGxpbmUncyBsZWZ0IGFuZCByaWdodCBwb2ludHNcclxuICAgICAgSXRlcmF0aW5nIHRocm91Z2ggdGhlIHBvaW50cyBhbmQgcG9wdWxhdGUgdGhlIHJpZ2h0UHRzIGFuZCBsZWZ0UHRzIGFycmF5cyxcclxuICAgc2tpcHBpbmcgdGhlIGZpcnN0IGFuZCBsYXN0IHBvaW50c20sIHdoaWNoIHdpbGwgZ2V0IGNhcHMgbGF0ZXIgb24uXHJcbiAgKi9cblxuICBmb3IgKHZhciBpID0gMTsgaSA8IGxlbiAtIDE7IGkrKykge1xuICAgIHZhciBfcG9pbnRzJGkgPSBwb2ludHNbaV0sXG4gICAgICAgIHBvaW50ID0gX3BvaW50cyRpLnBvaW50LFxuICAgICAgICBwcmVzc3VyZSA9IF9wb2ludHMkaS5wcmVzc3VyZSxcbiAgICAgICAgdmVjdG9yID0gX3BvaW50cyRpLnZlY3RvcixcbiAgICAgICAgZGlzdGFuY2UgPSBfcG9pbnRzJGkuZGlzdGFuY2UsXG4gICAgICAgIHJ1bm5pbmdMZW5ndGggPSBfcG9pbnRzJGkucnVubmluZ0xlbmd0aDtcbiAgICAvKlxyXG4gICAgICBDYWxjdWxhdGUgdGhlIHJhZGl1c1xyXG4gICAgICAgICAgIElmIG5vdCB0aGlubmluZywgdGhlIGN1cnJlbnQgcG9pbnQncyByYWRpdXMgd2lsbCBiZSBoYWxmIHRoZSBzaXplOyBvclxyXG4gICAgICBvdGhlcndpc2UsIHRoZSBzaXplIHdpbGwgYmUgYmFzZWQgb24gdGhlIGN1cnJlbnQgKHJlYWwgb3Igc2ltdWxhdGVkKVxyXG4gICAgICBwcmVzc3VyZS5cclxuICAgICovXG5cbiAgICBpZiAodGhpbm5pbmcpIHtcbiAgICAgIGlmIChzaW11bGF0ZVByZXNzdXJlKSB7XG4gICAgICAgIHZhciBycCA9IG1pbigxLCAxIC0gZGlzdGFuY2UgLyBzaXplKTtcbiAgICAgICAgdmFyIHNwID0gbWluKDEsIGRpc3RhbmNlIC8gc2l6ZSk7XG4gICAgICAgIHByZXNzdXJlID0gbWluKDEsIHByZXZQcmVzc3VyZSArIChycCAtIHByZXZQcmVzc3VyZSkgKiAoc3AgLyAyKSk7XG4gICAgICB9XG5cbiAgICAgIHJhZGl1cyA9IGdldFN0cm9rZVJhZGl1cyhzaXplLCB0aGlubmluZywgZWFzaW5nLCBwcmVzc3VyZSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHJhZGl1cyA9IHNpemUgLyAyO1xuICAgIH1cbiAgICAvKlxyXG4gICAgICBBcHBseSB0YXBlcmluZ1xyXG4gICAgICAgICAgIElmIHRoZSBjdXJyZW50IGxlbmd0aCBpcyB3aXRoaW4gdGhlIHRhcGVyIGRpc3RhbmNlIGF0IGVpdGhlciB0aGVcclxuICAgICAgc3RhcnQgb3IgdGhlIGVuZCwgY2FsY3VsYXRlIHRoZSB0YXBlciBzdHJlbmd0aHMuIEFwcGx5IHRoZSBzbWFsbGVyXHJcbiAgICAgIG9mIHRoZSB0d28gdGFwZXIgc3RyZW5ndGhzIHRvIHRoZSByYWRpdXMuXHJcbiAgICAqL1xuXG5cbiAgICB2YXIgdHMgPSBydW5uaW5nTGVuZ3RoIDwgdGFwZXJTdGFydCA/IHRhcGVyU3RhcnRFYXNlKHJ1bm5pbmdMZW5ndGggLyB0YXBlclN0YXJ0KSA6IDE7XG4gICAgdmFyIHRlID0gdG90YWxMZW5ndGggLSBydW5uaW5nTGVuZ3RoIDwgdGFwZXJFbmQgPyB0YXBlckVuZEVhc2UoKHRvdGFsTGVuZ3RoIC0gcnVubmluZ0xlbmd0aCkgLyB0YXBlckVuZCkgOiAxO1xuICAgIHJhZGl1cyAqPSBNYXRoLm1pbih0cywgdGUpO1xuICAgIC8qXHJcbiAgICAgIEhhbmRsZSBzaGFycCBjb3JuZXJzXHJcbiAgICAgICAgICAgRmluZCB0aGUgZGlmZmVyZW5jZSAoZG90IHByb2R1Y3QpIGJldHdlZW4gdGhlIGN1cnJlbnQgYW5kIG5leHQgdmVjdG9yLlxyXG4gICAgICBJZiB0aGUgbmV4dCB2ZWN0b3IgaXMgYXQgbW9yZSB0aGFuIGEgcmlnaHQgYW5nbGUgdG8gdGhlIGN1cnJlbnQgdmVjdG9yLFxyXG4gICAgICBkcmF3IGEgY2FwIGF0IHRoZSBjdXJyZW50IHBvaW50LlxyXG4gICAgKi9cblxuICAgIHZhciBuZXh0VmVjdG9yID0gcG9pbnRzW2kgKyAxXS52ZWN0b3I7XG4gICAgdmFyIGRwciQxID0gZHByKHZlY3RvciwgbmV4dFZlY3Rvcik7XG5cbiAgICBpZiAoZHByJDEgPCAwKSB7XG4gICAgICB2YXIgX29mZnNldCA9IG11bChwZXIocHJldlZlY3RvciksIHJhZGl1cyk7XG5cbiAgICAgIHZhciBsYSA9IGFkZChwb2ludCwgX29mZnNldCk7XG4gICAgICB2YXIgcmEgPSBzdWIocG9pbnQsIF9vZmZzZXQpO1xuXG4gICAgICBmb3IgKHZhciB0ID0gMC4yOyB0IDwgMTsgdCArPSAwLjIpIHtcbiAgICAgICAgdHIgPSByb3RBcm91bmQobGEsIHBvaW50LCBQSSAqIC10KTtcbiAgICAgICAgdGwgPSByb3RBcm91bmQocmEsIHBvaW50LCBQSSAqIHQpO1xuICAgICAgICByaWdodFB0cy5wdXNoKHRyKTtcbiAgICAgICAgbGVmdFB0cy5wdXNoKHRsKTtcbiAgICAgIH1cblxuICAgICAgcGwgPSB0bDtcbiAgICAgIHByID0gdHI7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgLypcclxuICAgICAgQWRkIHJlZ3VsYXIgcG9pbnRzXHJcbiAgICAgICAgICAgUHJvamVjdCBwb2ludHMgdG8gZWl0aGVyIHNpZGUgb2YgdGhlIGN1cnJlbnQgcG9pbnQsIHVzaW5nIHRoZVxyXG4gICAgICBjYWxjdWxhdGVkIHNpemUgYXMgYSBkaXN0YW5jZS4gSWYgYSBwb2ludCdzIGRpc3RhbmNlIHRvIHRoZVxyXG4gICAgICBwcmV2aW91cyBwb2ludCBvbiB0aGF0IHNpZGUgZ3JlYXRlciB0aGFuIHRoZSBtaW5pbXVtIGRpc3RhbmNlXHJcbiAgICAgIChvciBpZiB0aGUgY29ybmVyIGlzIGtpbmRhIHNoYXJwKSwgYWRkIHRoZSBwb2ludHMgdG8gdGhlIHNpZGUnc1xyXG4gICAgICBwb2ludHMgYXJyYXkuXHJcbiAgICAqL1xuXG5cbiAgICB2YXIgb2Zmc2V0ID0gbXVsKHBlcihscnAobmV4dFZlY3RvciwgdmVjdG9yLCBkcHIkMSkpLCByYWRpdXMpO1xuICAgIHRsID0gc3ViKHBvaW50LCBvZmZzZXQpO1xuICAgIHRyID0gYWRkKHBvaW50LCBvZmZzZXQpO1xuICAgIHZhciBhbHdheXNBZGQgPSBpID09PSAxIHx8IGRwciQxIDwgMC4yNTtcbiAgICB2YXIgbWluRGlzdGFuY2UgPSAocnVubmluZ0xlbmd0aCA+IHNpemUgPyBzaXplIDogc2l6ZSAvIDIpICogc21vb3RoaW5nO1xuXG4gICAgaWYgKGFsd2F5c0FkZCB8fCBkaXN0KHBsLCB0bCkgPiBtaW5EaXN0YW5jZSkge1xuICAgICAgbGVmdFB0cy5wdXNoKGxycChwbCwgdGwsIHN0cmVhbWxpbmUpKTtcbiAgICAgIHBsID0gdGw7XG4gICAgfVxuXG4gICAgaWYgKGFsd2F5c0FkZCB8fCBkaXN0KHByLCB0cikgPiBtaW5EaXN0YW5jZSkge1xuICAgICAgcmlnaHRQdHMucHVzaChscnAocHIsIHRyLCBzdHJlYW1saW5lKSk7XG4gICAgICBwciA9IHRyO1xuICAgIH0gLy8gU2V0IHZhcmlhYmxlcyBmb3IgbmV4dCBpdGVyYXRpb25cblxuXG4gICAgcHJldlByZXNzdXJlID0gcHJlc3N1cmU7XG4gICAgcHJldlZlY3RvciA9IHZlY3RvcjtcbiAgfVxuICAvKlxyXG4gICAgRHJhd2luZyBjYXBzXHJcbiAgICBcbiAgICBOb3cgdGhhdCB3ZSBoYXZlIG91ciBwb2ludHMgb24gZWl0aGVyIHNpZGUgb2YgdGhlIGxpbmUsIHdlIG5lZWQgdG9cclxuICAgIGRyYXcgY2FwcyBhdCB0aGUgc3RhcnQgYW5kIGVuZC4gVGFwZXJlZCBsaW5lcyBkb24ndCBoYXZlIGNhcHMsIGJ1dFxyXG4gICAgbWF5IGhhdmUgZG90cyBmb3IgdmVyeSBzaG9ydCBsaW5lcy5cclxuICAqL1xuXG5cbiAgdmFyIGZpcnN0UG9pbnQgPSBwb2ludHNbMF07XG4gIHZhciBsYXN0UG9pbnQgPSBwb2ludHNbbGVuIC0gMV07XG4gIHZhciBpc1ZlcnlTaG9ydCA9IHJpZ2h0UHRzLmxlbmd0aCA8IDIgfHwgbGVmdFB0cy5sZW5ndGggPCAyO1xuICAvKlxyXG4gICAgRHJhdyBhIGRvdCBmb3IgdmVyeSBzaG9ydCBvciBjb21wbGV0ZWQgc3Ryb2tlc1xyXG4gICAgXG4gICAgSWYgdGhlIGxpbmUgaXMgdG9vIHNob3J0IHRvIGdhdGhlciBsZWZ0IG9yIHJpZ2h0IHBvaW50cyBhbmQgaWYgdGhlIGxpbmUgaXNcclxuICAgIG5vdCB0YXBlcmVkIG9uIGVpdGhlciBzaWRlLCBkcmF3IGEgZG90LiBJZiB0aGUgbGluZSBpcyB0YXBlcmVkLCB0aGVuIG9ubHlcclxuICAgIGRyYXcgYSBkb3QgaWYgdGhlIGxpbmUgaXMgYm90aCB2ZXJ5IHNob3J0IGFuZCBjb21wbGV0ZS4gSWYgd2UgZHJhdyBhIGRvdCxcclxuICAgIHdlIGNhbiBqdXN0IHJldHVybiB0aG9zZSBwb2ludHMuXHJcbiAgKi9cblxuICBpZiAoaXNWZXJ5U2hvcnQgJiYgKCEodGFwZXJTdGFydCB8fCB0YXBlckVuZCkgfHwgaXNDb21wbGV0ZSkpIHtcbiAgICB2YXIgaXIgPSAwO1xuXG4gICAgZm9yICh2YXIgX2kyID0gMDsgX2kyIDwgbGVuOyBfaTIrKykge1xuICAgICAgdmFyIF9wb2ludHMkX2kgPSBwb2ludHNbX2kyXSxcbiAgICAgICAgICBfcHJlc3N1cmUgPSBfcG9pbnRzJF9pLnByZXNzdXJlLFxuICAgICAgICAgIF9ydW5uaW5nTGVuZ3RoMiA9IF9wb2ludHMkX2kucnVubmluZ0xlbmd0aDtcblxuICAgICAgaWYgKF9ydW5uaW5nTGVuZ3RoMiA+IHNpemUpIHtcbiAgICAgICAgaXIgPSBnZXRTdHJva2VSYWRpdXMoc2l6ZSwgdGhpbm5pbmcsIGVhc2luZywgX3ByZXNzdXJlKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgdmFyIF9zdGFydCA9IHN1YihmaXJzdFBvaW50LnBvaW50LCBtdWwocGVyKHVuaSh2ZWMobGFzdFBvaW50LnBvaW50LCBmaXJzdFBvaW50LnBvaW50KSkpLCBpciB8fCByYWRpdXMpKTtcblxuICAgIHZhciBkb3RQdHMgPSBbXTtcblxuICAgIGZvciAodmFyIF90ID0gMCwgc3RlcCA9IDAuMTsgX3QgPD0gMTsgX3QgKz0gc3RlcCkge1xuICAgICAgZG90UHRzLnB1c2gocm90QXJvdW5kKF9zdGFydCwgZmlyc3RQb2ludC5wb2ludCwgUEkgKiAyICogX3QpKTtcbiAgICB9XG5cbiAgICByZXR1cm4gZG90UHRzO1xuICB9XG4gIC8qXHJcbiAgICBEcmF3IGEgc3RhcnQgY2FwXHJcbiAgICAgICBVbmxlc3MgdGhlIGxpbmUgaGFzIGEgdGFwZXJlZCBzdGFydCwgb3IgdW5sZXNzIHRoZSBsaW5lIGhhcyBhIHRhcGVyZWQgZW5kXHJcbiAgICBhbmQgdGhlIGxpbmUgaXMgdmVyeSBzaG9ydCwgZHJhdyBhIHN0YXJ0IGNhcCBhcm91bmQgdGhlIGZpcnN0IHBvaW50LiBVc2VcclxuICAgIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBzZWNvbmQgbGVmdCBhbmQgcmlnaHQgcG9pbnQgZm9yIHRoZSBjYXAncyByYWRpdXMuXHJcbiAgICBGaW5hbGx5IHJlbW92ZSB0aGUgZmlyc3QgbGVmdCBhbmQgcmlnaHQgcG9pbnRzLiA6cHN5ZHVjazpcclxuICAqL1xuXG5cbiAgdmFyIHN0YXJ0Q2FwID0gW107XG5cbiAgaWYgKCF0YXBlclN0YXJ0ICYmICEodGFwZXJFbmQgJiYgaXNWZXJ5U2hvcnQpKSB7XG4gICAgdHIgPSByaWdodFB0c1sxXTtcbiAgICB0bCA9IGxlZnRQdHNbMV07XG5cbiAgICB2YXIgX3N0YXJ0MiA9IHN1YihmaXJzdFBvaW50LnBvaW50LCBtdWwodW5pKHZlYyh0ciwgdGwpKSwgZGlzdCh0ciwgdGwpIC8gMikpO1xuXG4gICAgZm9yICh2YXIgX3QyID0gMCwgX3N0ZXAgPSAwLjI7IF90MiA8PSAxOyBfdDIgKz0gX3N0ZXApIHtcbiAgICAgIHN0YXJ0Q2FwLnB1c2gocm90QXJvdW5kKF9zdGFydDIsIGZpcnN0UG9pbnQucG9pbnQsIFBJICogX3QyKSk7XG4gICAgfVxuXG4gICAgbGVmdFB0cy5zaGlmdCgpO1xuICAgIHJpZ2h0UHRzLnNoaWZ0KCk7XG4gIH1cbiAgLypcclxuICAgIERyYXcgYW4gZW5kIGNhcFxyXG4gICAgICAgSWYgdGhlIGxpbmUgZG9lcyBub3QgaGF2ZSBhIHRhcGVyZWQgZW5kLCBhbmQgdW5sZXNzIHRoZSBsaW5lIGhhcyBhIHRhcGVyZWRcclxuICAgIHN0YXJ0IGFuZCB0aGUgbGluZSBpcyB2ZXJ5IHNob3J0LCBkcmF3IGEgY2FwIGFyb3VuZCB0aGUgbGFzdCBwb2ludC4gRmluYWxseSxcclxuICAgIHJlbW92ZSB0aGUgbGFzdCBsZWZ0IGFuZCByaWdodCBwb2ludHMuIE90aGVyd2lzZSwgYWRkIHRoZSBsYXN0IHBvaW50LiBOb3RlXHJcbiAgICB0aGF0IFRoaXMgY2FwIGlzIGEgZnVsbC10dXJuLWFuZC1hLWhhbGY6IHRoaXMgcHJldmVudHMgaW5jb3JyZWN0IGNhcHMgb25cclxuICAgIHNoYXJwIGVuZCB0dXJucy5cclxuICAqL1xuXG5cbiAgdmFyIGVuZENhcCA9IFtdO1xuXG4gIGlmICghdGFwZXJFbmQgJiYgISh0YXBlclN0YXJ0ICYmIGlzVmVyeVNob3J0KSkge1xuICAgIHZhciBfc3RhcnQzID0gc3ViKGxhc3RQb2ludC5wb2ludCwgbXVsKHBlcihsYXN0UG9pbnQudmVjdG9yKSwgcmFkaXVzKSk7XG5cbiAgICBmb3IgKHZhciBfdDMgPSAwLCBfc3RlcDIgPSAwLjE7IF90MyA8PSAxOyBfdDMgKz0gX3N0ZXAyKSB7XG4gICAgICBlbmRDYXAucHVzaChyb3RBcm91bmQoX3N0YXJ0MywgbGFzdFBvaW50LnBvaW50LCBQSSAqIDMgKiBfdDMpKTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgZW5kQ2FwLnB1c2gobGFzdFBvaW50LnBvaW50KTtcbiAgfVxuICAvKlxyXG4gICAgUmV0dXJuIHRoZSBwb2ludHMgaW4gdGhlIGNvcnJlY3Qgd2luZGluZCBvcmRlcjogYmVnaW4gb24gdGhlIGxlZnQgc2lkZSwgdGhlblxyXG4gICAgY29udGludWUgYXJvdW5kIHRoZSBlbmQgY2FwLCB0aGVuIGNvbWUgYmFjayBhbG9uZyB0aGUgcmlnaHQgc2lkZSwgYW5kIGZpbmFsbHlcclxuICAgIGNvbXBsZXRlIHRoZSBzdGFydCBjYXAuXHJcbiAgKi9cblxuXG4gIHJldHVybiBsZWZ0UHRzLmNvbmNhdChlbmRDYXAsIHJpZ2h0UHRzLnJldmVyc2UoKSwgc3RhcnRDYXApO1xufVxuLyoqXHJcbiAqICMjIGdldFN0cm9rZVxyXG4gKiBAZGVzY3JpcHRpb24gUmV0dXJucyBhIHN0cm9rZSBhcyBhbiBhcnJheSBvZiBvdXRsaW5lIHBvaW50cy5cclxuICogQHBhcmFtIHBvaW50cyBBbiBhcnJheSBvZiBwb2ludHMgKGFzIGBbeCwgeSwgcHJlc3N1cmVdYCBvciBge3gsIHksIHByZXNzdXJlfWApLiBQcmVzc3VyZSBpcyBvcHRpb25hbC5cclxuICogQHBhcmFtIG9wdGlvbnMgQW4gKG9wdGlvbmFsKSBvYmplY3Qgd2l0aCBvcHRpb25zLlxyXG4gKiBAcGFyYW0gb3B0aW9ucy5zaXplXHRUaGUgYmFzZSBzaXplIChkaWFtZXRlcikgb2YgdGhlIHN0cm9rZS5cclxuICogQHBhcmFtIG9wdGlvbnMudGhpbm5pbmcgVGhlIGVmZmVjdCBvZiBwcmVzc3VyZSBvbiB0aGUgc3Ryb2tlJ3Mgc2l6ZS5cclxuICogQHBhcmFtIG9wdGlvbnMuc21vb3RoaW5nXHRIb3cgbXVjaCB0byBzb2Z0ZW4gdGhlIHN0cm9rZSdzIGVkZ2VzLlxyXG4gKiBAcGFyYW0gb3B0aW9ucy5lYXNpbmdcdEFuIGVhc2luZyBmdW5jdGlvbiB0byBhcHBseSB0byBlYWNoIHBvaW50J3MgcHJlc3N1cmUuXHJcbiAqIEBwYXJhbSBvcHRpb25zLnNpbXVsYXRlUHJlc3N1cmUgV2hldGhlciB0byBzaW11bGF0ZSBwcmVzc3VyZSBiYXNlZCBvbiB2ZWxvY2l0eS5cclxuICogQHBhcmFtIG9wdGlvbnMuc3RhcnQgVGFwZXJpbmcgYW5kIGVhc2luZyBmdW5jdGlvbiBmb3IgdGhlIHN0YXJ0IG9mIHRoZSBsaW5lLlxyXG4gKiBAcGFyYW0gb3B0aW9ucy5lbmQgVGFwZXJpbmcgYW5kIGVhc2luZyBmdW5jdGlvbiBmb3IgdGhlIGVuZCBvZiB0aGUgbGluZS5cclxuICogQHBhcmFtIG9wdGlvbnMubGFzdCBXaGV0aGVyIHRvIGhhbmRsZSB0aGUgcG9pbnRzIGFzIGEgY29tcGxldGVkIHN0cm9rZS5cclxuICovXG5cbmZ1bmN0aW9uIGdldFN0cm9rZShwb2ludHMsIG9wdGlvbnMpIHtcbiAgaWYgKG9wdGlvbnMgPT09IHZvaWQgMCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuXG4gIHJldHVybiBnZXRTdHJva2VPdXRsaW5lUG9pbnRzKGdldFN0cm9rZVBvaW50cyhwb2ludHMsIG9wdGlvbnMpLCBvcHRpb25zKTtcbn1cblxuZXhwb3J0IGRlZmF1bHQgZ2V0U3Ryb2tlO1xuZXhwb3J0IHsgZ2V0U3Ryb2tlT3V0bGluZVBvaW50cywgZ2V0U3Ryb2tlUG9pbnRzIH07XG4vLyMgc291cmNlTWFwcGluZ1VSTD1wZXJmZWN0LWZyZWVoYW5kLmVzbS5qcy5tYXBcbiIsImltcG9ydCB7IFVJQWN0aW9uVHlwZXMsIFdvcmtlckFjdGlvblR5cGVzLCB9IGZyb20gXCIuLi90eXBlc1wiO1xuaW1wb3J0IHsgZ2V0U3ZnUGF0aEZyb21TdHJva2UsIGFkZFZlY3RvcnMsIGludGVycG9sYXRlQ3ViaWNCZXppZXIsIH0gZnJvbSBcIi4uL3V0aWxzXCI7XG5pbXBvcnQgZ2V0U3Ryb2tlIGZyb20gXCJwZXJmZWN0LWZyZWVoYW5kXCI7XG5pbXBvcnQgeyBjb21wcmVzc1RvVVRGMTYsIGRlY29tcHJlc3NGcm9tVVRGMTYgfSBmcm9tIFwibHotc3RyaW5nXCI7XG4vKiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBDb21tcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAqL1xuLy8gU2VuZHMgYSBtZXNzYWdlIHRvIHRoZSBwbHVnaW4gVUlcbmZ1bmN0aW9uIHBvc3RNZXNzYWdlKHsgdHlwZSwgcGF5bG9hZCB9KSB7XG4gICAgZmlnbWEudWkucG9zdE1lc3NhZ2UoeyB0eXBlLCBwYXlsb2FkIH0pO1xufVxuLy8gU2F2ZSBzb21lIGluZm9ybWF0aW9uIGFib3V0IHRoZSBub2RlIHRvIGl0cyBwbHVnaW4gZGF0YS5cbmZ1bmN0aW9uIHNldE9yaWdpbmFsTm9kZShub2RlKSB7XG4gICAgY29uc3Qgb3JpZ2luYWxOb2RlID0ge1xuICAgICAgICBjZW50ZXI6IGdldENlbnRlcihub2RlKSxcbiAgICAgICAgdmVjdG9yTmV0d29yazogT2JqZWN0LmFzc2lnbih7fSwgbm9kZS52ZWN0b3JOZXR3b3JrKSxcbiAgICAgICAgdmVjdG9yUGF0aHM6IG5vZGUudmVjdG9yUGF0aHMsXG4gICAgfTtcbiAgICBub2RlLnNldFBsdWdpbkRhdGEoXCJwZXJmZWN0X2ZyZWVoYW5kXCIsIGNvbXByZXNzVG9VVEYxNihKU09OLnN0cmluZ2lmeShvcmlnaW5hbE5vZGUpKSk7XG4gICAgcmV0dXJuIG9yaWdpbmFsTm9kZTtcbn1cbmZ1bmN0aW9uIGRlY29tcHJlc3NQbHVnaW5EYXRhKHBsdWdpbkRhdGEpIHtcbiAgICAvLyBEZWNvbXByZXNzIHRoZSBzYXZlZCBkYXRhIGFuZCBwYXJzZSBvdXQgdGhlIG9yaWdpbmFsIG5vZGUuXG4gICAgY29uc3QgZGVjb21wcmVzc2VkID0gZGVjb21wcmVzc0Zyb21VVEYxNihwbHVnaW5EYXRhKTtcbiAgICBpZiAoIWRlY29tcHJlc3NlZCkge1xuICAgICAgICB0aHJvdyBFcnJvcihcIkZvdW5kIHNhdmVkIGRhdGEgZm9yIG9yaWdpbmFsIG5vZGUgYnV0IGNvdWxkIG5vdCBkZWNvbXByZXNzIGl0OiBcIiArXG4gICAgICAgICAgICBkZWNvbXByZXNzZWQpO1xuICAgIH1cbiAgICByZXR1cm4gSlNPTi5wYXJzZShkZWNvbXByZXNzZWQpO1xufVxuLy8gR2V0IGFuIG9yaWdpbmFsIG5vZGUgZnJvbSBhIG5vZGUncyBwbHVnaW4gZGF0YS5cbmZ1bmN0aW9uIGdldE9yaWdpbmFsTm9kZShpZCkge1xuICAgIGxldCBub2RlID0gZmlnbWEuZ2V0Tm9kZUJ5SWQoaWQpO1xuICAgIGlmICghbm9kZSlcbiAgICAgICAgdGhyb3cgRXJyb3IoXCJDb3VsZCBub3QgZmluZCB0aGF0IG5vZGU6IFwiICsgaWQpO1xuICAgIGNvbnN0IHBsdWdpbkRhdGEgPSBub2RlLmdldFBsdWdpbkRhdGEoXCJwZXJmZWN0X2ZyZWVoYW5kXCIpO1xuICAgIC8vIE5vdGhpbmcgb24gdGhlIG5vZGUg4oCUIHdlIGhhdmVuJ3QgbW9kaWZpZWQgaXQuXG4gICAgaWYgKCFwbHVnaW5EYXRhKVxuICAgICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIHJldHVybiBkZWNvbXByZXNzUGx1Z2luRGF0YShwbHVnaW5EYXRhKTtcbn1cbi8qIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gTm9kZXMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tICovXG4vLyBHZXQgdGhlIGN1cnJlbnRseSBzZWxlY3RlZCBWZWN0b3Igbm9kZXMgZm9yIHRoZSBVSS5cbmZ1bmN0aW9uIGdldFNlbGVjdGVkTm9kZXModXBkYXRlQ2VudGVyID0gZmFsc2UpIHtcbiAgICByZXR1cm4gZmlnbWEuY3VycmVudFBhZ2Uuc2VsZWN0aW9uLmZpbHRlcigoeyB0eXBlIH0pID0+IHR5cGUgPT09IFwiVkVDVE9SXCIpLm1hcCgobm9kZSkgPT4ge1xuICAgICAgICBjb25zdCBwbHVnaW5EYXRhID0gbm9kZS5nZXRQbHVnaW5EYXRhKFwicGVyZmVjdF9mcmVlaGFuZFwiKTtcbiAgICAgICAgaWYgKHBsdWdpbkRhdGEgJiYgdXBkYXRlQ2VudGVyKSB7XG4gICAgICAgICAgICBjb25zdCBjZW50ZXIgPSBnZXRDZW50ZXIobm9kZSk7XG4gICAgICAgICAgICBjb25zdCBvcmlnaW5hbE5vZGUgPSBkZWNvbXByZXNzUGx1Z2luRGF0YShwbHVnaW5EYXRhKTtcbiAgICAgICAgICAgIGlmICghKGNlbnRlci54ID09PSBvcmlnaW5hbE5vZGUuY2VudGVyLnggJiZcbiAgICAgICAgICAgICAgICBjZW50ZXIueSA9PT0gb3JpZ2luYWxOb2RlLmNlbnRlci55KSkge1xuICAgICAgICAgICAgICAgIG9yaWdpbmFsTm9kZS5jZW50ZXIgPSBjZW50ZXI7XG4gICAgICAgICAgICAgICAgbm9kZS5zZXRQbHVnaW5EYXRhKFwicGVyZmVjdF9mcmVlaGFuZFwiLCBjb21wcmVzc1RvVVRGMTYoSlNPTi5zdHJpbmdpZnkob3JpZ2luYWxOb2RlKSkpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBpZDogbm9kZS5pZCxcbiAgICAgICAgICAgIG5hbWU6IG5vZGUubmFtZSxcbiAgICAgICAgICAgIHR5cGU6IG5vZGUudHlwZSxcbiAgICAgICAgICAgIGNhblJlc2V0OiAhIXBsdWdpbkRhdGEsXG4gICAgICAgIH07XG4gICAgfSk7XG59XG4vLyBHZXR0aGUgY3VycmVudGx5IHNlbGVjdGVkIFZlY3RvciBub2RlcyBhcyBhbiBhcnJheSBvZiBJZHMuXG5mdW5jdGlvbiBnZXRTZWxlY3RlZE5vZGVJZHMoKSB7XG4gICAgcmV0dXJuIGZpZ21hLmN1cnJlbnRQYWdlLnNlbGVjdGlvbi5maWx0ZXIoKHsgdHlwZSB9KSA9PiB0eXBlID09PSBcIlZFQ1RPUlwiKS5tYXAoKHsgaWQgfSkgPT4gaWQpO1xufVxuLy8gRmluZCB0aGUgY2VudGVyIG9mIGEgbm9kZS5cbmZ1bmN0aW9uIGdldENlbnRlcihub2RlKSB7XG4gICAgbGV0IHsgeCwgeSwgd2lkdGgsIGhlaWdodCB9ID0gbm9kZTtcbiAgICByZXR1cm4geyB4OiB4ICsgd2lkdGggLyAyLCB5OiB5ICsgaGVpZ2h0IC8gMiB9O1xufVxuLy8gTW92ZSBhIG5vZGUgdG8gYSBjZW50ZXIuXG5mdW5jdGlvbiBtb3ZlTm9kZVRvQ2VudGVyKG5vZGUsIGNlbnRlcikge1xuICAgIGNvbnN0IHsgeDogeDAsIHk6IHkwIH0gPSBnZXRDZW50ZXIobm9kZSk7XG4gICAgY29uc3QgeyB4OiB4MSwgeTogeTEgfSA9IGNlbnRlcjtcbiAgICBub2RlLnggPSBub2RlLnggKyB4MSAtIHgwO1xuICAgIG5vZGUueSA9IG5vZGUueSArIHkxIC0geTA7XG59XG4vLyBab29tIHRoZSBGaWdtYSB2aWV3cG9ydCB0byBhIG5vZGUuXG5mdW5jdGlvbiB6b29tVG9Ob2RlKGlkKSB7XG4gICAgY29uc3Qgbm9kZSA9IGZpZ21hLmdldE5vZGVCeUlkKGlkKTtcbiAgICBpZiAoIW5vZGUpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihcIkNvdWxkIG5vdCBmaW5kIHRoYXQgbm9kZTogXCIgKyBpZCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgZmlnbWEudmlld3BvcnQuc2Nyb2xsQW5kWm9vbUludG9WaWV3KFtub2RlXSk7XG59XG4vKiAtLS0tLS0tLS0tLS0tLS0tLS0tLSBTZWxlY3Rpb24gLS0tLS0tLS0tLS0tLS0tLS0tLSAqL1xuLy8gRGVzZWxlY3QgYSBGaWdtYSBub2RlLlxuZnVuY3Rpb24gZGVzZWxlY3ROb2RlKGlkKSB7XG4gICAgY29uc3Qgc2VsZWN0aW9uID0gZmlnbWEuY3VycmVudFBhZ2Uuc2VsZWN0aW9uO1xuICAgIGZpZ21hLmN1cnJlbnRQYWdlLnNlbGVjdGlvbiA9IHNlbGVjdGlvbi5maWx0ZXIoKG5vZGUpID0+IG5vZGUuaWQgIT09IGlkKTtcbn1cbi8vIFNlbmQgdGhlIGN1cnJlbnQgc2VsZWN0aW9uIHRvIHRoZSBVSSBzdGF0ZS5cbmZ1bmN0aW9uIHNlbmRTZWxlY3RlZE5vZGVzKHVwZGF0ZUNlbnRlciA9IHRydWUpIHtcbiAgICBjb25zdCBzZWxlY3RlZE5vZGVzID0gZ2V0U2VsZWN0ZWROb2Rlcyh1cGRhdGVDZW50ZXIpO1xuICAgIHBvc3RNZXNzYWdlKHtcbiAgICAgICAgdHlwZTogV29ya2VyQWN0aW9uVHlwZXMuU0VMRUNURURfTk9ERVMsXG4gICAgICAgIHBheWxvYWQ6IHNlbGVjdGVkTm9kZXMsXG4gICAgfSk7XG59XG4vKiAtLS0tLS0tLS0tLS0tLSBDaGFuZ2luZyBWZWN0b3JOb2RlcyAtLS0tLS0tLS0tLS0tLSAqL1xuLy8gTnVtYmVyIG9mIG5ldyBub2RlcyB0byBpbnNlcnRcbmNvbnN0IFNQTElUID0gNTtcbi8vIFNvbWUgYmFzaWMgZWFzaW5nIGZ1bmN0aW9uc1xuY29uc3QgRUFTSU5HUyA9IHtcbiAgICBsaW5lYXI6ICh0KSA9PiB0LFxuICAgIGVhc2VJbjogKHQpID0+IHQgKiB0LFxuICAgIGVhc2VPdXQ6ICh0KSA9PiB0ICogKDIgLSB0KSxcbiAgICBlYXNlSW5PdXQ6ICh0KSA9PiAodCA8IDAuNSA/IDIgKiB0ICogdCA6IC0xICsgKDQgLSAyICogdCkgKiB0KSxcbn07XG4vLyBDb21wdXRlIGEgc3Ryb2tlIGJhc2VkIG9uIHRoZSB2ZWN0b3IgYW5kIGFwcGx5IGl0IHRvIHRoZSB2ZWN0b3IncyBwYXRoIGRhdGEuXG5mdW5jdGlvbiBhcHBseVBlcmZlY3RGcmVlaGFuZFRvVmVjdG9yTm9kZXMobm9kZUlkcywgeyBvcHRpb25zLCBlYXNpbmcgPSBcImxpbmVhclwiLCBjbGlwLCB9LCByZXN0cmljdFRvS25vd25Ob2RlcyA9IGZhbHNlKSB7XG4gICAgZm9yIChsZXQgaWQgb2Ygbm9kZUlkcykge1xuICAgICAgICAvLyBHZXQgdGhlIG5vZGUgdGhhdCB3ZSB3YW50IHRvIGNoYW5nZVxuICAgICAgICBjb25zdCBub2RlVG9DaGFuZ2UgPSBmaWdtYS5nZXROb2RlQnlJZChpZCk7XG4gICAgICAgIGlmICghbm9kZVRvQ2hhbmdlKSB7XG4gICAgICAgICAgICB0aHJvdyBFcnJvcihcIkNvdWxkIG5vdCBmaW5kIHRoYXQgbm9kZTogXCIgKyBpZCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gR2V0IHRoZSBvcmlnaW5hbCBub2RlXG4gICAgICAgIGxldCBvcmlnaW5hbE5vZGUgPSBnZXRPcmlnaW5hbE5vZGUobm9kZVRvQ2hhbmdlLmlkKTtcbiAgICAgICAgLy8gSWYgd2UgZG9uJ3Qga25vdyB0aGlzIG5vZGUuLi5cbiAgICAgICAgaWYgKCFvcmlnaW5hbE5vZGUpIHtcbiAgICAgICAgICAgIC8vIEJhaWwgaWYgd2UncmUgdXBkYXRpbmcgbm9kZXNcbiAgICAgICAgICAgIGlmIChyZXN0cmljdFRvS25vd25Ob2RlcylcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIC8vIENyZWF0ZSBhIG5ldyBvcmlnaW5hbCBub2RlIGFuZCBjb250aW51ZVxuICAgICAgICAgICAgb3JpZ2luYWxOb2RlID0gc2V0T3JpZ2luYWxOb2RlKG5vZGVUb0NoYW5nZSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gSW50ZXJwb2xhdGUgbmV3IHBvaW50cyBhbG9uZyB0aGUgdmVjdG9yJ3MgY3VydmVcbiAgICAgICAgY29uc3QgcHRzID0gW107XG4gICAgICAgIGZvciAobGV0IHNlZ21lbnQgb2Ygb3JpZ2luYWxOb2RlLnZlY3Rvck5ldHdvcmsuc2VnbWVudHMpIHtcbiAgICAgICAgICAgIGNvbnN0IHAwID0gb3JpZ2luYWxOb2RlLnZlY3Rvck5ldHdvcmsudmVydGljZXNbc2VnbWVudC5zdGFydF07XG4gICAgICAgICAgICBjb25zdCBwMyA9IG9yaWdpbmFsTm9kZS52ZWN0b3JOZXR3b3JrLnZlcnRpY2VzW3NlZ21lbnQuZW5kXTtcbiAgICAgICAgICAgIGNvbnN0IHAxID0gYWRkVmVjdG9ycyhwMCwgc2VnbWVudC50YW5nZW50U3RhcnQpO1xuICAgICAgICAgICAgY29uc3QgcDIgPSBhZGRWZWN0b3JzKHAzLCBzZWdtZW50LnRhbmdlbnRFbmQpO1xuICAgICAgICAgICAgY29uc3QgaW50ZXJwb2xhdG9yID0gaW50ZXJwb2xhdGVDdWJpY0JlemllcihwMCwgcDEsIHAyLCBwMyk7XG4gICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IFNQTElUOyBpKyspIHtcbiAgICAgICAgICAgICAgICBwdHMucHVzaChpbnRlcnBvbGF0b3IoaSAvIFNQTElUKSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ3JlYXRlIGEgbmV3IHN0cm9rZSB1c2luZyBwZXJmZWN0LWZyZWVoYW5kXG4gICAgICAgIGNvbnN0IHN0cm9rZSA9IGdldFN0cm9rZShwdHMsIE9iamVjdC5hc3NpZ24oT2JqZWN0LmFzc2lnbih7fSwgb3B0aW9ucyksIHsgZWFzaW5nOiBFQVNJTkdTW2Vhc2luZ10sIGxhc3Q6IHRydWUgfSkpO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgLy8gU2V0IHN0cm9rZSB0byB2ZWN0b3IgcGF0aHNcbiAgICAgICAgICAgIG5vZGVUb0NoYW5nZS52ZWN0b3JQYXRocyA9IFtcbiAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgIHdpbmRpbmdSdWxlOiBcIk5PTlpFUk9cIixcbiAgICAgICAgICAgICAgICAgICAgZGF0YTogZ2V0U3ZnUGF0aEZyb21TdHJva2Uoc3Ryb2tlKSxcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgXTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZSkge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcihcIkNvdWxkIG5vdCBhcHBseSBzdHJva2VcIiwgZS5tZXNzYWdlKTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIC8vIEFkanVzdCB0aGUgcG9zaXRpb24gb2YgdGhlIG5vZGUgc28gdGhhdCBpdHMgY2VudGVyIGRvZXMgbm90IGNoYW5nZVxuICAgICAgICBtb3ZlTm9kZVRvQ2VudGVyKG5vZGVUb0NoYW5nZSwgb3JpZ2luYWxOb2RlLmNlbnRlcik7XG4gICAgfVxuICAgIHNlbmRTZWxlY3RlZE5vZGVzKGZhbHNlKTtcbn1cbi8vIFJlc2V0IHRoZSBub2RlIHRvIGl0cyBvcmlnaW5hbCBwYXRoIGRhdGEsIHVzaW5nIGRhdGEgZnJvbSBvdXIgY2FjaGUgYW5kIHRoZW4gZGVsZXRlIHRoZSBub2RlLlxuZnVuY3Rpb24gcmVzZXRWZWN0b3JOb2RlcygpIHtcbiAgICBmb3IgKGxldCBpZCBvZiBnZXRTZWxlY3RlZE5vZGVJZHMoKSkge1xuICAgICAgICBjb25zdCBvcmlnaW5hbE5vZGUgPSBnZXRPcmlnaW5hbE5vZGUoaWQpO1xuICAgICAgICAvLyBXZSBoYXZlbid0IG1vZGlmaWVkIHRoaXMgbm9kZS5cbiAgICAgICAgaWYgKCFvcmlnaW5hbE5vZGUpXG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgY29uc3QgY3VycmVudE5vZGUgPSBmaWdtYS5nZXROb2RlQnlJZChpZCk7XG4gICAgICAgIGlmICghY3VycmVudE5vZGUpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJDb3VsZCBub3QgZmluZCB0aGF0IG5vZGU6IFwiICsgaWQpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgY3VycmVudE5vZGUudmVjdG9yUGF0aHMgPSBvcmlnaW5hbE5vZGUudmVjdG9yUGF0aHM7XG4gICAgICAgIGN1cnJlbnROb2RlLnNldFBsdWdpbkRhdGEoXCJwZXJmZWN0X2ZyZWVoYW5kXCIsIFwiXCIpO1xuICAgICAgICBzZW5kU2VsZWN0ZWROb2RlcyhmYWxzZSk7XG4gICAgfVxufVxuLyogLS0tLS0tLS0tLS0tLS0tLS0tLS0tIEtpY2tvZmYgLS0tLS0tLS0tLS0tLS0tLS0tLS0gKi9cbi8vIExpc3RlbiB0byBtZXNzYWdlcyByZWNlaXZlZCBmcm9tIHRoZSBwbHVnaW4gVUlcbmZpZ21hLnVpLm9ubWVzc2FnZSA9IGZ1bmN0aW9uICh7IHR5cGUsIHBheWxvYWQgfSkge1xuICAgIHN3aXRjaCAodHlwZSkge1xuICAgICAgICBjYXNlIFVJQWN0aW9uVHlwZXMuQ0xPU0U6XG4gICAgICAgICAgICBmaWdtYS5jbG9zZVBsdWdpbigpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgVUlBY3Rpb25UeXBlcy5aT09NX1RPX05PREU6XG4gICAgICAgICAgICB6b29tVG9Ob2RlKHBheWxvYWQpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgVUlBY3Rpb25UeXBlcy5ERVNFTEVDVF9OT0RFOlxuICAgICAgICAgICAgZGVzZWxlY3ROb2RlKHBheWxvYWQpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgVUlBY3Rpb25UeXBlcy5SRVNFVF9OT0RFUzpcbiAgICAgICAgICAgIHJlc2V0VmVjdG9yTm9kZXMoKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIFVJQWN0aW9uVHlwZXMuVFJBTlNGT1JNX05PREVTOlxuICAgICAgICAgICAgYXBwbHlQZXJmZWN0RnJlZWhhbmRUb1ZlY3Rvck5vZGVzKGdldFNlbGVjdGVkTm9kZUlkcygpLCBwYXlsb2FkLCBmYWxzZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSBVSUFjdGlvblR5cGVzLlVQREFURURfT1BUSU9OUzpcbiAgICAgICAgICAgIGFwcGx5UGVyZmVjdEZyZWVoYW5kVG9WZWN0b3JOb2RlcyhnZXRTZWxlY3RlZE5vZGVJZHMoKSwgcGF5bG9hZCwgdHJ1ZSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICB9XG59O1xuLy8gTGlzdGVuIGZvciBzZWxlY3Rpb24gY2hhbmdlc1xuZmlnbWEub24oXCJzZWxlY3Rpb25jaGFuZ2VcIiwgc2VuZFNlbGVjdGVkTm9kZXMpO1xuLy8gU2hvdyB0aGUgcGx1Z2luIGludGVyZmFjZVxuZmlnbWEuc2hvd1VJKF9faHRtbF9fLCB7IHdpZHRoOiAzMjAsIGhlaWdodDogNDgwIH0pO1xuLy8gU2VuZCB0aGUgY3VycmVudCBzZWxlY3Rpb24gdG8gdGhlIFVJXG5zZW5kU2VsZWN0ZWROb2RlcygpO1xuIiwiLy8gVUkgYWN0aW9uc1xuZXhwb3J0IHZhciBVSUFjdGlvblR5cGVzO1xuKGZ1bmN0aW9uIChVSUFjdGlvblR5cGVzKSB7XG4gICAgVUlBY3Rpb25UeXBlc1tcIkNMT1NFXCJdID0gXCJDTE9TRVwiO1xuICAgIFVJQWN0aW9uVHlwZXNbXCJaT09NX1RPX05PREVcIl0gPSBcIlpPT01fVE9fTk9ERVwiO1xuICAgIFVJQWN0aW9uVHlwZXNbXCJERVNFTEVDVF9OT0RFXCJdID0gXCJERVNFTEVDVF9OT0RFXCI7XG4gICAgVUlBY3Rpb25UeXBlc1tcIlRSQU5TRk9STV9OT0RFU1wiXSA9IFwiVFJBTlNGT1JNX05PREVTXCI7XG4gICAgVUlBY3Rpb25UeXBlc1tcIlJFU0VUX05PREVTXCJdID0gXCJSRVNFVF9OT0RFU1wiO1xuICAgIFVJQWN0aW9uVHlwZXNbXCJVUERBVEVEX09QVElPTlNcIl0gPSBcIlVQREFURURfT1BUSU9OU1wiO1xufSkoVUlBY3Rpb25UeXBlcyB8fCAoVUlBY3Rpb25UeXBlcyA9IHt9KSk7XG4vLyBXb3JrZXIgYWN0aW9uc1xuZXhwb3J0IHZhciBXb3JrZXJBY3Rpb25UeXBlcztcbihmdW5jdGlvbiAoV29ya2VyQWN0aW9uVHlwZXMpIHtcbiAgICBXb3JrZXJBY3Rpb25UeXBlc1tcIlNFTEVDVEVEX05PREVTXCJdID0gXCJTRUxFQ1RFRF9OT0RFU1wiO1xuICAgIFdvcmtlckFjdGlvblR5cGVzW1wiRk9VTkRfU0VMRUNURURfTk9ERVNcIl0gPSBcIkZPVU5EX1NFTEVDVEVEX05PREVTXCI7XG59KShXb3JrZXJBY3Rpb25UeXBlcyB8fCAoV29ya2VyQWN0aW9uVHlwZXMgPSB7fSkpO1xuIiwiLy8gaW1wb3J0IHBvbHlnb25DbGlwcGluZyBmcm9tIFwicG9seWdvbi1jbGlwcGluZ1wiXG5jb25zdCB7IHBvdyB9ID0gTWF0aDtcbmV4cG9ydCBmdW5jdGlvbiBjdWJpY0Jlemllcih0eCwgeDEsIHkxLCB4MiwgeTIpIHtcbiAgICAvLyBJbnNwaXJlZCBieSBEb24gTGFuY2FzdGVyJ3MgdHdvIGFydGljbGVzXG4gICAgLy8gaHR0cDovL3d3dy50aW5hamEuY29tL2dsaWIvY3ViZW1hdGgucGRmXG4gICAgLy8gaHR0cDovL3d3dy50aW5hamEuY29tL3RleHQvYmV6bWF0aC5odG1sXG4gICAgLy8gU2V0IHAwIGFuZCBwMSBwb2ludFxuICAgIGxldCB4MCA9IDAsIHkwID0gMCwgeDMgPSAxLCB5MyA9IDEsIFxuICAgIC8vIENvbnZlcnQgdGhlIGNvb3JkaW5hdGVzIHRvIGVxdWF0aW9uIHNwYWNlXG4gICAgQSA9IHgzIC0gMyAqIHgyICsgMyAqIHgxIC0geDAsIEIgPSAzICogeDIgLSA2ICogeDEgKyAzICogeDAsIEMgPSAzICogeDEgLSAzICogeDAsIEQgPSB4MCwgRSA9IHkzIC0gMyAqIHkyICsgMyAqIHkxIC0geTAsIEYgPSAzICogeTIgLSA2ICogeTEgKyAzICogeTAsIEcgPSAzICogeTEgLSAzICogeTAsIEggPSB5MCwgXG4gICAgLy8gVmFyaWFibGVzIGZvciB0aGUgbG9vcCBiZWxvd1xuICAgIHQgPSB0eCwgaXRlcmF0aW9ucyA9IDUsIGksIHNsb3BlLCB4LCB5O1xuICAgIC8vIExvb3AgdGhyb3VnaCBhIGZldyB0aW1lcyB0byBnZXQgYSBtb3JlIGFjY3VyYXRlIHRpbWUgdmFsdWUsIGFjY29yZGluZyB0byB0aGUgTmV3dG9uLVJhcGhzb24gbWV0aG9kXG4gICAgLy8gaHR0cDovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9OZXd0b24nc19tZXRob2RcbiAgICBmb3IgKGkgPSAwOyBpIDwgaXRlcmF0aW9uczsgaSsrKSB7XG4gICAgICAgIC8vIFRoZSBjdXJ2ZSdzIHggZXF1YXRpb24gZm9yIHRoZSBjdXJyZW50IHRpbWUgdmFsdWVcbiAgICAgICAgeCA9IEEgKiB0ICogdCAqIHQgKyBCICogdCAqIHQgKyBDICogdCArIEQ7XG4gICAgICAgIC8vIFRoZSBzbG9wZSB3ZSB3YW50IGlzIHRoZSBpbnZlcnNlIG9mIHRoZSBkZXJpdmF0ZSBvZiB4XG4gICAgICAgIHNsb3BlID0gMSAvICgzICogQSAqIHQgKiB0ICsgMiAqIEIgKiB0ICsgQyk7XG4gICAgICAgIC8vIEdldCB0aGUgbmV4dCBlc3RpbWF0ZWQgdGltZSB2YWx1ZSwgd2hpY2ggd2lsbCBiZSBtb3JlIGFjY3VyYXRlIHRoYW4gdGhlIG9uZSBiZWZvcmVcbiAgICAgICAgdCAtPSAoeCAtIHR4KSAqIHNsb3BlO1xuICAgICAgICB0ID0gdCA+IDEgPyAxIDogdCA8IDAgPyAwIDogdDtcbiAgICB9XG4gICAgLy8gRmluZCB0aGUgeSB2YWx1ZSB0aHJvdWdoIHRoZSBjdXJ2ZSdzIHkgZXF1YXRpb24sIHdpdGggdGhlIG5vdyBtb3JlIGFjY3VyYXRlIHRpbWUgdmFsdWVcbiAgICB5ID0gTWF0aC5hYnMoRSAqIHQgKiB0ICogdCArIEYgKiB0ICogdCArIEcgKiB0ICogSCk7XG4gICAgcmV0dXJuIHk7XG59XG5leHBvcnQgZnVuY3Rpb24gZ2V0UG9pbnRzQWxvbmdDdWJpY0JlemllcihwdENvdW50LCBweFRvbGVyYW5jZSwgQXgsIEF5LCBCeCwgQnksIEN4LCBDeSwgRHgsIER5KSB7XG4gICAgbGV0IGRlbHRhQkF4ID0gQnggLSBBeDtcbiAgICBsZXQgZGVsdGFDQnggPSBDeCAtIEJ4O1xuICAgIGxldCBkZWx0YURDeCA9IER4IC0gQ3g7XG4gICAgbGV0IGRlbHRhQkF5ID0gQnkgLSBBeTtcbiAgICBsZXQgZGVsdGFDQnkgPSBDeSAtIEJ5O1xuICAgIGxldCBkZWx0YURDeSA9IER5IC0gQ3k7XG4gICAgbGV0IGF4LCBheSwgYngsIGJ5LCBjeCwgY3k7XG4gICAgbGV0IGxhc3RYID0gLTEwMDAwO1xuICAgIGxldCBsYXN0WSA9IC0xMDAwMDtcbiAgICBsZXQgcHRzID0gW3sgeDogQXgsIHk6IEF5IH1dO1xuICAgIGZvciAobGV0IGkgPSAxOyBpIDwgcHRDb3VudDsgaSsrKSB7XG4gICAgICAgIGxldCB0ID0gaSAvIHB0Q291bnQ7XG4gICAgICAgIGF4ID0gQXggKyBkZWx0YUJBeCAqIHQ7XG4gICAgICAgIGJ4ID0gQnggKyBkZWx0YUNCeCAqIHQ7XG4gICAgICAgIGN4ID0gQ3ggKyBkZWx0YURDeCAqIHQ7XG4gICAgICAgIGF4ICs9IChieCAtIGF4KSAqIHQ7XG4gICAgICAgIGJ4ICs9IChjeCAtIGJ4KSAqIHQ7XG4gICAgICAgIGF5ID0gQXkgKyBkZWx0YUJBeSAqIHQ7XG4gICAgICAgIGJ5ID0gQnkgKyBkZWx0YUNCeSAqIHQ7XG4gICAgICAgIGN5ID0gQ3kgKyBkZWx0YURDeSAqIHQ7XG4gICAgICAgIGF5ICs9IChieSAtIGF5KSAqIHQ7XG4gICAgICAgIGJ5ICs9IChjeSAtIGJ5KSAqIHQ7XG4gICAgICAgIGNvbnN0IHggPSBheCArIChieCAtIGF4KSAqIHQ7XG4gICAgICAgIGNvbnN0IHkgPSBheSArIChieSAtIGF5KSAqIHQ7XG4gICAgICAgIGNvbnN0IGR4ID0geCAtIGxhc3RYO1xuICAgICAgICBjb25zdCBkeSA9IHkgLSBsYXN0WTtcbiAgICAgICAgaWYgKGR4ICogZHggKyBkeSAqIGR5ID4gcHhUb2xlcmFuY2UpIHtcbiAgICAgICAgICAgIHB0cy5wdXNoKHsgeDogeCwgeTogeSB9KTtcbiAgICAgICAgICAgIGxhc3RYID0geDtcbiAgICAgICAgICAgIGxhc3RZID0geTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBwdHMucHVzaCh7IHg6IER4LCB5OiBEeSB9KTtcbiAgICByZXR1cm4gcHRzO1xufVxuZXhwb3J0IGZ1bmN0aW9uIGludGVycG9sYXRlQ3ViaWNCZXppZXIocDAsIGMwLCBjMSwgcDEpIHtcbiAgICAvLyAwIDw9IHQgPD0gMVxuICAgIHJldHVybiBmdW5jdGlvbiBpbnRlcnBvbGF0b3IodCkge1xuICAgICAgICByZXR1cm4gW1xuICAgICAgICAgICAgcG93KDEgLSB0LCAzKSAqIHAwLnggK1xuICAgICAgICAgICAgICAgIDMgKiBwb3coMSAtIHQsIDIpICogdCAqIGMwLnggK1xuICAgICAgICAgICAgICAgIDMgKiAoMSAtIHQpICogcG93KHQsIDIpICogYzEueCArXG4gICAgICAgICAgICAgICAgcG93KHQsIDMpICogcDEueCxcbiAgICAgICAgICAgIHBvdygxIC0gdCwgMykgKiBwMC55ICtcbiAgICAgICAgICAgICAgICAzICogcG93KDEgLSB0LCAyKSAqIHQgKiBjMC55ICtcbiAgICAgICAgICAgICAgICAzICogKDEgLSB0KSAqIHBvdyh0LCAyKSAqIGMxLnkgK1xuICAgICAgICAgICAgICAgIHBvdyh0LCAzKSAqIHAxLnksXG4gICAgICAgIF07XG4gICAgfTtcbn1cbmV4cG9ydCBmdW5jdGlvbiBhZGRWZWN0b3JzKGEsIGIpIHtcbiAgICBpZiAoIWIpXG4gICAgICAgIHJldHVybiBhO1xuICAgIHJldHVybiB7IHg6IGEueCArIGIueCwgeTogYS55ICsgYi55IH07XG59XG5leHBvcnQgZnVuY3Rpb24gZ2V0U3ZnUGF0aEZyb21TdHJva2Uoc3Ryb2tlKSB7XG4gICAgaWYgKHN0cm9rZS5sZW5ndGggPT09IDApXG4gICAgICAgIHJldHVybiBcIlwiO1xuICAgIGNvbnN0IGQgPSBbXTtcbiAgICBsZXQgW3AwLCBwMV0gPSBzdHJva2U7XG4gICAgZC5wdXNoKFwiTVwiLCBwMFswXSwgcDBbMV0pO1xuICAgIGZvciAobGV0IGkgPSAxOyBpIDwgc3Ryb2tlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGQucHVzaChcIlFcIiwgcDBbMF0sIHAwWzFdLCAocDBbMF0gKyBwMVswXSkgLyAyLCAocDBbMV0gKyBwMVsxXSkgLyAyKTtcbiAgICAgICAgcDAgPSBwMTtcbiAgICAgICAgcDEgPSBzdHJva2VbaV07XG4gICAgfVxuICAgIGQucHVzaChcIlpcIik7XG4gICAgcmV0dXJuIGQuam9pbihcIiBcIik7XG59XG4vLyBleHBvcnQgZnVuY3Rpb24gZ2V0RmxhdFN2Z1BhdGhGcm9tU3Ryb2tlKHN0cm9rZTogbnVtYmVyW11bXSkge1xuLy8gICB0cnkge1xuLy8gICAgIGNvbnN0IHBvbHkgPSBwb2x5Z29uQ2xpcHBpbmcudW5pb24oW3N0cm9rZV0gYXMgYW55KVxuLy8gICAgIGNvbnN0IGQgPSBbXVxuLy8gICAgIGZvciAobGV0IGZhY2Ugb2YgcG9seSkge1xuLy8gICAgICAgZm9yIChsZXQgcG9pbnRzIG9mIGZhY2UpIHtcbi8vICAgICAgICAgcG9pbnRzLnB1c2gocG9pbnRzWzBdKVxuLy8gICAgICAgICBkLnB1c2goZ2V0U3ZnUGF0aEZyb21TdHJva2UocG9pbnRzKSlcbi8vICAgICAgIH1cbi8vICAgICB9XG4vLyAgICAgZC5wdXNoKFwiWlwiKVxuLy8gICAgIHJldHVybiBkLmpvaW4oXCIgXCIpXG4vLyAgIH0gY2F0Y2ggKGUpIHtcbi8vICAgICBjb25zb2xlLmVycm9yKFwiQ291bGQgbm90IGNsaXAgcGF0aC5cIilcbi8vICAgICByZXR1cm4gZ2V0U3ZnUGF0aEZyb21TdHJva2Uoc3Ryb2tlKVxuLy8gICB9XG4vLyB9XG4iXSwic291cmNlUm9vdCI6IiJ9 --------------------------------------------------------------------------------