[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nAn SVG snippet that is not sketched correctly or creates an unexpected result.\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nsample-application/dist\nsvg2roughjs-*.tgz\n.idea\ndist\ndebug.log\nout-tsc\ncoverage\n/nodejs-cli/*.svg"
  },
  {
    "path": ".prettierignore",
    "content": "bundled/*\ndist/*\nout-tsc/*"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"tabWidth\": 2,\n  \"useTabs\": false,\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"printWidth\": 100,\n  \"endOfLine\": \"auto\",\n  \"arrowParens\": \"avoid\",\n  \"trailingComma\": \"none\"\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"type\": \"chrome\",\n            \"request\": \"launch\",\n            \"name\": \"Launch Chrome against localhost\",\n            \"url\": \"http://localhost:8080\",\n            \"webRoot\": \"${workspaceFolder}\"\n        }\n    ]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"eslint.validate\": [\"typescript\"]\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nfskopf@gmail.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][mozilla coc].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][faq]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[mozilla coc]: https://github.com/mozilla/diversity\n[faq]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright 2022 Fabian Schwarzkopf, Johannes Rössel\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# svg2rough.js\n\n<p align=\"center\">\n  <img src=\"https://fskpf.github.io/static/assets/svg2roughjs-hero-sketch.svg\" alt=\"hero-image\">\n</p>\n\n<p align=\"center\">\n    <a href=\"https://github.com/fskpf/svg2roughjs\"><img src=\"https://img.shields.io/github/stars/fskpf/svg2roughjs?style=for-the-badge&logo=github&color=%23eac54f\" alt=\"npm\"></a>\n    <a href=\"https://github.com/fskpf/svg2roughjs/blob/master/LICENSE.md\"><img src=\"https://img.shields.io/github/license/fskpf/svg2roughjs?style=for-the-badge&logo=github\" alt=\"github\"></a>\n    <a href=\"https://www.npmjs.com/package/svg2roughjs\" target=\"_blank\"><img src=\"https://img.shields.io/npm/dt/svg2roughjs?style=for-the-badge&logo=npm\" alt=\"npm\"></a>\n    <a href=\"https://www.npmjs.com/package/svg2roughjs\" target=\"_blank\"><img src=\"https://img.shields.io/npm/v/svg2roughjs?style=for-the-badge&logo=npm\" alt=\"npm\"></a>\n</p>\n\nUtilizes [Rough.js](https://github.com/pshihn/rough) to convert an SVG to a hand-drawn visualization.\n\nTry the sample application [here](https://fskpf.github.io/).\n\n## NPM\n\nInstall from the NPM registry with\n\n```shell\nnpm install --save svg2roughjs\n```\n\n## Usage\n\nJust import `Svg2Roughjs` and instantiate it with an output div in which the\nSVG sketch should be created. Calling `sketch()` outputs the current `svg` to the given element\nas hand-drawn sketch.\n\nFor reference, a [sample application](https://fskpf.github.io/) is provided in\n[`/sample-application/`](https://github.com/fskpf/svg2roughjs/tree/master/sample-application).\n\n### ES Module\n\n```javascript\nimport { Svg2Roughjs } from 'svg2roughjs'\n\nconst svg2roughjs = new Svg2Roughjs('#output-div')\nsvg2roughjs.svg = document.getElementById('input-svg')\nsvg2roughjs.sketch()\n```\n\n### UMD Bundle\n\nAn UMD bundle that ca be loaded via script tags or a module loader e.g.\n[RequireJS](https://requirejs.org/) is included in the NPM package or can\nbe loaded from [unpkg](https://unpkg.com/):\n\n```\nhttps://unpkg.com/svg2roughjs/dist/svg2roughjs.umd.min.js\n```\n\n```javascript\n<!-- script loading -->\n<script src=\"https://unpkg.com/svg2roughjs/dist/svg2roughjs.umd.min.js\"></script>\n<script>\n  const svgConverter = new svg2roughjs.Svg2Roughjs('#output-div')\n  svgConverter.svg = document.getElementById('input-svg')\n  svgConverter.sketch()\n</script>\n```\n\n```javascript\n<!-- requirejs -->\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js\"></script>\n<script>\n  window.requirejs(['https://unpkg.com/svg2roughjs/dist/svg2roughjs.umd.min.js'], svg2roughjs => {\n    const svgConverter = new svg2roughjs.Svg2Roughjs('#output-div')\n    svgConverter.svg = document.getElementById('input-svg')\n    svgConverter.sketch()\n  });\n</script>\n```\n\n## API\n\n### Module Exports\n\n- `Svg2Roughjs`: The main class for the conversion.\n- `OutputType`: An enum that is used to switch between `SVG` and `CANVAS` output when targetting an `HTMLDivElement` as output container.\n\n### Methods\n\n- `constructor(target, outputType?, roughConfig?)`\n\n  Creates a new Svg2Rough.js instance.\n\n  `target` may either be a container `HTMLDivElement` (or a selector for the container) into which a new sketch should be created, or directly an `SVGSVGElement` or `HTMLCanvasElement` that should be used for the output.\n\n  The optional `outputType` defaults to the respective mode if `target` is either `SVGSVGElement` or `HTMLCanvasElement`. If targetting an HTML container element, then `OutputType.SVG` is used by default.\n\n- `sketch(sourceSvgChanged = false): Promise<SVGSVGElement | HTMLCanvasElement | null>`\n\n  Clears the configured `target` element and converts the current `svg2roughj.svg` to a hand-drawn sketch.\n\n  Setting `sourceSvgChanged` to `true` re-evaluates the given `svg2roughj.svg` as it was set anew. May be used to re-use the same Svg2Rough.js instance and the same SVG element as source container.\n\n### Properties\n\n| Property          | Description                                                                                                                       | Default                    |\n| ----------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |\n| `svg`             | The given `SVGSVGElement` that should be converted.                                                                               | `undefined`                |\n| `outputType`      | Switch between canvas or SVG output.                                                                                              | `OutputType.SVG`           |\n| `roughConfig`     | [Rough.js options](https://github.com/rough-stuff/rough/wiki#options) object, e.g. to change the fill-style, roughness or bowing. | `{}`                       |\n| `fontFamily`      | Font with which text elements should be drawn.<br>If set to `null`, the text element's original font-family is used.              | `'Comic Sans MS, cursive'` |\n| `backgroundColor` | Sets a background color onto which the sketch is drawn.                                                                           | `transparent`              |\n| `randomize`       | Randomize Rough.js' fillWeight, hachureAngle and hachureGap.                                                                      | `true`                     |\n| `seed`            | Optional, numerical seed for the randomness when creating the sketch.                                                             | `null`                     |\n| `sketchPatterns`  | Whether to sketch pattern fills/strokes or just copy them to the output                                                           | `true`                     |\n| `pencilFilter`    | Applies a pencil effect on the SVG rendering.                                                                                     | `false`                    |\n\n## Sample Images\n\nSome sample images with different Svg2rough.js settings. Try it yourself [here](https://fskpf.github.io/).\n\n| SVG                                                                                                                                                         | Sketch                                                                                                         |\n| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| <img src=\"https://fskpf.github.io/static/sample-images/bpmn1.svg\" width=\"400px\"><br>(created with [yEd Live](https://www.yworks.com/yed-live))              | <img src=\"https://fskpf.github.io/static/sample-images/bpmn1_sketch.svg\" width=\"400px\"><br>&nbsp;              |\n| &nbsp;                                                                                                                                                      | &nbsp;                                                                                                         |\n| <img src=\"https://fskpf.github.io/static/sample-images/hierarchic_diagram.svg\" width=\"400px\"><br>(created with [yEd Live](https://www.yworks.com/yed-live)) | <img src=\"https://fskpf.github.io/static/sample-images/hierarchic_diagram_sketch.svg\" width=\"400px\"><br>&nbsp; |\n| &nbsp;                                                                                                                                                      | &nbsp;                                                                                                         |\n| <img src=\"https://fskpf.github.io/static/sample-images/flowchart.svg\" width=\"400px\"><br>(created with [yEd Live](https://www.yworks.com/yed-live))          | <img src=\"https://fskpf.github.io/static/sample-images/flowchart_sketch.svg\" width=\"400px\"><br>&nbsp;          |\n| &nbsp;                                                                                                                                                      | &nbsp;                                                                                                         |\n| <img src=\"https://fskpf.github.io/static/sample-images/chirality.svg\" width=\"400px\">                                                                        | <img src=\"https://fskpf.github.io/static/sample-images/chirality_sketch.svg\" width=\"400px\">                    |\n| &nbsp;                                                                                                                                                      | &nbsp;                                                                                                         |\n| <img src=\"https://fskpf.github.io/static/sample-images/comic_boy.svg\" width=\"400px\">                                                                        | <img src=\"https://fskpf.github.io/static/sample-images/comic_boy_sketch.svg\" width=\"400px\">                    |\n| &nbsp;                                                                                                                                                      | &nbsp;                                                                                                         |\n| <img src=\"https://fskpf.github.io/static/sample-images/mars_rover_blueprint.svg\" width=\"400px\">                                                             | <img src=\"https://fskpf.github.io/static/sample-images/mars_rover_sketch.svg\" width=\"400px\">                   |\n\n## Local Build\n\nTo build the project locally, make sure to have [Node.js](https://nodejs.org/en) installed and run\n\n```\n> npm install\n> npm run build\n```\n\nThis creates a local `/dist/` folder containing both, the ES module and UMD build of `svg2roughjs`.\n\n### Tests\n\nTo perform all tests on the current build, run\n\n```\nnpm run test-all\n```\n\nThis converts all given tests in [`/test/`](https://github.com/fskpf/svg2roughjs/tree/master/test) and\ncompares the output SVG with the expected string. Each test contains a configuration file with different\nsettings and a fixed seed to account for the randomness in the sketched output.\n\n## Credits\n\n- [Rough.js](https://github.com/pshihn/rough) – Draws the hand-drawn elements\n- [svg-pathdata](https://github.com/nfroidure/svg-pathdata) – Parses SVGPathElements\n- [TinyColor](https://github.com/bgrins/TinyColor) – Color manipulation\n\n## License\n\n[MIT License](https://github.com/fskpf/svg2roughjs/blob/master/LICENSE.md) © Fabian Schwarzkopf and Johannes Rössel\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import js from '@eslint/js'\nimport tseslint from 'typescript-eslint'\nimport prettier from 'eslint-plugin-prettier/recommended'\n\nexport default [\n  js.configs.recommended,\n  ...tseslint.configs.recommended,\n  prettier,\n  {\n    rules: {\n      '@typescript-eslint/no-non-null-assertion': 'off',\n      '@typescript-eslint/no-inferrable-types': 'off',\n      '@typescript-eslint/ban-ts-comment': 'off',\n      '@typescript-eslint/no-var-requires': 'off',\n      // note you must disable the base rule\n      // as it can report incorrect errors\n      'no-unused-vars': 'off',\n      '@typescript-eslint/no-unused-vars': [\n        'error',\n        {\n          argsIgnorePattern: '_',\n          varsIgnorePattern: '_',\n          caughtErrorsIgnorePattern: '_'\n        }\n      ]\n    },\n    ignores: ['test/**'],\n    files: ['**/*.ts']\n  }\n]\n"
  },
  {
    "path": "nodejs-cli/README.md",
    "content": "# CLI\n\nCreate sketchs of an SVG on the command-line with [Node.js](https://nodejs.org/)\nand [Puppeteer](https://pptr.dev/).\n\n## Usage\n\nIn `/nodejs-cli/`\n\n```\n> npm install\n> node src/svg2roughjs ../test/complex/bpmn-diagram/test.svg -o ./test.svg\n```\n\n## CLI Parameters\n\nPass the SVG file that should be converted as first parameter.\n\nAdditionally, the following optional options are available:\n\n| Parameter                        | Description                                | Default                                    |\n| -------------------------------- | ------------------------------------------ | ------------------------------------------ |\n| `-o`<br>`--output`               | Output path for the resulting sketch.      | `'./sketch.svg'`                           |\n| `--roughConfig.ROUGHJS_PROPERTY` | see [svg2roughjs](../README.md#properties) | see [svg2roughjs](../README.md#properties) |\n| `--fontFamily`                   | see [svg2roughjs](../README.md#properties) | see [svg2roughjs](../README.md#properties) |\n| `--backgroundColor`              | see [svg2roughjs](../README.md#properties) | see [svg2roughjs](../README.md#properties) |\n| `--randomize`                    | see [svg2roughjs](../README.md#properties) | see [svg2roughjs](../README.md#properties) |\n| `--seed`                         | see [svg2roughjs](../README.md#properties) | see [svg2roughjs](../README.md#properties) |\n| `--sketchPatterns`               | see [svg2roughjs](../README.md#properties) | see [svg2roughjs](../README.md#properties) |\n| `--pencilFilter`                 | see [svg2roughjs](../README.md#properties) | see [svg2roughjs](../README.md#properties) |\n\nFor example:\n\n```\nnode src/svg2roughjs ../test/complex/bpmn-diagram/test.svg -o ./test.svg --roughConfig.roughness 1.5 --roughConfig.bowing 2 --backgroundColor white --fontFamily null\n```\n\n## Credits\n\n- [Puppeteer](https://pptr.dev/) - Headless browser for Node.js in which the conversion is done\n"
  },
  {
    "path": "nodejs-cli/package.json",
    "content": "{\n  \"name\": \"svg2roughjs-nodejs\",\n  \"version\": \"1.0.0\",\n  \"description\": \"An svg2roughjs CLI that runs in Nodejs with a headless browser\",\n  \"main\": \"svg2roughjs.js\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"test\": \"node src/svg2roughjs ../test/complex/bpmn-diagram/test.svg -o ./test.svg --roughConfig.roughness 1.5 --roughConfig.bowing 2 --backgroundColor white\"\n  },\n  \"author\": \"Fabian Schwarzkopf\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"minimist\": \"^1.2.8\",\n    \"puppeteer\": \"^24.37.5\"\n  }\n}\n"
  },
  {
    "path": "nodejs-cli/src/svg2roughjs-page.js",
    "content": "/**\n * @param {string} inputSvg The SVG that should be converted\n * @param {Record<string,unknown>} svg2roughjsArgs The SVG that should be converted\n * @returns {string} HTML content of the converter page\n */\nexport function svg2roughjsPage(inputSvg, svg2roughjsArgs) {\n  return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>svg2roughjs</title>\n  <script src=\"https://unpkg.com/svg2roughjs/dist/svg2roughjs.umd.min.js\"></script>\n</head>\n<body>\n  <div id=\"input-svg-container\">${inputSvg}</div>\n  <div id=\"output-container\"></div>\n  <script type=\"module\">\n    const { Svg2Roughjs, OutputType } = svg2roughjs\n    ${createSvg2RoughjsInstance(svg2roughjsArgs)}\n    svgConverter.svg = document.querySelector('#input-svg-container > svg')\n    await svgConverter.sketch()\n  </script>\n</body>\n</html>\n`\n}\n\n/**\n * @param {Record<string, unknown>} args\n * @returns {string}\n */\nfunction createSvg2RoughjsInstance(args) {\n  let instance = \"const svgConverter = new Svg2Roughjs('#output-container', OutputType.SVG)\"\n  for (const [key, value] of Object.entries(args)) {\n    if (typeof value === 'string') {\n      instance += `\\nsvgConverter.${key} = '${value}'`\n    } else if (typeof value === 'object') {\n      instance += `\\nsvgConverter.${key} = ${JSON.stringify(value)}`\n    } else {\n      instance += `\\nsvgConverter.${key} = ${value}`\n    }\n  }\n  return instance\n}\n"
  },
  {
    "path": "nodejs-cli/src/svg2roughjs.js",
    "content": "import puppeteer from 'puppeteer'\nimport minimist from 'minimist'\nimport fs from 'fs'\nimport { svg2roughjsPage } from './svg2roughjs-page.js'\n;(async () => {\n  const argv = minimist(process.argv.slice(2), {\n    string: ['backgroundColor', 'fontFamily'],\n    boolean: ['randomize', 'sketchPatterns', 'pencilFilter'],\n    alias: {\n      o: 'output'\n    }\n  })\n\n  const inputSvg = loadInputSvg(argv)\n\n  const browser = await puppeteer.launch({ headless: 'new' })\n  const page = await browser.newPage()\n\n  // load the input SVG as part of the HTML file and run svg2roughjs on the input\n  const svg2roughjsArgs = getSvg2RoughjsArgs(argv)\n  await page.setContent(svg2roughjsPage(inputSvg, svg2roughjsArgs))\n\n  // get the sketch from the DOM\n  const sketch = await page.$eval('#output-container > svg', el => el.outerHTML)\n\n  const outputFilePath = argv.output\n  saveSketch(sketch, outputFilePath)\n\n  // for debugging, disable close and use headless: false\n  await browser.close()\n})()\n\n/**\n * Loads the input SVG from the CLI.\n * @param {object} argv The CLI arguments\n * @returns {string} The content of the input file\n */\nfunction loadInputSvg(argv) {\n  const inputFile = argv._[0]\n  if (!inputFile) {\n    throw new Error('No input file provided. Please pass the input SVG as first parameter.')\n  }\n\n  if (!fs.existsSync(inputFile)) {\n    throw new Error(`File \"${inputFile}\" does not exist.`)\n  }\n\n  const content = fs.readFileSync(inputFile, 'utf8')\n\n  // TODO validate as SVG?\n\n  return content\n}\n\n/**\n * Downloads the sketch as file.\n * @param {string} content The SVG string of the sketch\n * @param {string?} outputFilePath The file path to save the output to.\n */\nfunction saveSketch(content, outputFilePath) {\n  if (!content) {\n    throw new Error('Could not save file, no sketch given.')\n  }\n  // TODO validate SVG?\n\n  fs.writeFileSync(outputFilePath ?? './sketch.svg', content)\n}\n\nfunction getSvg2RoughjsArgs(argv) {\n  const args = { ...argv }\n  // remove arguments for the CLI\n  delete args._\n  delete args.o\n  delete args.output\n\n  return Object.fromEntries(\n    Object.entries(args).map(([key, value]) => {\n      // replace null arguments with the actual null value\n      if (value === 'null') {\n        return [key, null]\n      }\n      return [key, value]\n    })\n  )\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"svg2roughjs\",\n  \"version\": \"3.2.3\",\n  \"description\": \"Leverages Rough.js to convert SVGs to a hand-drawn, sketchy representation\",\n  \"author\": \"Fabian Schwarzkopf\",\n  \"contributors\": [\n    \"Johannes Rössel\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"dist/svg2roughjs.umd.min.js\",\n  \"browser\": \"dist/svg2roughjs.es.min.js\",\n  \"module\": \"dist/svg2roughjs.es.min.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"homepage\": \"https://github.com/fskpf/svg2roughjs#readme\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/fskpf/svg2roughjs.git\"\n  },\n  \"keywords\": [\n    \"svg\",\n    \"roughjs\",\n    \"javascript\",\n    \"hand-drawn\",\n    \"sketch\"\n  ],\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"prepare\": \"npm run lint && npm run build\",\n    \"build\": \"npm run clean && tsc && rollup -c rollup.config.js\",\n    \"clean\": \"rimraf -g *.tgz && rimraf dist/ && rimraf out-tsc/\",\n    \"lint\": \"eslint ./src\",\n    \"tsc\": \"tsc --noEmit\",\n    \"test-all\": \"npm run clean && tsc && wtr\"\n  },\n  \"files\": [\n    \"dist/*\",\n    \"LICENSE.md\",\n    \"README.md\"\n  ],\n  \"dependencies\": {\n    \"roughjs\": \"^4.6.6\",\n    \"svg-pathdata\": \"^8.0.0\",\n    \"tinycolor2\": \"^1.6.0\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^10.0.1\",\n    \"@open-wc/testing\": \"4.0.0\",\n    \"@rollup/plugin-commonjs\": \"^29.0.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.3\",\n    \"@rollup/plugin-terser\": \"^1.0.0\",\n    \"@types/node\": \"^25.3.2\",\n    \"@types/tinycolor2\": \"^1.4.6\",\n    \"@web/dev-server-rollup\": \"0.6.4\",\n    \"@web/test-runner\": \"0.18.3\",\n    \"eslint\": \"^10.0.2\",\n    \"eslint-config-prettier\": \"^10.1.8\",\n    \"eslint-plugin-prettier\": \"^5.5.5\",\n    \"prettier\": \"3.8.1\",\n    \"rimraf\": \"^6.1.3\",\n    \"rollup\": \"^4.59.0\",\n    \"rollup-plugin-dts\": \"^6.3.0\",\n    \"typescript\": \"^5.9.3\",\n    \"typescript-eslint\": \"^8.56.1\"\n  }\n}\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import commonjs from '@rollup/plugin-commonjs'\nimport resolve from '@rollup/plugin-node-resolve'\nimport dts from 'rollup-plugin-dts'\nimport terser from '@rollup/plugin-terser'\nimport fs from 'fs'\n\nconst pkg = JSON.parse(fs.readFileSync('./package.json'))\n\nfunction matchSubmodules(externals) {\n  return externals.map(e => new RegExp(`^${e}(?:[/\\\\\\\\]|$)`))\n}\n\nconst externalsUmd = matchSubmodules([\n  ...Object.keys(pkg.peerDependencies || {}),\n  ...Object.keys(pkg.optionalDependencies || {})\n])\nconst externals = matchSubmodules([...Object.keys(pkg.dependencies || {}), ...externalsUmd])\n\nconst es = {\n  input: 'out-tsc/index.js',\n  output: [\n    {\n      file: pkg.module.replace('.min', ''),\n      format: 'es',\n      name: 'svg2roughjs',\n      sourcemap: true,\n      plugins: []\n    },\n    {\n      file: pkg.module,\n      format: 'es',\n      name: 'svg2roughjs',\n      sourcemap: true,\n      plugins: [terser({})]\n    }\n  ],\n  external: externals,\n  plugins: []\n}\n\nconst umd = {\n  input: 'out-tsc/index.js',\n  output: [\n    {\n      file: pkg.browser.replace('.es.min.', '.umd.'),\n      format: 'umd',\n      name: 'svg2roughjs',\n      sourcemap: true,\n      plugins: []\n    },\n    {\n      file: pkg.browser.replace('.es.', '.umd.'),\n      format: 'umd',\n      name: 'svg2roughjs',\n      sourcemap: true,\n      plugins: [terser({})]\n    }\n  ],\n  external: externalsUmd,\n  plugins: [commonjs(), resolve()]\n}\n\nconst typings = {\n  input: 'out-tsc/index.d.ts',\n  output: [{ file: 'dist/index.d.ts', format: 'es' }],\n  plugins: [dts()]\n}\n\nexport default [es, umd, typings]\n"
  },
  {
    "path": "sample-application/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"description\"\n    content=\"Convert your SVG file to a hand drawn sketch with Svg2Rough.js, a small open-source library.\">\n  <meta name=\"keywords\" content=\"svg, conversion, hand drawn, sketch, open source\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n  <title>Svg2Rough.js - Convert Your SVG to a Hand Drawn Image</title>\n</head>\n\n<body>\n\n  <div class=\"header\">\n    <img class=\"header-img\" src=\"./static/assets/svg-logo.svg\" alt=\"W3C SVG Logo\">\n    <div class=\"title\">\n      <h1><a href=\"https://github.com/fskpf/svg2roughjs\" target=\"_blank\">Svg2Rough.js</a></h1>\n      <a class=\"subtitle\" href=\"https://github.com/fskpf/svg2roughjs\" target=\"_blank\">on GitHub</a>\n    </div>\n    <img class=\"header-img\" src=\"./static/assets/svg-logo-rough.png\" alt=\"Sketchy W3C SVG Logo\">\n  </div>\n\n  <div class=\"main\">\n    <div class=\"sidebar\">\n      <h2>Drawing Options</h2>\n\n      <div class=\"separator\" style=\"margin-top: 5px;\"></div>\n\n      <label for=\"fill-style\">Fill Style</label>\n      <select id=\"fill-style\" style=\"margin: 2px 0;\">\n        <option value=\"hachure\">hachure</option>\n        <option value=\"solid\">solid</option>\n        <option value=\"zigzag\">zigzag</option>\n        <option value=\"cross-hatch\">cross-hatch</option>\n        <option value=\"dots\">dots (excruciatingly slow)</option>\n        <option value=\"dashed\">dashed (somewhat slow)</option>\n        <option value=\"zigzag-line\">zigzag-line (slow)</option>\n      </select>\n      <div class=\"separator\"></div>\n\n      <label for=\"output-format\">Output Format</label>\n      <select id=\"output-format\" style=\"margin: 2px 0;\">\n        <option value=\"svg\">SVG</option>\n        <option value=\"canvas\">Canvas</option>\n      </select>\n      <div class=\"separator\"></div>\n\n      <label for=\"roughness-input\">Roughness</label>\n      <input id=\"roughness-input\" type=\"range\" value=\"1\" min=\"0\" max=\"4\" step=\"0.5\">\n      <div class=\"separator\"></div>\n      <label for=\"bowing-input\">Bowing</label>\n      <input id=\"bowing-input\" type=\"range\" value=\"1\" min=\"0\" max=\"15\" step=\"1\">\n      <div class=\"separator\"></div>\n      <div class=\"checkbox\">\n        <input id=\"original-font\" type=\"checkbox\">\n        <label for=\"original-font\">Use original font</label>\n      </div>\n      <div class=\"separator\"></div>\n      <div class=\"checkbox\">\n        <input id=\"randomize\" type=\"checkbox\" checked>\n        <label for=\"randomize\">Randomize hatching per shape</label>\n      </div>\n      <div class=\"separator\"></div>\n      <div class=\"checkbox\">\n        <input id=\"sketchPatterns\" type=\"checkbox\" checked>\n        <label for=\"sketchPatterns\" title=\"Whether patterns should be sketched or just copied\">\n          Sketch patterns\n        </label>\n      </div>\n      <div class=\"separator\"></div>\n      <div class=\"checkbox\">\n        <input id=\"pencilFilter\" type=\"checkbox\">\n        <label for=\"pencilFilter\">Apply pencil texture</label>\n      </div>\n    </div>\n\n    <div id=\"preview\">\n\n      <div class=\"toolbar\">\n\n        <input type=\"checkbox\" id=\"source-toggle\" class=\"toggle-btn\">\n        <label for=\"source-toggle\" class=\"icon icon-raw\" title=\"Toggle code editor\"></label>\n\n        <input type=\"file\" id=\"file-chooser\" accept=\"image/svg+xml\">\n        <select id=\"sample-select\">\n          <option value=\"bpmn1\">bpmn1.svg</option>\n          <option value=\"computer-network\">computer-network.svg</option>\n          <option value=\"flowchart4\">flowchart4.svg</option>\n          <option value=\"hierarchical1\">hierarchical1.svg</option>\n          <option value=\"hierarchical2\">hierarchical2.svg</option>\n          <option value=\"mindmap\">mindmap.svg</option>\n          <option value=\"movies\">movies.svg</option>\n          <option value=\"organic1\">organic1.svg</option>\n          <option value=\"organic2\">organic2.svg</option>\n          <option value=\"tree1\">tree1.svg</option>\n          <option value=\"venn\">venn.svg</option>\n        </select>\n        <label for=\"opacity\">Compare: </label>\n        <input id=\"opacity\" type=\"range\" min=\"0\" max=\"1\" step=\"0.05\" value=\"0\">\n        <button id=\"download-btn\">Download Sketch</button>\n        <div id=\"local-testing\">\n          <button id=\"prev-testcase\" title=\"Previous testcase\" class=\"icon-button\"><span\n              class=\"icon icon-chevron-left\"></span></button>\n          <select id=\"select-testcase\">\n            <option value=\"\"></option>\n          </select>\n          <button id=\"next-testcase\" title=\"Previous testcase\" class=\"icon-button\"><span\n              class=\"icon icon-chevron-right\"></span></button>\n          <button id=\"download-testcase\" title=\"Create a testcase for the repository\" class=\"icon-button\"><span\n              class=\"icon icon-flask\"></span></button>\n        </div>\n      </div>\n\n      <div class=\"separator\" style=\"margin: 0;\"></div>\n\n      <div class=\"content-container\">\n        <div class=\"raw-svg-container hidden\"></div>\n        <div class=\"image-container\">\n          <div id=\"output\"></div>\n          <div id=\"input\"></div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <script type=\"module\" src=\"/src/index.ts\"></script>\n\n</body>\n\n</html>"
  },
  {
    "path": "sample-application/package.json",
    "content": "{\n  \"name\": \"svg2roughjs-sample\",\n  \"description\": \"A simple sample application to test and try svg2roughjs\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"tsc && vite build\",\n    \"preview\": \"vite preview\"\n  },\n  \"author\": \"Fabian Schwarzkopf\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@codemirror/lang-xml\": \"^6.1.0\",\n    \"codemirror\": \"^6.0.2\"\n  },\n  \"devDependencies\": {\n    \"@types/codemirror\": \"^5.60.17\",\n    \"prettier\": \"3.8.1\",\n    \"typescript\": \"^5.9.3\",\n    \"vite\": \"^7.3.1\"\n  }\n}\n"
  },
  {
    "path": "sample-application/src/assets/styles.css",
    "content": "body * {\n  box-sizing: border-box;\n}\n\nbody {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  right: 0;\n  left: 0;\n  margin: 0;\n  font-family: Arial, Helvetica, sans-serif;\n  color: #2a333d;\n}\n\n#preview {\n  display: flex;\n  flex-direction: column;\n  overflow: auto;\n  width: 100%;\n}\n\n#input {\n  position: absolute;\n  top: 0;\n  left: 0;\n  opacity: 0;\n  background-color: white;\n}\n\n.header {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: 20px 0;\n  width: 100%;\n  background-image: linear-gradient(60deg, #29323c 0%, #485563 100%);\n}\n\n.header-img {\n  width: 100px;\n  height: 100px;\n  margin: 0 40px;\n}\n\n.title {\n  text-align: center;\n  font-family: Arial, Helvetica, sans-serif;\n}\n\n.title h1 {\n  margin-bottom: 5px;\n  font-weight: bold;\n  letter-spacing: 5px;\n  font-size: 35px;\n}\n\na:link,\na:visited,\na:hover,\na:active {\n  color: #fff;\n  text-decoration: none;\n}\n\n.sidebar {\n  border-right: 1px solid rgba(0, 0, 0, 0.14);\n  display: flex;\n  flex-direction: column;\n  padding: 0 15px;\n  flex: 0 0 200px;\n}\n\n.sidebar h2 {\n  margin: 10px 0;\n  font-size: 18px;\n  text-align: center;\n  letter-spacing: 1px;\n}\n\n.toolbar {\n  display: flex;\n  justify-items: center;\n  align-items: center;\n  flex-wrap: wrap;\n  padding: 5px 10px;\n}\n\n.toolbar > * {\n  margin: 2px 5px;\n}\n\n.toolbar input.toggle-btn:checked:hover + label {\n  background-color: #b2b2b2;\n}\n\n.toolbar label.toggle-btn:hover,\n.toolbar input.toggle-btn:checked + label {\n  background-color: #dedede;\n}\n\nlabel {\n  user-select: none;\n}\n\n.icon {\n  display: block;\n  width: 32px;\n  height: 32px;\n  background-repeat: no-repeat;\n  background-position: center;\n  border: none;\n  cursor: pointer;\n  background-size: 24px;\n  border-radius: 5px;\n}\n\n.icon-raw {\n  background-image: url('./raw-source.svg');\n}\n\n.icon-flask {\n  background-image: url('./flask.svg');\n}\n\n.icon-chevron-left {\n  background-image: url('./chevron-left.svg');\n}\n\n.icon-chevron-right {\n  background-image: url('./chevron-right.svg');\n}\n\n.toggle-btn {\n  display: none;\n}\n\n.main {\n  position: absolute;\n  top: 140px;\n  right: 0;\n  left: 0;\n  bottom: 0;\n  overflow: hidden;\n  display: flex;\n}\n\n.content-container {\n  overflow: hidden;\n  height: 100%;\n  position: relative;\n  display: flex;\n}\n\n.raw-svg-container {\n  flex: 0 0 40%;\n  overflow: auto;\n  border-right: 1px solid rgba(0, 0, 0, 0.14);\n  transition: flex-basis 0.2s ease-in-out;\n}\n\n.raw-svg-container.hidden {\n  flex-basis: 0;\n}\n\n.raw-svg-container .CodeMirror {\n  height: 100%;\n}\n\n.image-container {\n  flex: 1 1 auto;\n  overflow: auto;\n  position: relative;\n}\n\n.separator {\n  border-top: 1px solid rgba(0, 0, 0, 0.14);\n  margin: 15px 20px;\n}\n\n.checkbox {\n  display: flex;\n  align-items: center;\n}\n\n.checkbox input {\n  margin: 8px;\n}\n\n.checkbox * {\n  cursor: pointer;\n}\n\nlabel[for='opacity'] {\n  cursor: pointer;\n}\n\n.icon-button {\n  width: 24px;\n  height: 24px;\n  padding: 3px;\n  margin: 0 6px;\n}\n\n.icon-button .icon {\n  width: 100%;\n  height: 100%;\n  background-size: contain;\n}\n\n#local-testing {\n  margin: 0 0 0 auto;\n  display: flex;\n  align-items: center;\n}\n"
  },
  {
    "path": "sample-application/src/index.ts",
    "content": "import './assets/styles.css'\nimport { EditorView } from 'codemirror'\nimport { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'\nimport { lineNumbers } from '@codemirror/view'\nimport { xml } from '@codemirror/lang-xml'\n\nimport SAMPLE_BPMN from './samples/bpmn1.svg?raw'\nimport SAMPLE_COMPUTER_NETWORK from './samples/computer-network.svg?raw'\nimport SAMPLE_FLOWCHART from './samples/flowchart4.svg?raw'\nimport SAMPLE_HIERARCHICAL1 from './samples/hierarchical1.svg?raw'\nimport SAMPLE_HIERARCHICAL2 from './samples/hierarchical2.svg?raw'\nimport SAMPLE_MINDMAP from './samples/mindmap.svg?raw'\nimport SAMPLE_MOVIES from './samples/movies.svg?raw'\nimport SAMPLE_ORGANIC1 from './samples/organic1.svg?raw'\nimport SAMPLE_ORGANIC2 from './samples/organic2.svg?raw'\nimport SAMPLE_TREE from './samples/tree1.svg?raw'\nimport SAMPLE_VENN from './samples/venn.svg?raw'\n\n// debug lib import for better debugging...\nimport { OutputType, Svg2Roughjs } from '../../src/index'\nimport { initializeTestUI } from './testing'\n\nlet svg2roughjs: Svg2Roughjs\nlet loadingSvg = false\nlet scheduledLoad: string | null = null\nlet debouncedTimer: ReturnType<typeof setTimeout> | null = null\nlet codeMirror: EditorView\nlet silentCodeMirrorChange = false\n\n// just for easier access, it's a debug project, so who cares...\nconst patternsCheckbox = document.getElementById('sketchPatterns') as HTMLInputElement\nconst pencilCheckbox = document.getElementById('pencilFilter') as HTMLInputElement\nconst sampleSelect = document.getElementById('sample-select') as HTMLSelectElement\nconst codeContainer = document.querySelector('.raw-svg-container') as HTMLDivElement\nconst fillStyleSelect = document.getElementById('fill-style') as HTMLSelectElement\nconst outputFormatSelect = document.getElementById('output-format') as HTMLSelectElement\nconst roughnessInput = document.getElementById('roughness-input') as HTMLInputElement\nconst bowingInput = document.getElementById('bowing-input') as HTMLInputElement\nconst opacityInput = document.getElementById('opacity') as HTMLInputElement\nconst fileInput = document.getElementById('file-chooser') as HTMLInputElement\nconst originalFontCheckbox = document.getElementById('original-font') as HTMLInputElement\nconst randomizeCheckbox = document.getElementById('randomize') as HTMLInputElement\n\nconst onCodeMirrorChange = (newCode: string) => {\n  if (debouncedTimer) {\n    clearTimeout(debouncedTimer)\n  }\n  debouncedTimer = setTimeout(() => {\n    debouncedTimer = null\n    try {\n      loadSvgString(newCode)\n    } catch (_) {\n      /* do nothing */\n    }\n  }, 500)\n}\n\n/**\n * Sets CodeMirror content without triggering the change listener\n */\nfunction setCodeMirrorValue(value: string) {\n  silentCodeMirrorChange = true\n  codeMirror.dispatch({\n    changes: { from: 0, to: codeMirror.state.doc.length, insert: value }\n  })\n}\n\nfunction getSvgSize(svg: SVGSVGElement): { width: number; height: number } {\n  let width, height\n  const hasViewbox = svg.hasAttribute('viewBox')\n  if (svg.hasAttribute('width')) {\n    // percantage sizes for the root SVG are unclear, thus use viewBox if available\n    if (svg.width.baseVal.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE && hasViewbox) {\n      width = svg.viewBox.baseVal.width\n    } else {\n      width = svg.width.baseVal.value\n    }\n  } else if (hasViewbox) {\n    width = svg.viewBox.baseVal.width\n  } else {\n    width = 300\n  }\n\n  if (svg.hasAttribute('height')) {\n    // percantage sizes for the root SVG are unclear, thus use viewBox if available\n    if (svg.height.baseVal.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE && hasViewbox) {\n      height = svg.viewBox.baseVal.height\n    } else {\n      height = svg.height.baseVal.value\n    }\n  } else if (hasViewbox) {\n    height = svg.viewBox.baseVal.height\n  } else {\n    height = 150\n  }\n\n  return { width, height }\n}\n\nexport function loadSvgString(fileContent: string) {\n  if (loadingSvg) {\n    scheduledLoad = fileContent\n    return\n  }\n  setUIState(false)\n\n  loadingSvg = true\n\n  const inputElement = document.getElementById('input')!\n  const outputElement = document.getElementById('output')!\n  const canvas = outputElement.querySelector('canvas')\n\n  const parser = new DOMParser()\n  const doc = parser.parseFromString(fileContent, 'image/svg+xml')\n  const svg = doc.querySelector('svg')\n\n  while (inputElement.childElementCount > 0) {\n    inputElement.removeChild(inputElement.firstChild!)\n  }\n\n  if (!svg) {\n    console.error('Could not load SVG file')\n    setUIState(true)\n    loadingSvg = false\n    return\n  }\n\n  const svgSize = getSvgSize(svg)\n  if (svgSize) {\n    inputElement.style.width = `${svgSize.width}px`\n    inputElement.style.height = `${svgSize.height}px`\n  }\n  inputElement.appendChild(svg)\n\n  // make sure the SVG is part of the DOM and rendered, before it is converted by\n  // Svg2Rough.js. Otherwise, CSS percentaged width/height might not be applied yet\n  setTimeout(async () => {\n    if (svg.tagName === 'HTML') {\n      console.error('Error parsing XML')\n      inputElement.style.opacity = '1'\n      inputElement.style.width = '100%'\n      inputElement.style.height = '100%'\n      if (canvas) {\n        canvas.style.opacity = '0'\n      }\n    } else {\n      const opacityInput = document.getElementById('opacity') as HTMLInputElement\n      inputElement.style.opacity = opacityInput.value\n      if (canvas) {\n        canvas.style.opacity = '1'\n      }\n      try {\n        svg2roughjs.svg = svg\n        await svg2roughjs.sketch()\n      } catch (e) {\n        console.error(\"Couldn't sketch content\")\n        throw e // re-throw to show error on console\n      } finally {\n        setUIState(true)\n        loadingSvg = false\n      }\n\n      // maybe there was a load during the rendering.. so load this instead\n      if (scheduledLoad) {\n        loadSvgString(scheduledLoad)\n        scheduledLoad = null\n      }\n    }\n  }, 0)\n}\n\nfunction loadSample(sample: string) {\n  let sampleString = ''\n  switch (sample) {\n    case 'bpmn1':\n      sampleString = SAMPLE_BPMN\n      break\n    case 'computer-network':\n      sampleString = SAMPLE_COMPUTER_NETWORK\n      break\n    case 'flowchart4':\n      sampleString = SAMPLE_FLOWCHART\n      break\n    case 'hierarchical1':\n      sampleString = SAMPLE_HIERARCHICAL1\n      break\n    case 'hierarchical2':\n      sampleString = SAMPLE_HIERARCHICAL2\n      break\n    case 'mindmap':\n      sampleString = SAMPLE_MINDMAP\n      break\n    case 'movies':\n      sampleString = SAMPLE_MOVIES\n      break\n    case 'organic1':\n      sampleString = SAMPLE_ORGANIC1\n      break\n    case 'organic2':\n      sampleString = SAMPLE_ORGANIC2\n      break\n    case 'tree1':\n      sampleString = SAMPLE_TREE\n      break\n    case 'venn':\n      sampleString = SAMPLE_VENN\n      break\n  }\n\n  setCodeMirrorValue(sampleString)\n\n  loadSvgString(sampleString)\n}\n\nfunction updateOpacity(inputContainerOpacity: number) {\n  const inputContainer = document.getElementById('input')!\n  const outputContainer = document.getElementById('output')!\n  inputContainer.style.opacity = `${inputContainerOpacity}`\n  outputContainer.style.opacity = `${1 - inputContainerOpacity}`\n}\n\nfunction run() {\n  svg2roughjs = new Svg2Roughjs('#output', OutputType.SVG)\n  svg2roughjs.backgroundColor = 'white'\n  svg2roughjs.pencilFilter = !!pencilCheckbox.checked\n  svg2roughjs.sketchPatterns = !!patternsCheckbox.checked\n  svg2roughjs.roughConfig = {\n    bowing: parseInt(bowingInput.value),\n    roughness: parseInt(roughnessInput.value),\n    fillStyle: fillStyleSelect.value\n  }\n  sampleSelect.addEventListener('change', () => {\n    loadSample(sampleSelect.value)\n  })\n\n  const toggleSourceBtn = document.getElementById('source-toggle') as HTMLInputElement\n  toggleSourceBtn.addEventListener('change', () => {\n    if (toggleSourceBtn.checked) {\n      codeContainer.classList.remove('hidden')\n      setTimeout(() => {\n        codeMirror.requestMeasure()\n        codeMirror.focus()\n      }, 20)\n    } else {\n      codeContainer.classList.add('hidden')\n    }\n  })\n\n  codeMirror = new EditorView({\n    parent: codeContainer,\n    extensions: [\n      lineNumbers(),\n      xml(),\n      syntaxHighlighting(defaultHighlightStyle),\n      EditorView.updateListener.of(e => {\n        if (e.docChanged && !silentCodeMirrorChange) {\n          onCodeMirrorChange(e.state.doc.toString())\n        }\n        silentCodeMirrorChange = false\n      })\n    ]\n  })\n\n  // codeMirrorInstance = CodeMirror(codeContainer, {\n  //   mode: 'xml',\n  //   lineNumbers: true\n  // })\n\n  // make sure codemirror is rendered when the expand animation has finished\n  codeContainer.addEventListener('transitionend', () => {\n    if (toggleSourceBtn.checked) {\n      codeMirror.requestMeasure()\n      codeMirror.focus()\n    }\n  })\n\n  // pre-select a sample\n  sampleSelect.selectedIndex = 0\n  loadSample(sampleSelect.value)\n\n  outputFormatSelect.addEventListener('change', async () => {\n    setUIState(false)\n    svg2roughjs.outputType = outputFormatSelect.value === 'svg' ? OutputType.SVG : OutputType.CANVAS\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n  fillStyleSelect.addEventListener('change', async () => {\n    svg2roughjs.roughConfig = {\n      bowing: parseInt(bowingInput.value),\n      roughness: parseInt(roughnessInput.value),\n      fillStyle: fillStyleSelect.value\n    }\n    setUIState(false)\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n  roughnessInput.addEventListener('change', async () => {\n    svg2roughjs.roughConfig = {\n      bowing: parseInt(bowingInput.value),\n      roughness: parseInt(roughnessInput.value),\n      fillStyle: fillStyleSelect.value\n    }\n    setUIState(false)\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n  bowingInput.addEventListener('change', async () => {\n    svg2roughjs.roughConfig = {\n      bowing: parseInt(bowingInput.value),\n      roughness: parseInt(roughnessInput.value),\n      fillStyle: fillStyleSelect.value\n    }\n    setUIState(false)\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n\n  opacityInput.addEventListener('change', () => {\n    updateOpacity(parseFloat(opacityInput.value))\n  })\n  const opacityLabel = document.querySelector('label[for=opacity]') as HTMLLabelElement\n  opacityLabel.addEventListener('click', () => {\n    const currentOpacity = parseFloat(opacityInput.value)\n    const newOpacity = currentOpacity < 1 ? 1 : 0\n    opacityInput.value = `${newOpacity}`\n    updateOpacity(newOpacity)\n  })\n\n  function loadFile(file: File) {\n    const reader = new FileReader()\n    reader.readAsText(file)\n    reader.addEventListener('load', () => {\n      const fileContent = reader.result as string\n      setCodeMirrorValue(fileContent)\n      loadSvgString(fileContent)\n    })\n  }\n\n  fileInput.addEventListener('change', () => {\n    const files = fileInput.files\n    if (files && files.length > 0) {\n      loadFile(files[0])\n    }\n  })\n\n  const body = document.getElementsByTagName('body')[0]\n  body.addEventListener('dragover', e => {\n    e.preventDefault()\n  })\n  body.addEventListener('drop', e => {\n    e.preventDefault()\n    if (e.dataTransfer && e.dataTransfer.items) {\n      for (let i = 0; i < e.dataTransfer.items.length; i++) {\n        if (e.dataTransfer.items[i].kind === 'file') {\n          const file = e.dataTransfer.items[i].getAsFile()\n          if (file) {\n            loadFile(file)\n          }\n          return\n        }\n      }\n    } else if (e.dataTransfer) {\n      // Use DataTransfer interface to access the file(s)\n      for (let i = 0; i < e.dataTransfer.files.length; i++) {\n        loadFile(e.dataTransfer.files[i])\n        return\n      }\n    }\n  })\n\n  const downloadBtn = document.getElementById('download-btn') as HTMLButtonElement\n  downloadBtn.addEventListener('click', () => {\n    const link = document.createElement('a')\n\n    if (svg2roughjs.outputType === OutputType.CANVAS) {\n      const canvas = document.querySelector('#output canvas') as HTMLCanvasElement\n      const image = canvas.toDataURL('image/png', 1.0).replace('image/png', 'image/octet-stream')\n      link.download = 'svg2roughjs.png'\n      link.href = image\n    } else {\n      const serializer = new XMLSerializer()\n      const svg = document.querySelector('#output svg') as SVGSVGElement\n      let svgString = serializer.serializeToString(svg)\n      svgString = '<?xml version=\"1.0\" standalone=\"no\"?>\\r\\n' + svgString\n      const svgBlob = new Blob([svgString], { type: 'image/svg+xml' })\n      link.download = 'svg2roughjs.svg'\n      link.href = URL.createObjectURL(svgBlob)\n    }\n\n    link.click()\n  })\n\n  initializeTestUI(svg2roughjs)\n\n  originalFontCheckbox.addEventListener('change', async () => {\n    if (originalFontCheckbox.checked) {\n      svg2roughjs.fontFamily = null\n    } else {\n      svg2roughjs.fontFamily = 'Comic Sans MS, cursive'\n    }\n    setUIState(false)\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n\n  randomizeCheckbox.addEventListener('change', async () => {\n    svg2roughjs.randomize = !!randomizeCheckbox.checked\n    setUIState(false)\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n  pencilCheckbox.addEventListener('change', async () => {\n    svg2roughjs.pencilFilter = !!pencilCheckbox.checked\n    setUIState(false)\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n  patternsCheckbox.addEventListener('change', async () => {\n    svg2roughjs.sketchPatterns = !!patternsCheckbox.checked\n    setUIState(false)\n    await svg2roughjs.sketch()\n    setUIState(true)\n  })\n}\n\nfunction setUIState(enabled: boolean) {\n  const elements = [\n    patternsCheckbox,\n    pencilCheckbox,\n    sampleSelect,\n    fillStyleSelect,\n    outputFormatSelect,\n    roughnessInput,\n    bowingInput,\n    opacityInput,\n    fileInput,\n    originalFontCheckbox,\n    randomizeCheckbox\n  ]\n\n  for (const ele of elements) {\n    ele.disabled = !enabled\n  }\n}\n\nrun()\n"
  },
  {
    "path": "sample-application/src/testing.ts",
    "content": "import { OutputType, Svg2Roughjs } from '../../src/index'\nimport { downloadFile } from './utils'\nimport { specTests } from '../../test/tests.js'\nimport { loadSvgString } from './index'\n\nconst localTestsContainer = document.getElementById('local-testing') as HTMLDivElement\nconst downloadTestcaseBtn = document.getElementById('download-testcase') as HTMLButtonElement\nconst testcaseSelect = document.getElementById('select-testcase') as HTMLSelectElement\nconst prevTestcaseBtn = document.getElementById('prev-testcase') as HTMLButtonElement\nconst nextTestcaseBtn = document.getElementById('next-testcase') as HTMLButtonElement\n\nexport function initializeTestUI(svg2roughjs: Svg2Roughjs) {\n  if (location.hostname !== 'localhost') {\n    localTestsContainer.style.display = 'none'\n  }\n\n  for (const testName of specTests) {\n    const option = document.createElement('option')\n    option.value = testName\n    option.text = testName\n    testcaseSelect.appendChild(option)\n  }\n\n  testcaseSelect.addEventListener('change', e =>\n    onTestcaseChange((e.target as HTMLOptionElement).value)\n  )\n  prevTestcaseBtn.addEventListener('click', () => {\n    const idx = testcaseSelect.selectedIndex\n    if (idx > 1) {\n      testcaseSelect.selectedIndex = idx - 1\n    }\n    onTestcaseChange(testcaseSelect.options[testcaseSelect.selectedIndex].value)\n  })\n  nextTestcaseBtn.addEventListener('click', () => {\n    const idx = testcaseSelect.selectedIndex\n    if (idx < testcaseSelect.childElementCount - 1) {\n      testcaseSelect.selectedIndex = idx + 1\n    }\n    onTestcaseChange(testcaseSelect.options[testcaseSelect.selectedIndex].value)\n  })\n  downloadTestcaseBtn.addEventListener('click', () => downloadTestcase(svg2roughjs))\n}\n\nasync function onTestcaseChange(testName: string) {\n  const svgString = loadSvg(`/specs/${testName}/test.svg`)\n  loadSvgString(svgString)\n}\n\nfunction loadSvg(url: string) {\n  const request = new XMLHttpRequest()\n  request.open('GET', url, false)\n  request.overrideMimeType('text/plain; charset=utf-8')\n  request.send()\n  if (request.status !== 200) {\n    throw new Error(`Unable to fetch ${url}, status code: ${request.status}`)\n  }\n  return request.responseText\n}\n\nfunction isExistingTestcase(testcase: string): boolean {\n  return specTests.indexOf(testcase) !== -1\n}\n\n/**\n * Creates a reproducible testcase\n */\nasync function downloadTestcase(svg2roughjs: Svg2Roughjs) {\n  const prevRandomize = svg2roughjs.randomize\n  const prevPencilFilter = svg2roughjs.pencilFilter\n  const prevOutputType = svg2roughjs.outputType\n  const prevSketchPatters = svg2roughjs.sketchPatterns\n  const prevConfig = Object.assign({}, svg2roughjs.roughConfig)\n  svg2roughjs.randomize = false\n  svg2roughjs.pencilFilter = false\n  svg2roughjs.sketchPatterns = true\n  svg2roughjs.outputType = OutputType.SVG\n  svg2roughjs.backgroundColor = 'white'\n  svg2roughjs.roughConfig = {\n    ...svg2roughjs.roughConfig,\n    fixedDecimalPlaceDigits: 3,\n    fillStyle: 'solid', // just use solid for tests, to make the more stable on lib changes\n    seed: 4242\n  }\n  await svg2roughjs.sketch()\n  const serializer = new XMLSerializer()\n\n  const testcaseName = testcaseSelect.options[testcaseSelect.selectedIndex].value\n  if (!isExistingTestcase(testcaseName)) {\n    const test = document.querySelector('#input svg') as SVGSVGElement\n    let inputSvg = serializer.serializeToString(test)\n    inputSvg = '<?xml version=\"1.0\" standalone=\"no\"?>\\r\\n' + inputSvg\n    downloadFile(inputSvg, 'image/svg+xml', 'test.svg')\n  }\n\n  const spec = document.querySelector('#output svg') as SVGSVGElement\n  let sketchedSvg = serializer.serializeToString(spec)\n  sketchedSvg = '<?xml version=\"1.0\" standalone=\"no\"?>\\r\\n' + sketchedSvg\n  downloadFile(sketchedSvg, 'image/svg+xml', 'expect.svg')\n  const config = {\n    roughConfig: svg2roughjs.roughConfig,\n    outputType: svg2roughjs.outputType,\n    pencilFilter: svg2roughjs.pencilFilter,\n    sketchPatterns: svg2roughjs.sketchPatterns,\n    backgroundColor: 'white'\n  }\n  downloadFile(JSON.stringify(config), 'text/json', 'config.json')\n  // reset state to before testcase creation\n  svg2roughjs.randomize = prevRandomize\n  svg2roughjs.pencilFilter = prevPencilFilter\n  svg2roughjs.outputType = prevOutputType\n  svg2roughjs.sketchPatterns = prevSketchPatters\n  svg2roughjs.roughConfig = prevConfig\n  await svg2roughjs.sketch()\n}\n"
  },
  {
    "path": "sample-application/src/types.d.ts",
    "content": "declare module '*.svg' {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const content: any\n  export default content\n}\n"
  },
  {
    "path": "sample-application/src/utils.ts",
    "content": "export function downloadFile(content: string, mime: string, fileName: string) {\n  const link = document.createElement('a')\n  const configBlob = new Blob([content], { type: mime })\n  link.download = fileName\n  link.href = URL.createObjectURL(configBlob)\n  link.click()\n}\n"
  },
  {
    "path": "sample-application/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "sample-application/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitAny\": false\n  },\n  \"include\": [\"../dist/index.d.ts\", \"src/types.d.ts\", \"src/**/*.ts\"]\n}\n"
  },
  {
    "path": "sample-application/vite.config.js",
    "content": "import { defineConfig } from 'vite'\n\nexport default defineConfig({\n  publicDir: '../test/'\n})\n"
  },
  {
    "path": "src/OutputType.ts",
    "content": "export enum OutputType {\n  SVG,\n  CANVAS\n}\n"
  },
  {
    "path": "src/RandomNumberGenerator.ts",
    "content": "import { Random } from 'roughjs/bin/math'\n\n/**\n * A simple random number generator that allows for seeding.\n */\nexport class RandomNumberGenerator {\n  private readonly rng: Random | null\n  constructor(seed: number | null) {\n    // since we already depend on Rough.js, we may just use its seedable RNG implementation\n    this.rng = seed ? new Random(seed) : null\n  }\n\n  /**\n   * Returns a random number in the given range.\n   */\n  next(range?: [number, number]): number {\n    const rnd = this.rng?.next() ?? Math.random()\n    if (range) {\n      const min = range[0]\n      const max = range[1]\n      return rnd * (max - min) + min\n    }\n    return rnd\n  }\n}\n"
  },
  {
    "path": "src/Svg2Roughjs.ts",
    "content": "import { Options } from 'roughjs/bin/core'\nimport rough from 'roughjs/bin/rough'\nimport { OutputType } from './OutputType'\nimport { processRoot } from './processor'\nimport { createPencilFilter } from './styles/textures'\nimport { RenderContext } from './types'\nimport { getDefsElement } from './utils'\nimport { RandomNumberGenerator } from './RandomNumberGenerator'\n\n/**\n * Svg2Roughjs parses an SVG and converts it to a hand-drawn sketch.\n */\nexport class Svg2Roughjs {\n  /**\n   * Optional solid background color with which the canvas should be initialized.\n   * It is drawn on a transparent canvas by default.\n   */\n  backgroundColor: string | null = null\n\n  /**\n   * Set a font-family for the rendering of text elements.\n   * If set to `null`, then the font-family of the SVGTextElement is used.\n   * By default, 'Comic Sans MS, cursive' is used.\n   */\n  fontFamily: string | null = 'Comic Sans MS, cursive'\n\n  /**\n   * Whether to randomize Rough.js' fillWeight, hachureAngle and hachureGap.\n   * Also randomizes the disableMultiStroke option of Rough.js.\n   * The randomness may be seeded with the `seed` property.\n   * By default `true`.\n   */\n  randomize: boolean = true\n\n  /**\n   * Optional seed for the randomness when creating the sketch.\n   * Providing a value implicitly seeds Rough.js which may be overwritten\n   * by provding a different seed with the optional `roughConfig` property.\n   * By default `null`.\n   */\n  seed: number | null = null\n\n  /**\n   * Whether pattern elements should be sketched or just copied to the output.\n   * For smaller pattern base sizes, it's often beneficial to just copy it over\n   * as the sketch will be too smalle to actually look sketched at all.\n   */\n  sketchPatterns: boolean = true\n\n  /**\n   * Whether to apply a pencil filter.\n   */\n  pencilFilter: boolean = false\n\n  private $svg?: SVGSVGElement\n  private width: number = 0\n  private height: number = 0\n  private $outputType: OutputType\n  private $roughConfig: Options = {}\n  private idElements: Record<string, SVGElement | string> = {}\n\n  private outputElement: Element\n  private lastResult: SVGSVGElement | HTMLCanvasElement | null = null\n\n  /**\n   * Set the SVG that should be sketched.\n   */\n  set svg(svg: SVGSVGElement) {\n    if (this.$svg !== svg) {\n      this.$svg = svg\n      this.sourceSvgChanged()\n    }\n  }\n\n  /**\n   * Returns the SVG that should be sketched.\n   */\n  get svg(): SVGSVGElement | undefined {\n    return this.$svg\n  }\n\n  /**\n   * Sets the output format of the sketch.\n   *\n   * Applies only to instances that have been created with a\n   * container as output element instead of an actual SVG or canvas\n   * element.\n   *\n   * Throws when the given mode does not match the output element\n   * with which this instance was created.\n   */\n  set outputType(type: OutputType) {\n    if (this.$outputType === type) {\n      return\n    }\n\n    const incompatible =\n      (type === OutputType.CANVAS && this.outputElement instanceof SVGSVGElement) ||\n      (type === OutputType.SVG && this.outputElement instanceof HTMLCanvasElement)\n    if (incompatible) {\n      throw new Error(\n        `Output format ${type} incompatible with given output element ${this.outputElement.tagName}`\n      )\n    }\n\n    this.$outputType = type\n  }\n\n  /**\n   * Returns the currently configured output type.\n   */\n  get outputType(): OutputType {\n    return this.$outputType\n  }\n\n  /**\n   * Sets the config object that is passed to Rough.js and considered\n   * during rendering of the `SVGElement`s.\n   *\n   * Sets `fixedDecimalPlaceDigits` to `3` if not specified otherwise.\n   */\n  set roughConfig(config: Options) {\n    if (typeof config.fixedDecimalPlaceDigits === 'undefined') {\n      config.fixedDecimalPlaceDigits = 3\n    }\n    this.$roughConfig = config\n  }\n\n  /**\n   * Returns the currently configured rendering configuration.\n   */\n  get roughConfig(): Options {\n    return this.$roughConfig\n  }\n\n  /**\n   * Creates a new instance of Svg2roughjs.\n   * @param target Either a container `HTMLDivElement` (or a selector for the container) to which a sketch should be added\n   * or an `HTMLCanvasElement` or `SVGSVGElement` that should be used as output target.\n   * @param outputType Whether the output should be an SVG or drawn to an HTML canvas.\n   * Defaults to SVG or CANVAS depending if the given target is of type `HTMLCanvasElement` or `SVGSVGElement`,\n   * otherwise it defaults to SVG.\n   * @param roughConfig Config object that is passed to Rough.js and considered during\n   * rendering of the `SVGElement`s.\n   */\n  constructor(\n    target: string | HTMLDivElement | HTMLCanvasElement | SVGSVGElement,\n    outputType: OutputType = OutputType.SVG,\n    roughConfig: Options = {}\n  ) {\n    if (!target) {\n      throw new Error('No target provided')\n    }\n\n    const targetElement = typeof target === 'string' ? document.querySelector(target) : target\n    if (!targetElement) {\n      throw new Error('Could not find target in document')\n    }\n\n    this.roughConfig = roughConfig\n\n    this.outputElement = targetElement\n    if (targetElement instanceof HTMLCanvasElement) {\n      this.$outputType = OutputType.CANVAS\n    } else if (targetElement instanceof SVGSVGElement) {\n      this.$outputType = OutputType.SVG\n    } else {\n      this.$outputType = outputType\n    }\n  }\n\n  /**\n   * Triggers an entire redraw of the SVG which\n   * processes the input element anew.\n   * @param sourceSvgChanged When `true`, the given {@link svg} is re-evaluated as if it was set anew.\n   *  This allows the Svg2Rough.js instance to be used mutliple times with the same source SVG container but different contents.\n   * @returns A promise that resolves with the sketched output element or null if no {@link svg} is set.\n   */\n  sketch(sourceSvgChanged = false): Promise<SVGSVGElement | HTMLCanvasElement | null> {\n    if (!this.svg) {\n      return Promise.resolve(null)\n    }\n\n    if (sourceSvgChanged) {\n      this.sourceSvgChanged()\n    }\n\n    const sketchContainer = this.prepareRenderContainer()\n    const renderContext = this.createRenderContext(sketchContainer)\n\n    // prepare filter effects\n    if (this.pencilFilter) {\n      const defs = getDefsElement(renderContext)\n      defs.appendChild(createPencilFilter())\n    }\n\n    // sketchify the SVG\n    renderContext.processElement(renderContext, this.svg, null, this.width, this.height)\n\n    if (this.outputElement instanceof SVGSVGElement) {\n      // sketch already in the outputElement\n      return Promise.resolve(this.outputElement)\n    } else if (this.outputElement instanceof HTMLCanvasElement) {\n      return this.drawToCanvas(renderContext, this.outputElement)\n    }\n\n    // remove the previous attached result\n    this.lastResult?.parentNode?.removeChild(this.lastResult)\n    // assume that the given output element is a container, thus append the sketch to it\n    if (this.outputType === OutputType.SVG) {\n      const svgSketch = renderContext.svgSketch\n      this.outputElement.appendChild(svgSketch)\n      this.lastResult = svgSketch\n      return Promise.resolve(svgSketch)\n    } else {\n      // canvas output type\n      const canvas = document.createElement('canvas')\n      this.outputElement.appendChild(canvas)\n      this.lastResult = canvas\n      return this.drawToCanvas(renderContext, canvas)\n    }\n  }\n\n  /**\n   * Creates a new context which contains the current state of the\n   * Svg2Roughs instance for rendering.\n   */\n  private createRenderContext(sketchContainer: SVGSVGElement): RenderContext {\n    if (!this.svg) {\n      throw new Error('No source SVG set yet.')\n    }\n    let roughConfig = this.roughConfig\n    if (this.seed !== null) {\n      roughConfig = { seed: this.seed, ...roughConfig }\n    }\n    return {\n      rc: rough.svg(sketchContainer, { options: roughConfig }),\n      roughConfig: this.roughConfig,\n      fontFamily: this.fontFamily,\n      pencilFilter: this.pencilFilter,\n      randomize: this.randomize,\n      rng: new RandomNumberGenerator(this.seed),\n      sketchPatterns: this.sketchPatterns,\n      idElements: this.idElements,\n      sourceSvg: this.svg,\n      svgSketch: sketchContainer,\n      svgSketchIsInDOM: document.body.contains(sketchContainer),\n      styleSheets: Array.from(this.svg.querySelectorAll('style'))\n        .map(s => s.sheet)\n        .filter(s => s !== null) as CSSStyleSheet[],\n      processElement: processRoot\n    }\n  }\n\n  /**\n   * Helper method to draw the sketched SVG to a HTMLCanvasElement.\n   */\n  private drawToCanvas(\n    renderContext: RenderContext,\n    canvas: HTMLCanvasElement\n  ): Promise<HTMLCanvasElement> {\n    canvas.width = this.width\n    canvas.height = this.height\n    const canvasCtx = canvas.getContext('2d') as CanvasRenderingContext2D\n    canvasCtx.clearRect(0, 0, this.width, this.height)\n    return new Promise(resolve => {\n      const svgString = new XMLSerializer().serializeToString(renderContext.svgSketch)\n      const img = new Image()\n      img.onload = function () {\n        canvasCtx.drawImage(this as HTMLImageElement, 0, 0)\n        resolve(canvas)\n      }\n      img.src = `data:image/svg+xml;charset=utf8,${encodeURIComponent(svgString)}`\n    })\n  }\n\n  /**\n   * Prepares the given SVG element depending on the set properties.\n   */\n  private prepareRenderContainer(): SVGSVGElement {\n    let svgElement: SVGSVGElement\n\n    if (this.outputElement instanceof SVGSVGElement) {\n      // just use the user given outputElement directly as sketch-container\n      svgElement = this.outputElement\n    } else {\n      // we need a separate svgElement as output element\n      svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n    }\n\n    // make sure it has all the proper namespaces\n    svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg')\n    svgElement.setAttributeNS(\n      'http://www.w3.org/2000/xmlns/',\n      'xmlns:xlink',\n      'http://www.w3.org/1999/xlink'\n    )\n\n    // clear SVG element\n    while (svgElement.firstChild) {\n      svgElement.removeChild(svgElement.firstChild)\n    }\n\n    // set size\n    svgElement.setAttribute('width', this.width.toString())\n    svgElement.setAttribute('height', this.height.toString())\n\n    // apply backgroundColor\n    let backgroundElement\n    if (this.backgroundColor) {\n      backgroundElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect')\n      backgroundElement.width.baseVal.value = this.width\n      backgroundElement.height.baseVal.value = this.height\n      backgroundElement.setAttribute('fill', this.backgroundColor)\n      svgElement.appendChild(backgroundElement)\n    }\n\n    // use round linecap to emphasize a ballpoint pen like drawing\n    svgElement.setAttribute('stroke-linecap', 'round')\n\n    return svgElement\n  }\n\n  /**\n   * Initializes the size based on the currently set SVG and collects elements\n   * with an ID property that may be referenced in the SVG.\n   */\n  private sourceSvgChanged() {\n    const svg = this.$svg\n    if (svg) {\n      const precision = this.roughConfig.fixedDecimalPlaceDigits\n      this.width = parseFloat(this.coerceSize(svg, 'width', 300).toFixed(precision))\n      this.height = parseFloat(this.coerceSize(svg, 'height', 150).toFixed(precision))\n\n      // pre-process defs for subsequent references\n      this.collectElementsWithID()\n    }\n  }\n\n  /**\n   * Stores elements with IDs for later use.\n   */\n  private collectElementsWithID() {\n    this.idElements = {}\n    const elementsWithID: SVGElement[] = Array.prototype.slice.apply(\n      this.svg!.querySelectorAll('*[id]')\n    )\n    for (const elt of elementsWithID) {\n      const id = elt.getAttribute('id')\n      if (id) {\n        this.idElements[id] = elt\n      }\n    }\n  }\n\n  /**\n   * Helper to handle percentage values for width / height of the input SVG.\n   */\n  private coerceSize(svg: SVGSVGElement, property: 'width' | 'height', fallback: number): number {\n    let size = fallback\n    const hasViewbox = svg.hasAttribute('viewBox')\n    if (svg.hasAttribute(property)) {\n      // percentage sizes for the root SVG are unclear, thus use viewBox if available\n      if (svg[property].baseVal.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE && hasViewbox) {\n        size = svg.viewBox.baseVal[property]\n      } else {\n        size = svg[property].baseVal.value\n      }\n    } else if (hasViewbox) {\n      size = svg.viewBox.baseVal[property]\n    }\n    return size\n  }\n}\n"
  },
  {
    "path": "src/clipping.ts",
    "content": "import { getIdFromUrl, getNodeChildren } from './dom-helpers'\nimport { applyCircleClip } from './geom/circle'\nimport { applyEllipseClip } from './geom/ellipse'\nimport { applyPathClip } from './geom/path'\nimport { applyPolygonClip } from './geom/polygon'\nimport { applyRectClip } from './geom/rect'\nimport { getCombinedTransform } from './transformation'\nimport { RenderContext } from './types'\nimport { getDefsElement, SKETCH_CLIP_ATTRIBUTE } from './utils'\n\n/**\n * Applies the clip-path to the CanvasContext.\n */\nexport function applyClipPath(\n  context: RenderContext,\n  owner: SVGElement,\n  clipPathAttr: string,\n  svgTransform: SVGTransform | null\n): void {\n  const id = getIdFromUrl(clipPathAttr)\n  if (!id) {\n    return\n  }\n\n  const clipPath = context.idElements[id] as SVGElement\n  if (!clipPath) {\n    return\n  }\n\n  // TODO clipPath: consider clipPathUnits\n  //  create clipPath defs\n  const targetDefs = getDefsElement(context)\n  // unfortunately, we cannot reuse clip-paths due to the 'global transform' approach\n  const sketchClipPathId = `${id}_${targetDefs.childElementCount}`\n  const clipContainer = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath')\n  clipContainer.id = sketchClipPathId\n  storeSketchClipId(owner, sketchClipPathId)\n\n  // traverse clip-path elements in DFS\n  const stack: { element: SVGElement; transform: SVGTransform | null }[] = []\n  const children = getNodeChildren(clipPath)\n  for (let i = children.length - 1; i >= 0; i--) {\n    const childElement = children[i] as SVGGraphicsElement\n    const childTransform = getCombinedTransform(context, childElement, svgTransform)\n    stack.push({ element: childElement, transform: childTransform })\n  }\n\n  while (stack.length > 0) {\n    const { element, transform } = stack.pop()!\n\n    try {\n      applyElementClip(context, element, clipContainer, transform)\n    } catch (e) {\n      console.error(e)\n    }\n\n    if (\n      element.tagName === 'defs' ||\n      element.tagName === 'svg' ||\n      element.tagName === 'clipPath' ||\n      element.tagName === 'text'\n    ) {\n      // some elements are ignored on clippaths\n      continue\n    }\n    // process children\n    const children = getNodeChildren(element)\n    for (let i = children.length - 1; i >= 0; i--) {\n      const childElement = children[i] as SVGGraphicsElement\n      const childTransform = getCombinedTransform(context, childElement, transform)\n      stack.push({ element: childElement, transform: childTransform })\n    }\n  }\n\n  if (clipContainer.childNodes.length > 0) {\n    // add the clip-path only if it contains converted elements\n    // some elements are not yet supported\n    targetDefs.appendChild(clipContainer)\n  }\n}\n\n/**\n * Creates a clip element and appends it to the given container.\n */\nfunction applyElementClip(\n  context: RenderContext,\n  element: SVGElement,\n  container: SVGClipPathElement,\n  svgTransform: SVGTransform | null\n) {\n  switch (element.tagName) {\n    case 'rect':\n      applyRectClip(context, element as SVGRectElement, container, svgTransform)\n      break\n    case 'circle':\n      applyCircleClip(context, element as SVGCircleElement, container, svgTransform)\n      break\n    case 'ellipse':\n      applyEllipseClip(context, element as SVGEllipseElement, container, svgTransform)\n      break\n    case 'polygon':\n      applyPolygonClip(context, element as SVGPolygonElement, container, svgTransform)\n      break\n    case 'path':\n      applyPathClip(context, element as SVGPathElement, container, svgTransform)\n      break\n  }\n}\n\n/**\n * Store clippath-id on each child for <g> elements, or on the owner itself for other\n * elements.\n *\n * <g> elements are skipped in the processing loop, thus the clip-path id must be stored\n * on the child elements.\n */\nfunction storeSketchClipId(element: SVGElement, id: string): void {\n  if (element.tagName !== 'g') {\n    element.setAttribute(SKETCH_CLIP_ATTRIBUTE, id)\n    return\n  }\n\n  const stack: SVGElement[] = []\n  const children = getNodeChildren(element)\n  for (let i = children.length - 1; i >= 0; i--) {\n    stack.push(children[i] as SVGElement)\n  }\n\n  while (stack.length > 0) {\n    const element = stack.pop()!\n    element.setAttribute(SKETCH_CLIP_ATTRIBUTE, id)\n\n    const children = getNodeChildren(element)\n    for (let i = children.length - 1; i >= 0; i--) {\n      stack.push(children[i] as SVGElement)\n    }\n  }\n}\n"
  },
  {
    "path": "src/dom-helpers.ts",
    "content": "import { RenderContext } from './types'\n\n/**\n * Returns the Node's children, since Node.prototype.children is not available on all browsers.\n * https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/children\n */\nexport function getNodeChildren(element: Element): Element[] {\n  if (typeof element.children !== 'undefined') {\n    return element.children as unknown as Element[]\n  }\n  let i = 0\n  let node\n  const nodes = element.childNodes\n  const children: Element[] = []\n  while ((node = nodes[i++])) {\n    if (node.nodeType === 1) {\n      children.push(node as Element)\n    }\n  }\n  return children\n}\n\n/**\n * IE doesn't support `element.parentElement` in SVG documents.\n * This helper utilizes `parentNode` and checks for the `nodeType`.\n */\nexport function getParentElement(node: Node): Element | null {\n  const parentNode = node.parentNode\n  if (parentNode && parentNode.nodeType === Node.ELEMENT_NODE) {\n    return parentNode as Element\n  }\n  return null\n}\n\n/**\n * Returns the CSS rules that apply to the given element (ignoring inheritance).\n *\n * Based on https://stackoverflow.com/a/22638396\n */\nexport function getMatchedCssRules(context: RenderContext, el: Element): CSSStyleRule[] {\n  const ret: CSSStyleRule[] = []\n  el.matches =\n    el.matches ||\n    el.webkitMatchesSelector ||\n    // @ts-expect-error: legacy browser support\n    el.mozMatchesSelector ||\n    // @ts-expect-error: legacy browser support\n    el.msMatchesSelector ||\n    // @ts-expect-error: legacy browser support\n    el.oMatchesSelector\n\n  context.styleSheets.forEach(sheet => {\n    const rules = sheet.rules || sheet.cssRules\n    for (const r in rules) {\n      const rule = rules[r] as CSSStyleRule\n      if (el.matches(rule.selectorText)) {\n        ret.push(rule)\n      }\n    }\n  })\n  return ret\n}\n\n/**\n * Moves the child-nodes from the source to a new parent.\n */\nexport function reparentNodes<T extends SVGElement>(newParent: T, source: SVGElement): T {\n  while (source.firstChild) {\n    newParent.append(source.firstChild)\n  }\n  return newParent\n}\n\n/**\n * Returns the id from the url string\n */\nexport function getIdFromUrl(url: string | null): string | null {\n  if (url === null) {\n    return null\n  }\n  const result =\n    /url\\('#?(.*?)'\\)/.exec(url) || /url\\(\"#?(.*?)\"\\)/.exec(url) || /url\\(#?(.*?)\\)/.exec(url)\n  if (result && result.length > 1) {\n    return result[1]\n  }\n  return null\n}\n"
  },
  {
    "path": "src/geom/circle.ts",
    "content": "import { appendPatternPaint } from '../styles/pattern'\nimport { parseStyleConfig } from '../styles/styles'\nimport {\n  applyTransform,\n  applyMatrix,\n  isIdentityTransform,\n  isTranslationTransform\n} from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement, sketchPath } from '../utils'\nimport { str } from './primitives'\n\nexport function drawCircle(\n  context: RenderContext,\n  circle: SVGCircleElement,\n  svgTransform: SVGTransform | null\n): void {\n  const cx = circle.cx.baseVal.value\n  const cy = circle.cy.baseVal.value\n  const r = circle.r.baseVal.value\n\n  if (r === 0) {\n    // zero-radius circle is not rendered\n    return\n  }\n\n  const center = applyMatrix({ x: cx, y: cy }, svgTransform)\n  const radiusPoint = applyMatrix({ x: cx + r, y: cy + r }, svgTransform)\n  const transformedRadius = radiusPoint.x - center.x\n\n  let result\n  if (isIdentityTransform(svgTransform) || isTranslationTransform(svgTransform)) {\n    // transform a point on the ellipse to get the transformed radius\n    result = context.rc.circle(center.x, center.y, 2 * transformedRadius, {\n      ...parseStyleConfig(context, circle, svgTransform),\n      preserveVertices: true\n    })\n  } else {\n    // in other cases we need to construct the path manually.\n    const factor = (4 / 3) * (Math.sqrt(2) - 1)\n    const p1 = applyMatrix({ x: cx + r, y: cy }, svgTransform)\n    const p2 = applyMatrix({ x: cx, y: cy + r }, svgTransform)\n    const p3 = applyMatrix({ x: cx - r, y: cy }, svgTransform)\n    const p4 = applyMatrix({ x: cx, y: cy - r }, svgTransform)\n    const c1 = applyMatrix({ x: cx + r, y: cy + factor * r }, svgTransform)\n    const c2 = applyMatrix({ x: cx + factor * r, y: cy + r }, svgTransform)\n    const c4 = applyMatrix({ x: cx - r, y: cy + factor * r }, svgTransform)\n    const c6 = applyMatrix({ x: cx - factor * r, y: cy - r }, svgTransform)\n    const c8 = applyMatrix({ x: cx + r, y: cy - factor * r }, svgTransform)\n    const path = `M ${str(p1)} C ${str(c1)} ${str(c2)} ${str(p2)} S ${str(c4)} ${str(p3)} S ${str(\n      c6\n    )} ${str(p4)} S ${str(c8)} ${str(p1)}z`\n    result = sketchPath(context, path, parseStyleConfig(context, circle, svgTransform))\n  }\n\n  appendPatternPaint(context, circle, () => {\n    const proxy = document.createElementNS('http://www.w3.org/2000/svg', 'circle')\n    proxy.cx.baseVal.value = center.x\n    proxy.cy.baseVal.value = center.y\n    proxy.r.baseVal.value = transformedRadius\n    return proxy\n  })\n  appendSketchElement(context, circle, result)\n}\n\nexport function applyCircleClip(\n  context: RenderContext,\n  circle: SVGCircleElement,\n  container: SVGClipPathElement,\n  svgTransform: SVGTransform | null\n): void {\n  const cx = circle.cx.baseVal.value\n  const cy = circle.cy.baseVal.value\n  const r = circle.r.baseVal.value\n\n  if (r === 0) {\n    // zero-radius circle is not rendered\n    return\n  }\n\n  const clip = document.createElementNS('http://www.w3.org/2000/svg', 'circle')\n  clip.cx.baseVal.value = cx\n  clip.cy.baseVal.value = cy\n  clip.r.baseVal.value = r\n  applyTransform(context, svgTransform, clip)\n  container.appendChild(clip)\n}\n"
  },
  {
    "path": "src/geom/ellipse.ts",
    "content": "import { appendPatternPaint } from '../styles/pattern'\nimport { parseStyleConfig } from '../styles/styles'\nimport {\n  applyTransform,\n  applyMatrix,\n  isIdentityTransform,\n  isTranslationTransform\n} from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement, sketchPath } from '../utils'\nimport { str } from './primitives'\n\nexport function drawEllipse(\n  context: RenderContext,\n  ellipse: SVGEllipseElement,\n  svgTransform: SVGTransform | null\n): void {\n  const cx = ellipse.cx.baseVal.value\n  const cy = ellipse.cy.baseVal.value\n  const rx = ellipse.rx.baseVal.value\n  const ry = ellipse.ry.baseVal.value\n\n  if (rx === 0 || ry === 0) {\n    // zero-radius ellipse is not rendered\n    return\n  }\n\n  const center = applyMatrix({ x: cx, y: cy }, svgTransform)\n  // transform a point on the ellipse to get the transformed radius\n  const radiusPoint = applyMatrix({ x: cx + rx, y: cy + ry }, svgTransform)\n  const transformedRx = radiusPoint.x - center.x\n  const transformedRy = radiusPoint.y - center.y\n\n  let result\n  if (isIdentityTransform(svgTransform) || isTranslationTransform(svgTransform)) {\n    // Simple case, there's no transform and we can use the ellipse command\n    result = context.rc.ellipse(center.x, center.y, 2 * transformedRx, 2 * transformedRy, {\n      ...parseStyleConfig(context, ellipse, svgTransform),\n      preserveVertices: true\n    })\n  } else {\n    // in other cases we need to construct the path manually.\n    const factor = (4 / 3) * (Math.sqrt(2) - 1)\n    const p1 = applyMatrix({ x: cx + rx, y: cy }, svgTransform)\n    const p2 = applyMatrix({ x: cx, y: cy + ry }, svgTransform)\n    const p3 = applyMatrix({ x: cx - rx, y: cy }, svgTransform)\n    const p4 = applyMatrix({ x: cx, y: cy - ry }, svgTransform)\n    const c1 = applyMatrix({ x: cx + rx, y: cy + factor * ry }, svgTransform)\n    const c2 = applyMatrix({ x: cx + factor * rx, y: cy + ry }, svgTransform)\n    const c4 = applyMatrix({ x: cx - rx, y: cy + factor * ry }, svgTransform)\n    const c6 = applyMatrix({ x: cx - factor * rx, y: cy - ry }, svgTransform)\n    const c8 = applyMatrix({ x: cx + rx, y: cy - factor * ry }, svgTransform)\n    const path = `M ${str(p1)} C ${str(c1)} ${str(c2)} ${str(p2)} S ${str(c4)} ${str(p3)} S ${str(\n      c6\n    )} ${str(p4)} S ${str(c8)} ${str(p1)}z`\n    result = sketchPath(context, path, parseStyleConfig(context, ellipse, svgTransform))\n  }\n\n  appendPatternPaint(context, ellipse, () => {\n    const proxy = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse')\n    proxy.cx.baseVal.value = center.x\n    proxy.cy.baseVal.value = center.y\n    proxy.rx.baseVal.value = transformedRx\n    proxy.ry.baseVal.value = transformedRy\n    return proxy\n  })\n  appendSketchElement(context, ellipse, result)\n}\n\nexport function applyEllipseClip(\n  context: RenderContext,\n  ellipse: SVGEllipseElement,\n  container: SVGClipPathElement,\n  svgTransform: SVGTransform | null\n): void {\n  const cx = ellipse.cx.baseVal.value\n  const cy = ellipse.cy.baseVal.value\n  const rx = ellipse.rx.baseVal.value\n  const ry = ellipse.ry.baseVal.value\n\n  if (rx === 0 || ry === 0) {\n    // zero-radius ellipse is not rendered\n    return\n  }\n\n  const clip = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse')\n  clip.cx.baseVal.value = cx\n  clip.cy.baseVal.value = cy\n  clip.rx.baseVal.value = rx\n  clip.ry.baseVal.value = ry\n  applyTransform(context, svgTransform, clip)\n  container.appendChild(clip)\n}\n"
  },
  {
    "path": "src/geom/foreign-object.ts",
    "content": "import { applyTransform } from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement } from '../utils'\n\nexport function drawForeignObject(\n  context: RenderContext,\n  foreignObject: SVGForeignObjectElement,\n  svgTransform: SVGTransform | null\n): void {\n  const foreignObjectClone = foreignObject.cloneNode(true) as SVGForeignObjectElement\n  const container = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n\n  // foreignObject often relies on CSS styling, and just copying the <style> element\n  // won't do the trick, because sketching the SVG rebuilds the entire element tree, thus\n  // existing CSS rules don't apply anymore in most cases.\n  //\n  // To to make the MOST SIMPLE cases of foreignObject text elements work better,\n  // try to apply the computed style on the new SVG container.\n  // To properly fix it, we'd need to inline all computed styles recursively on the\n  // foreignObject tree.\n\n  const copyStyleProperties = [\n    'color',\n    'font-family',\n    'font-size',\n    'font-style',\n    'font-variant',\n    'font-weight'\n  ]\n  const style = getComputedStyle(foreignObject)\n  for (const prop of copyStyleProperties) {\n    container.style.setProperty(prop, style.getPropertyValue(prop))\n  }\n\n  // transform is already considered in svgTransform\n  foreignObjectClone.transform.baseVal.clear()\n\n  // transform the foreignObject to its destination location\n  applyTransform(context, svgTransform, container)\n  container.appendChild(foreignObjectClone)\n  appendSketchElement(context, foreignObjectClone, container)\n}\n"
  },
  {
    "path": "src/geom/image.ts",
    "content": "import { applyTransform } from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement } from '../utils'\n\nexport function drawImage(\n  context: RenderContext,\n  svgImage: SVGImageElement,\n  svgTransform: SVGTransform | null\n): void {\n  const href = svgImage.href.baseVal\n  const x = svgImage.x.baseVal.value\n  const y = svgImage.y.baseVal.value\n  let width, height\n  if (svgImage.getAttribute('width') && svgImage.getAttribute('height')) {\n    width = svgImage.width.baseVal.value\n    height = svgImage.height.baseVal.value\n  }\n  if (href.startsWith('data:') && href.indexOf('image/svg+xml') !== -1) {\n    // data:[<media type>][;charset=<character set>][;base64],<data>\n    const dataUrlRegex = /^data:([^,]*),(.*)/\n    const match = dataUrlRegex.exec(href)\n    if (match && match.length > 2) {\n      const meta = match[1]\n      let svgString = match[2]\n      const isBase64 = meta.indexOf('base64') !== -1\n      const isUtf8 = meta.indexOf('utf8') !== -1\n      if (isBase64) {\n        svgString = atob(svgString)\n      }\n      if (!isUtf8) {\n        svgString = decodeURIComponent(svgString)\n      }\n      const parser = new DOMParser()\n      const doc = parser.parseFromString(svgString, 'image/svg+xml')\n      const svg = doc.firstChild as SVGSVGElement\n\n      let matrix = context.sourceSvg.createSVGMatrix().translate(x, y)\n      matrix = svgTransform ? svgTransform.matrix.multiply(matrix) : matrix\n\n      context.processElement(\n        context,\n        svg,\n        context.sourceSvg.createSVGTransformFromMatrix(matrix),\n        width,\n        height\n      )\n      return\n    }\n  } else {\n    const imageClone = svgImage.cloneNode()\n    const container = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n    applyTransform(context, svgTransform, container)\n    container.appendChild(imageClone)\n    appendSketchElement(context, svgImage, container)\n  }\n}\n"
  },
  {
    "path": "src/geom/line.ts",
    "content": "import { appendPatternPaint } from '../styles/pattern'\nimport { parseStyleConfig } from '../styles/styles'\nimport { applyMatrix } from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement } from '../utils'\nimport { drawMarkers } from './marker'\n\nexport function drawLine(\n  context: RenderContext,\n  line: SVGLineElement,\n  svgTransform: SVGTransform | null\n): void {\n  const p1 = { x: line.x1.baseVal.value, y: line.y1.baseVal.value }\n  const p2 = { x: line.x2.baseVal.value, y: line.y2.baseVal.value }\n  const { x: tp1x, y: tp1y } = applyMatrix(p1, svgTransform)\n  const { x: tp2x, y: tp2y } = applyMatrix(p2, svgTransform)\n\n  if (tp1x === tp2x && tp1y === tp2y) {\n    // zero-length line is not rendered\n    return\n  }\n\n  const lineSketch = context.rc.line(\n    tp1x,\n    tp1y,\n    tp2x,\n    tp2y,\n    parseStyleConfig(context, line, svgTransform)\n  )\n\n  appendPatternPaint(context, line, () => {\n    const proxy = document.createElementNS('http://www.w3.org/2000/svg', 'line')\n    proxy.x1.baseVal.value = tp1x\n    proxy.y1.baseVal.value = tp1y\n    proxy.x2.baseVal.value = tp2x\n    proxy.y2.baseVal.value = tp2y\n    return proxy\n  })\n\n  appendSketchElement(context, line, lineSketch)\n\n  drawMarkers(context, line, [p1, p2], svgTransform)\n}\n"
  },
  {
    "path": "src/geom/marker.ts",
    "content": "import { getIdFromUrl } from '../dom-helpers'\nimport { getEffectiveAttribute } from '../styles/effective-attributes'\nimport { convertToPixelUnit } from '../svg-units'\nimport { RenderContext } from '../types'\nimport { equals, Point } from './primitives'\n\nexport function drawMarkers(\n  context: RenderContext,\n  element: SVGPathElement | SVGLineElement | SVGPolylineElement | SVGPolygonElement,\n  points: Point[],\n  svgTransform: SVGTransform | null\n): void {\n  if (points.length === 0) {\n    return\n  }\n\n  const startPt = points[0]\n  const endPt = points[points.length - 1]\n\n  // start marker\n  const markerStartId = getIdFromUrl(element.getAttribute('marker-start'))\n  const markerStartElement = markerStartId\n    ? (context.idElements[markerStartId] as SVGMarkerElement)\n    : null\n\n  // marker-start is only rendered when there are at least two points\n  if (markerStartElement && points.length > 1) {\n    let angle = markerStartElement.orientAngle.baseVal.value\n\n    const nextPt = points[1]\n    const orientAttr = markerStartElement.getAttribute('orient')\n    if (orientAttr === 'auto' || orientAttr === 'auto-start-reverse') {\n      const reverse = orientAttr === 'auto' ? 0 : 180\n      const prevPt = points[points.length - 2]\n      if (isClosedPath(points)) {\n        // https://www.w3.org/TR/SVG11/painting.html#OrientAttribute\n        // use angle bisector of incoming and outgoing angle\n        angle = getBisectingAngle(prevPt, endPt, nextPt) - reverse\n      } else {\n        const vOut = { x: nextPt.x - startPt.x, y: nextPt.y - startPt.y }\n        angle = getAngle({ x: 1, y: 0 }, vOut) - reverse\n      }\n    }\n\n    const matrix = context.sourceSvg\n      .createSVGMatrix()\n      .translate(startPt.x, startPt.y)\n      .rotate(angle)\n      .scale(getScaleFactor(context, markerStartElement, element))\n\n    const combinedMatrix = svgTransform ? svgTransform.matrix.multiply(matrix) : matrix\n    const markerTransform = context.sourceSvg.createSVGTransformFromMatrix(combinedMatrix)\n\n    context.processElement(context, markerStartElement, markerTransform)\n  }\n\n  // end marker\n  const markerEndId = getIdFromUrl(element.getAttribute('marker-end'))\n  const markerEndElement = markerEndId\n    ? (context.idElements[markerEndId] as SVGMarkerElement)\n    : null\n\n  // marker-end is also rendered if the path has only one point\n  if (markerEndElement) {\n    let angle = markerEndElement.orientAngle.baseVal.value\n\n    if (points.length > 1) {\n      const orientAttr = markerEndElement.getAttribute('orient')\n      if (orientAttr === 'auto' || orientAttr === 'auto-start-reverse') {\n        // by spec, \"auto-start-reverse\" has no effect on marker end\n        const prevPt = points[points.length - 2]\n        if (isClosedPath(points)) {\n          // https://www.w3.org/TR/SVG11/painting.html#OrientAttribute\n          // use angle bisector of incoming and outgoing angle\n          const nextPt = points[1] // start and end points are equal, take second point\n          angle = getBisectingAngle(prevPt, endPt, nextPt)\n        } else {\n          const vIn = { x: endPt.x - prevPt.x, y: endPt.y - prevPt.y }\n          angle = getAngle({ x: 1, y: 0 }, vIn)\n        }\n      }\n    }\n\n    const matrix = context.sourceSvg\n      .createSVGMatrix()\n      .translate(endPt.x, endPt.y)\n      .rotate(angle)\n      .scale(getScaleFactor(context, markerEndElement, element))\n\n    const combinedMatrix = svgTransform ? svgTransform.matrix.multiply(matrix) : matrix\n    const markerTransform = context.sourceSvg.createSVGTransformFromMatrix(combinedMatrix)\n\n    context.processElement(context, markerEndElement, markerTransform)\n  }\n\n  // mid marker(s)\n  const markerMidId = getIdFromUrl(element.getAttribute('marker-mid'))\n  const markerMidElement = markerMidId\n    ? (context.idElements[markerMidId] as SVGMarkerElement)\n    : null\n\n  if (markerMidElement && points.length > 2) {\n    for (let i = 0; i < points.length; i++) {\n      const loc = points[i]\n      if (i === 0 || i === points.length - 1) {\n        // mid markers are not drawn on first or last point\n        continue\n      }\n\n      let angle = markerMidElement.orientAngle.baseVal.value\n      const orientAttr = markerMidElement.getAttribute('orient')\n      if (orientAttr === 'auto' || orientAttr === 'auto-start-reverse') {\n        // by spec, \"auto-start-reverse\" has no effect on marker mid\n        const prevPt = points[i - 1]\n        const nextPt = points[i + 1]\n        // https://www.w3.org/TR/SVG11/painting.html#OrientAttribute\n        // use angle bisector of incoming and outgoing angle\n        angle = getBisectingAngle(prevPt, loc, nextPt)\n      }\n\n      const matrix = context.sourceSvg\n        .createSVGMatrix()\n        .translate(loc.x, loc.y)\n        .rotate(angle)\n        .scale(getScaleFactor(context, markerMidElement, element))\n\n      const combinedMatrix = svgTransform ? svgTransform.matrix.multiply(matrix) : matrix\n      const markerTransform = context.sourceSvg.createSVGTransformFromMatrix(combinedMatrix)\n\n      context.processElement(context, markerMidElement, markerTransform)\n    }\n  }\n}\n\n/**\n * Consider scaled coordinate system for markerWidth/markerHeight.\n */\nfunction getScaleFactor(\n  context: RenderContext,\n  marker: SVGMarkerElement,\n  referrer: SVGElement\n): number {\n  const markerUnits = marker.getAttribute('markerUnits')\n  let scaleFactor = 1\n  if (!markerUnits || markerUnits === 'strokeWidth') {\n    // default is strokeWidth by SVG spec\n    const strokeWidth = getEffectiveAttribute(context, referrer, 'stroke-width')\n    if (strokeWidth) {\n      scaleFactor = convertToPixelUnit(context, referrer, strokeWidth, 'stroke-width')\n    }\n  }\n  return scaleFactor\n}\n\n/**\n * Whether the path is closed, i.e. the start and end points are identical\n */\nfunction isClosedPath(points: Point[]): boolean {\n  return equals(points[0], points[points.length - 1])\n}\n\n/**\n * Returns the bisection angle of the angle that is spanned by the given points.\n * @param prevPt The point from which the incoming flank is pointing\n * @param crossingPt The anchor point of the angle\n * @param nextPt Th point to which the outgoing flank is pointing\n * @returns The bisecting angle\n */\nfunction getBisectingAngle(prevPt: Point, crossingPt: Point, nextPt: Point): number {\n  const vIn = { x: nextPt.x - crossingPt.x, y: nextPt.y - crossingPt.y }\n  const vOut = { x: prevPt.x - crossingPt.x, y: prevPt.y - crossingPt.y }\n\n  // the relative angle between the two vectors\n  const vectorAngle = getAngle(vIn, vOut)\n\n  // calculate the absolute angle of the vectors considering the x-axis as reference\n  const refPoint = { x: crossingPt.x + 1, y: crossingPt.y }\n  const refVector = { x: refPoint.x - crossingPt.x, y: refPoint.y - crossingPt.y }\n  const refAngle = getAngle(vIn, refVector)\n\n  // return the absolute bisector\n  return getOppositeAngle(vectorAngle) / 2 - refAngle\n}\n\n/**\n * Returns the opposite angle of the line. Considers the direction of the angle\n * (i.e. positive for clockwise, negative for counter-clickwise).\n */\nfunction getOppositeAngle(angle: number): number {\n  return angle - Math.sign(angle) * 180\n}\n\n/**\n * Returns the signed angle between the vectors (i.e. positive for clockwise,\n * negative for counter-clickwise).\n * @param v1 2-dimensional vector\n * @param v2 2-dimensional vector\n * @returns The signed angle between the vectors\n */\nfunction getAngle(v1: Point, v2: Point): number {\n  const a1 = Math.atan2(v1.y, v1.x)\n  const a2 = Math.atan2(v2.y, v2.x)\n  const angle = a2 - a1\n  const K = -Math.sign(angle) * Math.PI * 2\n  const a = Math.abs(K + angle) < Math.abs(angle) ? K + angle : angle\n  return Math.round((360 * a) / (Math.PI * 2))\n}\n"
  },
  {
    "path": "src/geom/path.ts",
    "content": "import { encodeSVGPath, SVGPathData, SVGPathDataTransformer } from 'svg-pathdata'\nimport { appendPatternPaint } from '../styles/pattern'\nimport { parseStyleConfig } from '../styles/styles'\nimport { applyTransform } from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement, sketchPath } from '../utils'\nimport { drawMarkers } from './marker'\nimport { Point } from './primitives'\n\nexport function drawPath(\n  context: RenderContext,\n  path: SVGPathElement,\n  svgTransform: SVGTransform | null\n): void {\n  const dataAttrs = path.getAttribute('d')\n  const pathData =\n    // Parse path data and convert to absolute coordinates\n    new SVGPathData(dataAttrs!)\n      .toAbs()\n      // Normalize H and V to L commands - those cannot work with how we draw transformed paths otherwise\n      .transform(SVGPathDataTransformer.NORMALIZE_HVZ())\n      // Normalize S and T to Q and C commands - Rough.js has a bug with T where it expects 4 parameters instead of 2\n      .transform(SVGPathDataTransformer.NORMALIZE_ST())\n\n  // If there's a transform, transform the whole path accordingly\n  const transformedPathData = new SVGPathData(\n    // clone the commands, we might need them untransformed for markers\n    pathData.commands.map(cmd => Object.assign({}, cmd))\n  )\n  if (svgTransform) {\n    transformedPathData.transform(\n      SVGPathDataTransformer.MATRIX(\n        svgTransform.matrix.a,\n        svgTransform.matrix.b,\n        svgTransform.matrix.c,\n        svgTransform.matrix.d,\n        svgTransform.matrix.e,\n        svgTransform.matrix.f\n      )\n    )\n  }\n\n  const encodedPathData = encodeSVGPath(transformedPathData.commands)\n  if (encodedPathData.indexOf('undefined') !== -1) {\n    // DEBUG STUFF\n    console.error('broken path data')\n    return\n  }\n\n  const pathSketch = sketchPath(\n    context,\n    encodedPathData,\n    parseStyleConfig(context, path, svgTransform)\n  )\n\n  appendPatternPaint(context, path, () => {\n    const proxy = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n    proxy.setAttribute('d', encodedPathData)\n    return proxy\n  })\n\n  appendSketchElement(context, path, pathSketch)\n\n  // https://www.w3.org/TR/SVG11/painting.html#MarkerProperties\n  // Note that for a ‘path’ element which ends with a closed sub-path,\n  // the last vertex is the same as the initial vertex on the given\n  // sub-path (same applies to polygon).\n  const points: Point[] = []\n  let currentSubPathBegin: Point\n  pathData.commands.forEach(cmd => {\n    switch (cmd.type) {\n      case SVGPathData.MOVE_TO: {\n        const p = { x: cmd.x, y: cmd.y }\n        points.push(p)\n        // each moveto starts a new subpath\n        currentSubPathBegin = p\n        break\n      }\n      case SVGPathData.LINE_TO:\n      case SVGPathData.QUAD_TO:\n      case SVGPathData.SMOOTH_QUAD_TO:\n      case SVGPathData.CURVE_TO:\n      case SVGPathData.SMOOTH_CURVE_TO:\n      case SVGPathData.ARC:\n        points.push({ x: cmd.x, y: cmd.y })\n        break\n      case SVGPathData.HORIZ_LINE_TO:\n        points.push({ x: cmd.x, y: 0 })\n        break\n      case SVGPathData.VERT_LINE_TO:\n        points.push({ x: 0, y: cmd.y })\n        break\n      case SVGPathData.CLOSE_PATH:\n        if (currentSubPathBegin) {\n          points.push(currentSubPathBegin)\n        }\n        break\n    }\n  })\n  drawMarkers(context, path, points, svgTransform)\n}\n\nexport function applyPathClip(\n  context: RenderContext,\n  path: SVGPathElement,\n  container: SVGClipPathElement,\n  svgTransform: SVGTransform | null\n): void {\n  const clip = document.createElementNS('http://www.w3.org/2000/svg', 'path')\n  clip.setAttribute('d', path.getAttribute('d')!)\n  applyTransform(context, svgTransform, clip)\n  container.appendChild(clip)\n}\n"
  },
  {
    "path": "src/geom/polygon.ts",
    "content": "import { Point } from 'roughjs/bin/geometry'\nimport { appendPatternPaint } from '../styles/pattern'\nimport { parseStyleConfig } from '../styles/styles'\nimport { applyTransform, applyMatrix } from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement, getPointsArray } from '../utils'\nimport { drawMarkers } from './marker'\n\nexport function drawPolygon(\n  context: RenderContext,\n  polygon: SVGPolygonElement,\n  svgTransform: SVGTransform | null\n): void {\n  const points = getPointsArray(polygon)\n\n  const transformed = points.map(p => {\n    const pt = applyMatrix(p, svgTransform)\n    return [pt.x, pt.y] as Point\n  })\n\n  const polygonSketch = context.rc.polygon(\n    transformed,\n    parseStyleConfig(context, polygon, svgTransform)\n  )\n\n  appendPatternPaint(context, polygon, () => {\n    const proxy = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n    proxy.setAttribute('points', transformed.join(' '))\n    return proxy\n  })\n\n  appendSketchElement(context, polygon, polygonSketch)\n\n  // https://www.w3.org/TR/SVG11/painting.html#MarkerProperties\n  // Note that for a ‘path’ element which ends with a closed sub-path,\n  // the last vertex is the same as the initial vertex on the given\n  // sub-path (same applies to polygon).\n  if (points.length > 0) {\n    points.push(points[0])\n    drawMarkers(context, polygon, points, svgTransform)\n  }\n}\n\nexport function applyPolygonClip(\n  context: RenderContext,\n  polygon: SVGPolygonElement,\n  container: SVGClipPathElement,\n  svgTransform: SVGTransform | null\n): void {\n  const clip = document.createElementNS('http://www.w3.org/2000/svg', 'polygon')\n  clip.setAttribute('points', polygon.getAttribute('points')!)\n  applyTransform(context, svgTransform, clip)\n  container.appendChild(clip)\n}\n"
  },
  {
    "path": "src/geom/polyline.ts",
    "content": "import { Point } from 'roughjs/bin/geometry'\nimport { appendPatternPaint } from '../styles/pattern'\nimport { parseStyleConfig } from '../styles/styles'\nimport { applyMatrix } from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement, getPointsArray } from '../utils'\nimport { drawMarkers } from './marker'\n\nexport function drawPolyline(\n  context: RenderContext,\n  polyline: SVGPolylineElement,\n  svgTransform: SVGTransform | null\n): void {\n  const points = getPointsArray(polyline)\n  const transformed = points.map(p => {\n    const pt = applyMatrix(p, svgTransform)\n    return [pt.x, pt.y] as Point\n  })\n  const style = parseStyleConfig(context, polyline, svgTransform)\n\n  appendPatternPaint(context, polyline, () => {\n    const proxy = document.createElementNS('http://www.w3.org/2000/svg', 'polyline')\n    proxy.setAttribute('points', transformed.join(' '))\n    return proxy\n  })\n\n  if (style.fill && style.fill !== 'none') {\n    const fillStyle = { ...style, stroke: 'none' }\n    appendSketchElement(context, polyline, context.rc.polygon(transformed, fillStyle))\n  }\n  appendSketchElement(context, polyline, context.rc.linearPath(transformed, style))\n\n  drawMarkers(context, polyline, points, svgTransform)\n}\n"
  },
  {
    "path": "src/geom/primitives.ts",
    "content": "export type Point = { x: number; y: number }\nexport type Size = { w: number; h: number }\n\nexport type Rectangle = Point & Size\n\nexport function str(p: Point) {\n  return `${p.x},${p.y}`\n}\n\nexport function equals(p0: Point, p1: Point): boolean {\n  return p0.x === p1.x && p0.y === p1.y\n}\n"
  },
  {
    "path": "src/geom/rect.ts",
    "content": "import { appendPatternPaint } from '../styles/pattern'\nimport { parseStyleConfig } from '../styles/styles'\nimport {\n  applyTransform,\n  applyMatrix,\n  isIdentityTransform,\n  isTranslationTransform\n} from '../transformation'\nimport { RenderContext } from '../types'\nimport { appendSketchElement, sketchPath } from '../utils'\nimport { Rectangle, str } from './primitives'\n\nexport function drawRect(\n  context: RenderContext,\n  rect: SVGRectElement,\n  svgTransform: SVGTransform | null\n): void {\n  const x = rect.x.baseVal.value\n  const y = rect.y.baseVal.value\n  const width = rect.width.baseVal.value\n  const height = rect.height.baseVal.value\n\n  if (width === 0 || height === 0) {\n    // zero-width or zero-height rect will not be rendered\n    return\n  }\n\n  // Negative values are an error and result in the default value, and clamp both values to half their sides' lengths\n  let rx = rect.hasAttribute('rx') ? Math.min(Math.max(0, rect.rx.baseVal.value), width / 2) : null\n  let ry = rect.hasAttribute('ry') ? Math.min(Math.max(0, rect.ry.baseVal.value), height / 2) : null\n  if (rx !== null || ry !== null) {\n    // If only one of the two values is specified, the other has the same value\n    rx = rx === null ? ry : rx\n    ry = ry === null ? rx : ry\n  }\n\n  // the transformed, rectangular bounds\n  const p1 = applyMatrix({ x, y }, svgTransform)\n  const p2 = applyMatrix({ x: x + width, y: y + height }, svgTransform)\n  const transformedWidth = p2.x - p1.x\n  const transformedHeight = p2.y - p1.y\n  const transformedBounds = { x: p1.x, y: p1.y, w: transformedWidth, h: transformedHeight }\n\n  if ((isIdentityTransform(svgTransform) || isTranslationTransform(svgTransform)) && !rx && !ry) {\n    // Simple case; just a rectangle\n    const sketchRect = context.rc.rectangle(\n      transformedBounds.x,\n      transformedBounds.y,\n      transformedBounds.w,\n      transformedBounds.h,\n      parseStyleConfig(context, rect, svgTransform)\n    )\n\n    applyPatternPaint(context, rect, transformedBounds)\n    appendSketchElement(context, rect, sketchRect)\n  } else {\n    let path = ''\n    if (rx !== null && ry !== null) {\n      const factor = (4 / 3) * (Math.sqrt(2) - 1)\n\n      // Construct path for the rounded rectangle\n      // perform an absolute moveto operation to location (x+rx,y), where x is the value of the ‘rect’ element's ‘x’ attribute converted to user space, rx is the effective value of the ‘rx’ attribute converted to user space and y is the value of the ‘y’ attribute converted to user space\n      const p1 = applyMatrix({ x: x + rx, y }, svgTransform)\n      path += `M ${str(p1)}`\n      // perform an absolute horizontal lineto operation to location (x+width-rx,y), where width is the ‘rect’ element's ‘width’ attribute converted to user space\n      const p2 = applyMatrix({ x: x + width - rx, y }, svgTransform)\n      path += `L ${str(p2)}`\n      // perform an absolute elliptical arc operation to coordinate (x+width,y+ry), where the effective values for the ‘rx’ and ‘ry’ attributes on the ‘rect’ element converted to user space are used as the rx and ry attributes on the elliptical arc command, respectively, the x-axis-rotation is set to zero, the large-arc-flag is set to zero, and the sweep-flag is set to one\n      const p3c1 = applyMatrix({ x: x + width - rx + factor * rx, y }, svgTransform)\n      const p3c2 = applyMatrix({ x: x + width, y: y + factor * ry }, svgTransform)\n      const p3 = applyMatrix({ x: x + width, y: y + ry }, svgTransform)\n      path += `C ${str(p3c1)} ${str(p3c2)} ${str(p3)}` // We cannot use the arc command, since we no longer draw in the expected coordinates. So approximate everything with lines and béziers\n\n      // perform a absolute vertical lineto to location (x+width,y+height-ry), where height is the ‘rect’ element's ‘height’ attribute converted to user space\n      const p4 = applyMatrix({ x: x + width, y: y + height - ry }, svgTransform)\n      path += `L ${str(p4)}`\n      // perform an absolute elliptical arc operation to coordinate (x+width-rx,y+height)\n      const p5c1 = applyMatrix({ x: x + width, y: y + height - ry + factor * ry }, svgTransform)\n      const p5c2 = applyMatrix({ x: x + width - factor * rx, y: y + height }, svgTransform)\n      const p5 = applyMatrix({ x: x + width - rx, y: y + height }, svgTransform)\n      path += `C ${str(p5c1)} ${str(p5c2)} ${str(p5)}`\n      // perform an absolute horizontal lineto to location (x+rx,y+height)\n      const p6 = applyMatrix({ x: x + rx, y: y + height }, svgTransform)\n      path += `L ${str(p6)}`\n      // perform an absolute elliptical arc operation to coordinate (x,y+height-ry)\n      const p7c1 = applyMatrix({ x: x + rx - factor * rx, y: y + height }, svgTransform)\n      const p7c2 = applyMatrix({ x, y: y + height - factor * ry }, svgTransform)\n      const p7 = applyMatrix({ x, y: y + height - ry }, svgTransform)\n      path += `C ${str(p7c1)} ${str(p7c2)} ${str(p7)}`\n      // perform an absolute absolute vertical lineto to location (x,y+ry)\n      const p8 = applyMatrix({ x, y: y + ry }, svgTransform)\n      path += `L ${str(p8)}`\n      // perform an absolute elliptical arc operation to coordinate (x+rx,y)\n      const p9c1 = applyMatrix({ x, y: y + factor * ry }, svgTransform)\n      const p9c2 = applyMatrix({ x: x + factor * rx, y }, svgTransform)\n      path += `C ${str(p9c1)} ${str(p9c2)} ${str(p1)}`\n      path += 'z'\n    } else {\n      // No rounding, so just construct the respective path as a simple polygon\n      const p1 = applyMatrix({ x, y }, svgTransform)\n      const p2 = applyMatrix({ x: x + width, y }, svgTransform)\n      const p3 = applyMatrix({ x: x + width, y: y + height }, svgTransform)\n      const p4 = applyMatrix({ x, y: y + height }, svgTransform)\n      path += `M ${str(p1)}`\n      path += `L ${str(p2)}`\n      path += `L ${str(p3)}`\n      path += `L ${str(p4)}`\n      path += `z`\n    }\n\n    const result = sketchPath(context, path, parseStyleConfig(context, rect, svgTransform))\n\n    applyPatternPaint(context, rect, transformedBounds)\n    appendSketchElement(context, rect, result)\n  }\n}\n\nfunction applyPatternPaint(\n  context: RenderContext,\n  rect: SVGRectElement,\n  { x, y, w, h }: Rectangle\n): void {\n  appendPatternPaint(context, rect, () => {\n    const proxy = document.createElementNS('http://www.w3.org/2000/svg', 'rect')\n    proxy.x.baseVal.value = x\n    proxy.y.baseVal.value = y\n    proxy.width.baseVal.value = w\n    proxy.height.baseVal.value = h\n    return proxy\n  })\n}\n\nexport function applyRectClip(\n  context: RenderContext,\n  rect: SVGRectElement,\n  container: SVGClipPathElement,\n  svgTransform: SVGTransform | null\n): void {\n  const x = rect.x.baseVal.value\n  const y = rect.y.baseVal.value\n  const width = rect.width.baseVal.value\n  const height = rect.height.baseVal.value\n\n  if (width === 0 || height === 0) {\n    // zero-width or zero-height rect will not be rendered\n    return\n  }\n\n  const rx = rect.hasAttribute('rx') ? rect.rx.baseVal.value : null\n  const ry = rect.hasAttribute('ry') ? rect.ry.baseVal.value : null\n\n  const clip = document.createElementNS('http://www.w3.org/2000/svg', 'rect')\n  clip.x.baseVal.value = x\n  clip.y.baseVal.value = y\n  clip.width.baseVal.value = width\n  clip.height.baseVal.value = height\n  if (rx) {\n    clip.rx.baseVal.value = rx\n  }\n  if (ry) {\n    clip.ry.baseVal.value = ry\n  }\n  applyTransform(context, svgTransform, clip)\n  container.appendChild(clip)\n}\n"
  },
  {
    "path": "src/geom/text.ts",
    "content": "import { getNodeChildren } from '../dom-helpers'\nimport { getEffectiveAttribute } from '../styles/effective-attributes'\nimport { concatStyleStrings } from '../styles/styles'\nimport { convertToPixelUnit } from '../svg-units'\nimport { applyTransform } from '../transformation'\nimport { RenderContext } from '../types'\nimport { SKETCH_CLIP_ATTRIBUTE, appendSketchElement, measureText } from '../utils'\nimport { Size } from './primitives'\n\ntype FontAttributes = Partial<{\n  fontStyle: string\n  fontWeight: string\n  fontSize: string\n  fontFamiliy: string\n}>\n\nexport function drawText(\n  context: RenderContext,\n  text: SVGTextElement,\n  svgTransform: SVGTransform | null\n): void {\n  const container = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n  container.setAttribute('class', 'text-container')\n  applyTransform(context, svgTransform, container)\n  const textClone = text.cloneNode(true) as SVGTextElement\n  if (textClone.transform.baseVal.numberOfItems > 0) {\n    // remove transformation, since it is transformed globally by its parent container\n    textClone.transform.baseVal.clear()\n  }\n\n  // clip-path is applied on the container\n  textClone.removeAttribute('clip-path')\n\n  const { cssFont, fontSize: effectiveFontSize } = getCssFont(context, text, true)\n  textClone.setAttribute('style', concatStyleStrings(textClone.getAttribute('style'), cssFont))\n  copyTextStyleAttributes(context, text, textClone)\n\n  // apply styling to any tspan\n  if (textClone.childElementCount > 0) {\n    const children = getNodeChildren(textClone)\n    const origChildren = getNodeChildren(text) as SVGElement[]\n    for (let i = 0; i < children.length; i++) {\n      const child = children[i]\n      if (child instanceof SVGTSpanElement) {\n        copyTextStyleAttributes(context, origChildren[i] as SVGTSpanElement, child)\n      }\n    }\n  }\n\n  container.appendChild(textClone)\n  appendSketchElement(context, text, container)\n\n  // avoid text clipping by scaling the text when changing the font\n  const useCustomFontFamily = context.fontFamily !== null\n  const hasClipPath = textClone.hasAttribute(SKETCH_CLIP_ATTRIBUTE)\n  if (useCustomFontFamily && hasClipPath && effectiveFontSize) {\n    fitFontSize(context, text, textClone, effectiveFontSize)\n  }\n}\n\n/**\n * Applies a font-size on the clone such that the clone has a smaller width than the original element.\n * Only fits the width because the height is usually no problem wrt. clipping.\n */\nfunction fitFontSize(\n  context: RenderContext,\n  original: SVGTextElement,\n  clone: SVGTextElement,\n  effectiveFontSize: string\n): void {\n  const { width, height } = original.getBBox()\n  if (width <= 0 || height <= 0) {\n    return\n  }\n  const fontSizePx = convertToPixelUnit(context, clone, effectiveFontSize, 'font-size')\n  fitFontSizeCore(context, { w: width, h: height }, clone, fontSizePx)\n}\n\n/**\n * Recursively shrinks the font-size on the element until its width is smaller than the original width.\n */\nfunction fitFontSizeCore(\n  context: RenderContext,\n  originalSize: Size,\n  clone: SVGTextElement,\n  fontSizePx: number\n): void {\n  const STEP_SIZE = 1\n  const { w: cloneWidth } = measureText(context, clone)\n\n  if (cloneWidth < originalSize.w) {\n    // fits original width\n    return\n  }\n\n  if (fontSizePx <= 1) {\n    // already too small\n    return\n  }\n\n  // try a smaller size\n  const newFontSize = fontSizePx - STEP_SIZE\n  clone.style.fontSize = `${newFontSize}px`\n\n  // check again\n  fitFontSizeCore(context, originalSize, clone, newFontSize)\n}\n\n/**\n * @param asStyleString Formats the return value as inline style string\n */\nfunction getCssFont(\n  context: RenderContext,\n  text: SVGTextElement,\n  asStyleString: boolean = false\n): FontAttributes & { cssFont: string } {\n  const effectiveAttributes: FontAttributes = {}\n\n  let cssFont = ''\n  const fontStyle = getEffectiveAttribute(context, text, 'font-style', context.useElementContext)\n  if (fontStyle) {\n    cssFont += asStyleString ? `font-style: ${fontStyle};` : fontStyle\n    effectiveAttributes.fontStyle = fontStyle\n  }\n  const fontWeight = getEffectiveAttribute(context, text, 'font-weight', context.useElementContext)\n  if (fontWeight) {\n    cssFont += asStyleString ? `font-weight: ${fontWeight};` : ` ${fontWeight}`\n    effectiveAttributes.fontWeight = fontWeight\n  }\n  const fontSize = getEffectiveAttribute(context, text, 'font-size', context.useElementContext)\n  if (fontSize) {\n    cssFont += asStyleString ? `font-size: ${fontSize};` : ` ${fontSize}`\n    effectiveAttributes.fontSize = fontSize\n  }\n  if (context.fontFamily) {\n    cssFont += asStyleString ? `font-family: ${context.fontFamily};` : ` ${context.fontFamily}`\n    effectiveAttributes.fontFamiliy = context.fontFamily\n  } else {\n    const fontFamily = getEffectiveAttribute(\n      context,\n      text,\n      'font-family',\n      context.useElementContext\n    )\n    if (fontFamily) {\n      cssFont += asStyleString ? `font-family: ${fontFamily};` : ` ${fontFamily}`\n      effectiveAttributes.fontFamiliy = fontFamily\n    }\n  }\n\n  cssFont = cssFont.trim()\n  return { ...effectiveAttributes, cssFont }\n}\n\nfunction copyTextStyleAttributes(\n  context: RenderContext,\n  srcElement: SVGTextElement | SVGTSpanElement,\n  tgtElement: SVGTextElement | SVGTSpanElement\n): void {\n  const stroke = getEffectiveAttribute(context, srcElement, 'stroke')\n  const strokeWidth = stroke ? getEffectiveAttribute(context, srcElement, 'stroke-width') : null\n  const fill = getEffectiveAttribute(context, srcElement, 'fill')\n  const dominantBaseline = getEffectiveAttribute(context, srcElement, 'dominant-baseline')\n  const textAnchor = getEffectiveAttribute(\n    context,\n    srcElement,\n    'text-anchor',\n    context.useElementContext\n  )\n\n  if (stroke) {\n    tgtElement.setAttribute('stroke', stroke)\n  }\n  if (strokeWidth) {\n    tgtElement.setAttribute('stroke-width', strokeWidth)\n  }\n  if (fill) {\n    tgtElement.setAttribute('fill', fill)\n  }\n  if (textAnchor) {\n    tgtElement.setAttribute('text-anchor', textAnchor)\n  }\n  if (dominantBaseline) {\n    tgtElement.setAttribute('dominant-baseline', dominantBaseline)\n  }\n}\n"
  },
  {
    "path": "src/geom/use.ts",
    "content": "import { getCombinedTransform } from '../transformation'\nimport { RenderContext } from '../types'\n\nexport function drawUse(\n  context: RenderContext,\n  use: SVGUseElement,\n  svgTransform: SVGTransform | null\n): void {\n  let href = use.href.baseVal\n  if (href.startsWith('#')) {\n    href = href.substring(1)\n  }\n  const defElement = context.idElements[href] as SVGElement\n  if (defElement) {\n    let useWidth, useHeight\n    if (use.getAttribute('width') && use.getAttribute('height')) {\n      // Use elements can overwrite the width which is important if it is a nested SVG\n      useWidth = use.width.baseVal.value\n      useHeight = use.height.baseVal.value\n    }\n    // We need to account for x and y attributes as well. Those change where the element is drawn.\n    // We can simply change the transform to include that.\n    const x = use.x.baseVal.value\n    const y = use.y.baseVal.value\n    let matrix = context.sourceSvg.createSVGMatrix().translate(x, y)\n    matrix = svgTransform ? svgTransform.matrix.multiply(matrix) : matrix\n\n    // the defsElement itself might have a transform that needs to be incorporated\n    const elementTransform = context.sourceSvg.createSVGTransformFromMatrix(matrix)\n\n    // use elements must be processed in their context, particularly regarding\n    // the styling of them\n    if (!context.useElementContext) {\n      context.useElementContext = { root: use, referenced: defElement, parentContext: null }\n    } else {\n      const newContext = {\n        root: use,\n        referenced: defElement,\n        parentContext: Object.assign({}, context.useElementContext)\n      }\n      context.useElementContext = newContext\n    }\n\n    // draw the referenced element\n    context.processElement(\n      context,\n      defElement,\n      getCombinedTransform(context, defElement as SVGGraphicsElement, elementTransform),\n      useWidth,\n      useHeight\n    )\n\n    // restore default context\n    if (context.useElementContext.parentContext) {\n      context.useElementContext = context.useElementContext.parentContext\n    } else {\n      context.useElementContext = null\n    }\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "export * from './Svg2Roughjs'\nexport * from './OutputType'\n"
  },
  {
    "path": "src/processor.ts",
    "content": "import { applyClipPath } from './clipping'\nimport { getNodeChildren } from './dom-helpers'\nimport { drawCircle } from './geom/circle'\nimport { drawEllipse } from './geom/ellipse'\nimport { drawForeignObject } from './geom/foreign-object'\nimport { drawImage } from './geom/image'\nimport { drawLine } from './geom/line'\nimport { drawPath } from './geom/path'\nimport { drawPolygon } from './geom/polygon'\nimport { drawPolyline } from './geom/polyline'\nimport { Rectangle } from './geom/primitives'\nimport { drawRect } from './geom/rect'\nimport { drawText } from './geom/text'\nimport { drawUse } from './geom/use'\nimport { isHidden } from './styles/styles'\nimport { getCombinedTransform } from './transformation'\nimport { RenderContext } from './types'\n\n/**\n * Traverses the SVG in DFS and draws each element to the canvas.\n * @param root either an SVG- or g-element\n * @param width Use elements can overwrite width\n * @param height Use elements can overwrite height\n */\nexport function processRoot(\n  context: RenderContext,\n  root: SVGSVGElement | SVGGElement | SVGSymbolElement | SVGMarkerElement | SVGElement,\n  svgTransform: SVGTransform | null,\n  width?: number,\n  height?: number\n): void {\n  // traverse svg in DFS\n  const stack: { element: SVGElement; transform: SVGTransform | null; viewBox: Rectangle }[] = []\n\n  const currentViewBox: Rectangle = { x: 0, y: 0, w: width ?? 0, h: height ?? 0 }\n\n  if (\n    root instanceof SVGSVGElement ||\n    root instanceof SVGSymbolElement ||\n    root instanceof SVGMarkerElement\n  ) {\n    let rootX = 0\n    let rootY = 0\n    if (root instanceof SVGSymbolElement) {\n      rootX = parseFloat(root.getAttribute('x') ?? '') || 0\n      rootY = parseFloat(root.getAttribute('y') ?? '') || 0\n      width = width ?? (parseFloat(root.getAttribute('width')!) || void 0)\n      height = height ?? (parseFloat(root.getAttribute('height')!) || void 0)\n    } else if (root instanceof SVGMarkerElement) {\n      // markers use refX / refY which is applied after user-space transformation\n      const mw = root.getAttribute('markerWidth')\n      const mh = root.getAttribute('markerHeight')\n      width = mw !== null ? parseFloat(mw) : 3 // marker-size is 3 by SVG spec\n      height = mh !== null ? parseFloat(mh) : 3\n    } else if (root !== context.sourceSvg) {\n      // apply translation of nested elements\n      rootX = root.x.baseVal.value\n      rootY = root.y.baseVal.value\n    }\n\n    let rootTransform = context.sourceSvg.createSVGMatrix()\n\n    if (root.getAttribute('viewBox')) {\n      const {\n        x: viewBoxX,\n        y: viewBoxY,\n        width: viewBoxWidth,\n        height: viewBoxHeight\n      } = root.viewBox.baseVal\n\n      currentViewBox.x = viewBoxX\n      currentViewBox.y = viewBoxY\n      currentViewBox.w = viewBoxWidth\n      currentViewBox.h = viewBoxHeight\n\n      if (typeof width !== 'undefined' && typeof height !== 'undefined') {\n        // viewBox values might scale the SVGs content\n        const sx = width / viewBoxWidth\n        const sy = height / viewBoxHeight\n        const centerviewportX = rootX + width * 0.5\n        const centerviewportY = rootY + height * 0.5\n        const centerViewBoxX = viewBoxX + viewBoxWidth * 0.5\n        const centerViewBoxY = viewBoxY + viewBoxHeight * 0.5\n        // only support scaling from the center, e.g. xMidYMid\n        rootTransform = rootTransform.translate(centerviewportX, centerviewportY)\n        if (root.getAttribute('preserveAspectRatio') === 'none') {\n          rootTransform = rootTransform.scaleNonUniform(sx, sy)\n        } else {\n          rootTransform = rootTransform.scale(Math.min(sx, sy))\n        }\n        rootTransform = rootTransform.translate(-centerViewBoxX, -centerViewBoxY)\n      }\n    } else {\n      rootTransform = rootTransform.translate(rootX, rootY)\n    }\n\n    if (root instanceof SVGMarkerElement) {\n      // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/refX#symbol\n      // ref coordinates are interpreted as being in the coordinate system of the element contents,\n      // after application of the viewBox and preserveAspectRatio attributes.\n      rootTransform = rootTransform.translate(-root.refX.baseVal.value, -root.refY.baseVal.value)\n    }\n\n    const combinedMatrix = svgTransform\n      ? svgTransform.matrix.multiply(rootTransform)\n      : rootTransform\n    svgTransform = context.sourceSvg.createSVGTransformFromMatrix(combinedMatrix)\n\n    // don't put the SVG itself into the stack, so start with the children of it\n    const children = getNodeChildren(root)\n    for (let i = children.length - 1; i >= 0; i--) {\n      const child = children[i] as SVGGraphicsElement\n      if (child instanceof SVGSymbolElement || child instanceof SVGMarkerElement) {\n        // symbols and marker can only be instantiated by specific elements\n        continue\n      }\n      const childTransform = getCombinedTransform(context, child, svgTransform)\n      stack.push({ element: child, transform: childTransform, viewBox: currentViewBox })\n    }\n  } else {\n    stack.push({ element: root, transform: svgTransform, viewBox: currentViewBox })\n  }\n\n  while (stack.length > 0) {\n    const { element, transform, viewBox } = stack.pop()!\n\n    // maybe draw the element\n    try {\n      context.viewBox = viewBox\n      drawElement(context, element, transform)\n    } catch (e) {\n      console.error(e)\n    }\n\n    if (\n      element.tagName === 'defs' ||\n      element.tagName === 'symbol' ||\n      element.tagName === 'marker' ||\n      element.tagName === 'svg' ||\n      element.tagName === 'clipPath'\n    ) {\n      // Defs are prepocessed separately.\n      // Symbols and marker can only be instantiated by specific elements.\n      // Don't traverse the SVG element itself. This is done by drawElement -> processRoot.\n      // ClipPaths are not drawn and processed separately.\n      continue\n    }\n    // process children\n    const children = getNodeChildren(element)\n    for (let i = children.length - 1; i >= 0; i--) {\n      const childElement = children[i] as SVGGraphicsElement\n      const newTransform = getCombinedTransform(context, childElement, transform)\n      stack.push({ element: childElement, transform: newTransform, viewBox })\n    }\n  }\n}\n\nexport function drawRoot(\n  context: RenderContext,\n  element: SVGSVGElement | SVGSymbolElement,\n  svgTransform: SVGTransform | null\n): void {\n  let width: number | undefined = parseFloat(element.getAttribute('width')!)\n  let height: number | undefined = parseFloat(element.getAttribute('height')!)\n  if (isNaN(width) || isNaN(height)) {\n    // use only if both are set\n    width = height = undefined\n  }\n  processRoot(context, element, svgTransform, width, height)\n}\n\n/**\n * The main switch to delegate drawing of `SVGElement`s\n * to different subroutines.\n */\nfunction drawElement(\n  context: RenderContext,\n  element: SVGElement,\n  svgTransform: SVGTransform | null\n): void {\n  if (isHidden(element)) {\n    // just skip hidden elements\n    return\n  }\n\n  // possibly apply a clip on the canvas before drawing on it\n  const clipPath = element.getAttribute('clip-path')\n  if (clipPath) {\n    applyClipPath(context, element, clipPath, svgTransform)\n  }\n\n  switch (element.tagName) {\n    case 'svg':\n    case 'symbol':\n      drawRoot(context, element as SVGSVGElement | SVGSymbolElement, svgTransform)\n      break\n    case 'rect':\n      drawRect(context, element as SVGRectElement, svgTransform)\n      break\n    case 'path':\n      drawPath(context, element as SVGPathElement, svgTransform)\n      break\n    case 'use':\n      drawUse(context, element as SVGUseElement, svgTransform)\n      break\n    case 'line':\n      drawLine(context, element as SVGLineElement, svgTransform)\n      break\n    case 'circle':\n      drawCircle(context, element as SVGCircleElement, svgTransform)\n      break\n    case 'ellipse':\n      drawEllipse(context, element as SVGEllipseElement, svgTransform)\n      break\n    case 'polyline':\n      drawPolyline(context, element as SVGPolylineElement, svgTransform)\n      break\n    case 'polygon':\n      drawPolygon(context, element as SVGPolygonElement, svgTransform)\n      break\n    case 'text':\n      drawText(context, element as SVGTextElement, svgTransform)\n      break\n    case 'image':\n      drawImage(context, element as SVGImageElement, svgTransform)\n      break\n    case 'foreignObject':\n      drawForeignObject(context, element as SVGForeignObjectElement, svgTransform)\n      break\n  }\n}\n"
  },
  {
    "path": "src/styles/colors.ts",
    "content": "import tinycolor, { Instance as TinyColorInstance } from 'tinycolor2'\n\n/**\n * Converts an SVG gradient to a color by mixing all stop colors\n * with `tinycolor.mix`.\n */\nexport function gradientToColor(\n  gradient: SVGLinearGradientElement | SVGRadialGradientElement,\n  opacity: number\n): string {\n  const stops = Array.prototype.slice.apply(gradient.querySelectorAll('stop'))\n  if (stops.length === 0) {\n    return 'transparent'\n  } else if (stops.length === 1) {\n    const color = getStopColor(stops[0])\n    color.setAlpha(opacity)\n    return color.toString()\n  } else {\n    // Because roughjs can only deal with solid colors, we try to calculate\n    // the average color of the gradient here.\n    // The idea is to create an array of discrete (average) colors that represents the\n    // gradient under consideration of the stop's offset. Thus, larger offsets\n    // result in more entries of the same mixed color (of the two adjacent color stops).\n    // At the end, this array is averaged again, to create a single solid color.\n    const resolution = 10\n    const discreteColors: TinyColorInstance[] = []\n\n    let lastColor: TinyColorInstance | null = null\n    for (let i = 0; i < stops.length; i++) {\n      const currentColor = getStopColor(stops[i])\n      const currentOffset = getStopOffset(stops[i])\n\n      // combine the adjacent colors\n      const combinedColor = lastColor ? averageColor([lastColor, currentColor]) : currentColor\n\n      // fill the discrete color array depending on the offset size\n      let entries = Math.max(1, (currentOffset / resolution) | 0)\n      while (entries > 0) {\n        discreteColors.push(combinedColor)\n        entries--\n      }\n\n      lastColor = currentColor\n    }\n\n    // average the discrete colors again for the final result\n    const mixedColor = averageColor(discreteColors)\n    mixedColor.setAlpha(opacity)\n    return mixedColor.toString()\n  }\n}\n\n/**\n * Returns the `stop-color` of an `SVGStopElement`.\n */\nexport function getStopColor(stop: SVGStopElement): TinyColorInstance {\n  let stopColorStr = stop.getAttribute('stop-color')\n  if (!stopColorStr) {\n    const style = stop.getAttribute('style') ?? ''\n    const match = /stop-color:\\s?(.*);?/.exec(style)\n    if (match && match.length > 1) {\n      stopColorStr = match[1]\n    }\n  }\n  return stopColorStr ? tinycolor(stopColorStr) : tinycolor('white')\n}\n\n/**\n * Calculates the average color of the colors in the given array.\n * @returns The average color\n */\nexport function averageColor(colorArray: TinyColorInstance[]): TinyColorInstance {\n  const count = colorArray.length\n  let r = 0\n  let g = 0\n  let b = 0\n  let a = 0\n  colorArray.forEach(tinycolor => {\n    const color = tinycolor.toRgb()\n    r += color.r * color.r\n    g += color.g * color.g\n    b += color.b * color.b\n    a += color.a\n  })\n  return tinycolor({\n    r: Math.sqrt(r / count),\n    g: Math.sqrt(g / count),\n    b: Math.sqrt(b / count),\n    a: a / count\n  })\n}\n\n/**\n * Returns the `offset` of an `SVGStopElement`.\n * @return stop percentage\n */\nexport function getStopOffset(stop: SVGStopElement): number {\n  const offset = stop.getAttribute('offset')\n  if (!offset) {\n    return 0\n  }\n  if (offset.indexOf('%')) {\n    return parseFloat(offset.substring(0, offset.length - 1))\n  } else {\n    return parseFloat(offset) * 100\n  }\n}\n"
  },
  {
    "path": "src/styles/effective-attributes.ts",
    "content": "import { getParentElement } from '../dom-helpers'\nimport { RenderContext, UseContext } from '../types'\n\n/**\n * Returns the attribute value of an element under consideration\n * of inherited attributes from the `parentElement`.\n * @param attributeName Name of the attribute to look up\n * @param currentUseCtx Consider different DOM hierarchy for use elements\n * @return attribute value if it exists\n */\n\nexport function getEffectiveAttribute(\n  context: RenderContext,\n  element: SVGElement,\n  attributeName: string,\n  currentUseCtx?: UseContext | null\n): string | undefined {\n  // getComputedStyle doesn't work for, e.g. <svg fill='rgba(...)'>\n  let attr\n  if (!currentUseCtx) {\n    attr =\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      (getComputedStyle(element) as any)[attributeName] || element.getAttribute(attributeName)\n  } else {\n    // use elements traverse a different parent-hierarchy, thus we cannot use getComputedStyle here\n    attr = element.getAttribute(attributeName)\n  }\n\n  if (!attr) {\n    let parent: Node | null = getParentElement(element)\n\n    const useCtx = currentUseCtx\n    let nextCtx = useCtx\n\n    if (useCtx && useCtx.referenced === element) {\n      // switch context and traverse the use-element parent now\n      parent = useCtx.root\n      nextCtx = useCtx.parentContext\n    }\n\n    if (!parent || parent === context.sourceSvg) {\n      return\n    }\n    return getEffectiveAttribute(context, parent as SVGElement, attributeName, nextCtx)\n  }\n  return attr\n}\n\n/**\n * Traverses the given elements hierarchy bottom-up to determine its effective\n * opacity attribute.\n * @param currentUseCtx Consider different DOM hierarchy for use elements\n */\nexport function getEffectiveElementOpacity(\n  context: RenderContext,\n  element: SVGElement,\n  currentOpacity: number,\n  currentUseCtx?: UseContext | null\n): number {\n  let attr: string | null\n  if (!currentUseCtx) {\n    attr = getComputedStyle(element)['opacity'] || element.getAttribute('opacity')\n  } else {\n    // use elements traverse a different parent-hierarchy, thus we cannot use getComputedStyle here\n    attr = element.getAttribute('opacity')\n  }\n  if (attr) {\n    let elementOpacity: number\n    if (attr.indexOf('%') !== -1) {\n      elementOpacity = Math.min(\n        1,\n        Math.max(0, parseFloat(attr.substring(0, attr.length - 1)) / 100)\n      )\n    } else {\n      elementOpacity = Math.min(1, Math.max(0, parseFloat(attr)))\n    }\n    // combine opacities\n    currentOpacity *= elementOpacity\n  }\n  // traverse upwards to combine parent opacities as well\n  let parent: Node | null = getParentElement(element)\n\n  const useCtx = currentUseCtx\n  let nextUseCtx = useCtx\n\n  if (useCtx && useCtx.referenced === element) {\n    // switch context and traverse the use-element parent now\n    parent = useCtx.root\n    nextUseCtx = useCtx.parentContext\n  }\n\n  if (!parent || parent === context.sourceSvg) {\n    return currentOpacity\n  }\n\n  return getEffectiveElementOpacity(context, parent as SVGElement, currentOpacity, nextUseCtx)\n}\n"
  },
  {
    "path": "src/styles/pattern.ts",
    "content": "import { getIdFromUrl, reparentNodes } from '../dom-helpers'\nimport { RenderContext } from '../types'\nimport { appendSketchElement, getDefsElement, sketchFragment } from '../utils'\nimport { getEffectiveAttribute } from './effective-attributes'\n\n/**\n * If the input element has a pattern stroke/fill, an additional element is added to the result,\n * which just provides the pattern storke/fill.\n * @param patternProxyCreator Should return the transformed `SVGElement` that holds the stroke/fill pattern.\n */\nexport function appendPatternPaint(\n  context: RenderContext,\n  sourceElement: SVGElement,\n  patternProxyCreator: () => SVGElement\n): void {\n  const { fillId, strokeId } = getPatternPaintIds(context, sourceElement)\n  if (fillId !== null || strokeId !== null) {\n    // the additional element that should provide the pattern\n    const patternProxy = patternProxyCreator()\n    patternProxy.setAttribute('fill', fillId !== null ? `url(#${fillId})` : 'none')\n    patternProxy.setAttribute('stroke', strokeId !== null ? `url(#${strokeId})` : 'none')\n\n    const strokeWidth = getEffectiveAttribute(\n      context,\n      sourceElement,\n      'stroke-width',\n      context.useElementContext\n    )\n    patternProxy.setAttribute('stroke-width', strokeWidth ?? '0')\n\n    // append the proxy\n    appendSketchElement(context, sourceElement, patternProxy)\n\n    // add the pattern defs\n    appendPatternDefsElement(context, fillId)\n    appendPatternDefsElement(context, strokeId)\n  }\n}\n\n/**\n * Returns the element's referenced fill and stroke pattern ids if there are any.\n */\nfunction getPatternPaintIds(\n  context: RenderContext,\n  element: SVGElement\n): { fillId: string | null; strokeId: string | null } {\n  function getPatternId(attributeName: string): string | null {\n    const attr = element.getAttribute(attributeName)\n    if (attr && attr.indexOf('url') !== -1) {\n      const id = getIdFromUrl(attr)\n      if (id) {\n        const paint = context.idElements[id]\n        if (paint instanceof SVGPatternElement) {\n          return id\n        }\n      }\n    }\n    return null\n  }\n  return { fillId: getPatternId('fill'), strokeId: getPatternId('stroke') }\n}\n\n/**\n * Obtains the pattern fill element from the source SVG and provides it as defs element\n * in the output sketch element if missing.\n */\nfunction appendPatternDefsElement(context: RenderContext, patternId: string | null): void {\n  if (patternId === null) {\n    return\n  }\n\n  const sketchDefs = getDefsElement(context)\n  const defId = `#${patternId}`\n  if (!sketchDefs.querySelector(defId)) {\n    const sourceDefElement = context.sourceSvg.querySelector(defId) as SVGPatternElement\n    if (sourceDefElement) {\n      if (!context.sketchPatterns) {\n        // just copy the pattern to the output\n        sketchDefs.appendChild(sourceDefElement.cloneNode(true))\n        return\n      }\n\n      // create a proxy for the pattern element to be sketched separately\n      const patternElement = reparentNodes(\n        document.createElementNS('http://www.w3.org/2000/svg', 'g'),\n        sourceDefElement.cloneNode(true) as SVGPatternElement\n      )\n\n      // sketch the pattern separately from the main processor loop\n      const sketchPattern = sketchFragment(context, patternElement, {\n        // patterns usually don't benefit from too crazy sketch values due to their base-size\n        fillStyle: 'solid',\n        roughness: 0.5 // TODO ideally this should scale with the pattern's size\n      })\n\n      // move the result into an copy of the original def element\n      const defElementRoot = sourceDefElement.cloneNode() as SVGPatternElement\n      sketchDefs.appendChild(reparentNodes(defElementRoot, sketchPattern))\n    }\n  }\n}\n"
  },
  {
    "path": "src/styles/pens.ts",
    "content": "import { RenderContext } from '../types'\n\ntype Range = [number, number]\ntype AngleConfig = { normal: Range; horizontal: Range; vertical: Range }\ntype WeightConfig = { normal: Range; small: Range }\ntype GapConfig = { normal: Range; small: Range }\ntype PenConfiguration = { angle: AngleConfig; weight: WeightConfig; gap: GapConfig }\nexport type Pen = { angle: number; weight: number; gap: number }\n\nfunction getPenConfiguration(fillStyle?: string): PenConfiguration {\n  // the svg2roughjs v2 config\n  const legacyConfig: PenConfiguration = {\n    angle: {\n      normal: [-30, -50],\n      horizontal: [-30, -50],\n      vertical: [-30, -50]\n    },\n    weight: {\n      normal: [0.5, 3],\n      small: [0.5, 3]\n    },\n    gap: {\n      normal: [3, 5],\n      small: [3, 5]\n    }\n  }\n\n  // adjusted config for more variation\n  const defaultConfig: PenConfiguration = {\n    angle: {\n      // just lean more into the direction of the aspect ratio\n      normal: [-30, -50],\n      horizontal: [-50, -75],\n      vertical: [-30, -15]\n    },\n    weight: {\n      normal: [1, 3],\n      small: [0.5, 1.7]\n    },\n    gap: {\n      normal: [2, 5],\n      small: [1, 3]\n    }\n  }\n\n  // fine-tune configs depending on fill-style\n  switch (fillStyle) {\n    default:\n      return defaultConfig\n    case 'zigzag':\n    case 'zigzag-line':\n      return {\n        ...defaultConfig,\n        weight: { normal: [0.5, 3], small: [0.5, 2] },\n        gap: { normal: [2, 6], small: [2, 5] }\n      }\n    case 'cross-hatch':\n      return {\n        ...defaultConfig,\n        weight: { normal: [1, 3], small: [0.5, 1.3] },\n        gap: { normal: [4, 8], small: [2, 5] }\n      }\n    case 'dots':\n      return legacyConfig\n  }\n}\n\n/**\n * Creates a random rendering configuration for the given element.\n * The returned pen is specific of the `config.fillStyle` and the element's shape.\n */\nexport function createPen(context: RenderContext, element: SVGElement): Pen {\n  if (context.roughConfig.fillStyle === 'solid') {\n    // config doesn't affect drawing\n    return { angle: 0, gap: 0, weight: 0 }\n  }\n\n  // Only works when the element is in the DOM, but no need to check it here,\n  // since the related methods can cope with non-finite or zero cases.\n  const { width, height } = element.getBoundingClientRect()\n  const aspectRatio = width / height\n  const sideLength = Math.sqrt(width * height)\n\n  const { angle, gap, weight } = getPenConfiguration(context.roughConfig.fillStyle)\n  return {\n    angle: getHachureAngle(context, angle, aspectRatio),\n    gap: getHachureGap(context, gap, sideLength),\n    weight: getFillWeight(context, weight, sideLength)\n  }\n}\n\n/**\n * Returns a random hachure angle in the range of the given config.\n *\n * Rough.js default is -41deg\n */\nfunction getHachureAngle(\n  { rng }: RenderContext,\n  { normal, horizontal, vertical }: AngleConfig,\n  aspectRatio: number\n): number {\n  if (isFinite(aspectRatio)) {\n    // sketch elements along the smaller side\n    if (aspectRatio < 0.25) {\n      return rng.next(horizontal)\n    } else if (aspectRatio > 6) {\n      return rng.next(vertical)\n    }\n  }\n  return rng.next(normal)\n}\n\n/**\n * Returns a random hachure gap in the range of the given config.\n *\n * Rough.js default is 4 * strokeWidth\n */\nfunction getHachureGap(\n  { rng }: RenderContext,\n  { normal, small }: GapConfig,\n  sideLength: number\n): number {\n  return sideLength < 45 ? rng.next(small) : rng.next(normal)\n}\n\n/**\n * Returns a random fill weight in the range of the given config.\n *\n * Rough.js default is 0.5 * strokeWidth\n */\nfunction getFillWeight(\n  { rng }: RenderContext,\n  { normal, small }: WeightConfig,\n  sideLength: number\n): number {\n  return sideLength < 45 ? rng.next(small) : rng.next(normal)\n}\n"
  },
  {
    "path": "src/styles/styles.ts",
    "content": "import { Options } from 'roughjs/bin/core'\nimport tinycolor from 'tinycolor2'\nimport { getIdFromUrl } from '../dom-helpers'\nimport { convertToPixelUnit } from '../svg-units'\nimport { isIdentityTransform } from '../transformation'\nimport { RenderContext } from '../types'\nimport { gradientToColor } from './colors'\nimport { getEffectiveAttribute, getEffectiveElementOpacity } from './effective-attributes'\nimport { createPen } from './pens'\n\n/**\n * Converts the effective style attributes of the given `SVGElement`\n * to a Rough.js config object that is used to draw the element with\n * Rough.js.\n * @return config for Rough.js drawing\n */\nexport function parseStyleConfig(\n  context: RenderContext,\n  element: SVGElement,\n  svgTransform: SVGTransform | null\n): Options {\n  const precision = context.roughConfig.fixedDecimalPlaceDigits ?? 15\n  const config = Object.assign({}, context.roughConfig)\n\n  // Scalefactor for certain style attributes. For lack of a better option here, use the determinant\n  let scaleFactor = 1\n  if (!isIdentityTransform(svgTransform)) {\n    const m = svgTransform!.matrix\n    const det = m.a * m.d - m.c * m.b\n    scaleFactor = Math.sqrt(Math.abs(det))\n  }\n\n  // incorporate the elements base opacity\n  const elementOpacity = getEffectiveElementOpacity(context, element, 1, context.useElementContext)\n\n  const fill = getEffectiveAttribute(context, element, 'fill', context.useElementContext) || 'black'\n  const fillOpacity = elementOpacity * getOpacity(element, 'fill-opacity')\n  if (fill) {\n    if (fill.indexOf('url') !== -1) {\n      const gradientColor = convertGradient(context, fill, fillOpacity)\n      if (gradientColor !== 'none') {\n        config.fill = gradientColor\n      } else {\n        // delete fill, otherwise it may create an invisible 'hachure' element\n        delete config.fill\n      }\n    } else if (fill === 'none') {\n      // delete fill, otherwise it may create an invisible 'hachure' element\n      delete config.fill\n    } else {\n      const color = tinycolor(fill)\n      color.setAlpha(fillOpacity)\n      config.fill = color.toString()\n    }\n  }\n\n  const stroke = getEffectiveAttribute(context, element, 'stroke', context.useElementContext)\n  const strokeOpacity = elementOpacity * getOpacity(element, 'stroke-opacity')\n  if (stroke) {\n    if (stroke.indexOf('url') !== -1) {\n      config.stroke = convertGradient(context, stroke, strokeOpacity)\n    } else if (stroke === 'none') {\n      config.stroke = 'none'\n    } else {\n      const color = tinycolor(stroke)\n      color.setAlpha(strokeOpacity)\n      config.stroke = color.toString()\n    }\n  } else {\n    config.stroke = 'none'\n  }\n\n  const strokeWidth = getEffectiveAttribute(\n    context,\n    element,\n    'stroke-width',\n    context.useElementContext\n  )\n  if (strokeWidth) {\n    // Convert to user space units (px)\n    const scaledWidth =\n      convertToPixelUnit(context, element, strokeWidth, 'stroke-width') * scaleFactor\n    config.strokeWidth = parseFloat(scaledWidth.toFixed(precision))\n  } else {\n    // default stroke-width is 1\n    config.strokeWidth = 1\n  }\n\n  const strokeDashArray = getEffectiveAttribute(\n    context,\n    element,\n    'stroke-dasharray',\n    context.useElementContext\n  )\n  if (strokeDashArray && strokeDashArray !== 'none') {\n    config.strokeLineDash = strokeDashArray\n      .split(/[\\s,]+/)\n      .filter(entry => entry.length > 0)\n      // make sure that dashes/dots are at least somewhat visible\n      .map(dash => {\n        const scaledLineDash =\n          convertToPixelUnit(context, element, dash, 'stroke-dasharray') * scaleFactor\n        return Math.max(0.5, parseFloat(scaledLineDash.toFixed(precision)))\n      })\n  }\n\n  const strokeDashOffset = getEffectiveAttribute(\n    context,\n    element,\n    'stroke-dashoffset',\n    context.useElementContext\n  )\n  if (strokeDashOffset) {\n    const scaledOffset =\n      convertToPixelUnit(context, element, strokeDashOffset, 'stroke-dashoffset') * scaleFactor\n    config.strokeLineDashOffset = parseFloat(scaledOffset.toFixed(precision))\n  }\n\n  // unstroked but filled shapes look weird, so always apply a stroke if we fill something\n  if (config.fill && config.stroke === 'none') {\n    config.stroke = config.fill\n    config.strokeWidth = 1\n  }\n\n  if (context.randomize) {\n    const { angle, gap, weight } = createPen(context, element)\n    config.hachureAngle = angle\n    config.hachureGap = Math.round(gap) // must be integer (avg gap in pixels)\n    config.fillWeight = parseFloat(weight.toFixed(precision)) // value is used in the sketched output as-is\n    // randomize double stroke effect if not explicitly set through user config\n    if (typeof config.disableMultiStroke === 'undefined') {\n      config.disableMultiStroke = context.rng.next() > 0.3\n    }\n  }\n\n  return config\n}\n\n/**\n * Converts SVG opacity attributes to a [0, 1] range.\n */\nexport function getOpacity(element: SVGElement, attribute: string): number {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const attr = (getComputedStyle(element) as any)[attribute] || element.getAttribute(attribute)\n  if (attr) {\n    if (attr.indexOf('%') !== -1) {\n      return Math.min(1, Math.max(0, parseFloat(attr.substring(0, attr.length - 1)) / 100))\n    }\n    return Math.min(1, Math.max(0, parseFloat(attr)))\n  }\n  return 1\n}\n\n/**\n * Parses a `fill` url by looking in the SVG `defs` element.\n * When a gradient is found, it is converted to a color and stored\n * in the internal defs store for this url.\n *\n * Patterns are ignored and returned with 'none'.\n *\n * @returns The parsed color\n */\nexport function convertGradient(context: RenderContext, url: string, opacity: number): string {\n  const id = getIdFromUrl(url)\n  if (!id) {\n    return 'none'\n  }\n\n  const paint = context.idElements[id]\n  if (!paint) {\n    return 'none'\n  }\n\n  if (typeof paint === 'string') {\n    // maybe it was already parsed and replaced with a color\n    return paint\n  } else if (\n    paint instanceof SVGLinearGradientElement ||\n    paint instanceof SVGRadialGradientElement\n  ) {\n    const color = gradientToColor(paint, opacity)\n    context.idElements[id] = color\n    return color\n  } else {\n    // pattern or something else that cannot be directly used in the roughjs config\n    return 'none'\n  }\n}\n\nexport function isHidden(element: SVGElement): boolean {\n  const style = element.style\n  if (!style) {\n    return false\n  }\n  return style.display === 'none' || style.visibility === 'hidden'\n}\n\nexport function concatStyleStrings(...args: (string | null)[]): string {\n  let ret = ''\n  args = args.filter(s => s !== null)\n  for (const style of args) {\n    if (ret.length > 0 && ret[ret.length - 1] !== ';') {\n      ret += ';'\n    }\n    ret += style\n  }\n  return ret\n}\n"
  },
  {
    "path": "src/styles/textures.ts",
    "content": "export function createPencilFilter(): SVGFilterElement {\n  const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter')\n  filter.setAttribute('id', 'pencilTextureFilter')\n  filter.setAttribute('x', '0%')\n  filter.setAttribute('y', '0%')\n  filter.setAttribute('width', '100%')\n  filter.setAttribute('height', '100%')\n  filter.setAttribute('filterUnits', 'objectBoundingBox')\n\n  const feTurbulence = document.createElementNS('http://www.w3.org/2000/svg', 'feTurbulence')\n  feTurbulence.setAttribute('type', 'fractalNoise')\n  feTurbulence.setAttribute('baseFrequency', '2')\n  feTurbulence.setAttribute('numOctaves', '5')\n  feTurbulence.setAttribute('stitchTiles', 'stitch')\n  feTurbulence.setAttribute('result', 'f1')\n  filter.appendChild(feTurbulence)\n\n  const feColorMatrix = document.createElementNS('http://www.w3.org/2000/svg', 'feColorMatrix')\n  feColorMatrix.setAttribute('type', 'matrix')\n  feColorMatrix.setAttribute('values', '0 0 0 0 0, 0 0 0 0 0, 0 0 0 0 0, 0 0 0 -1.5 1.5')\n  feColorMatrix.setAttribute('result', 'f2')\n  filter.appendChild(feColorMatrix)\n\n  const feComposite = document.createElementNS('http://www.w3.org/2000/svg', 'feComposite')\n  feComposite.setAttribute('operator', 'in')\n  feComposite.setAttribute('in', 'SourceGraphic')\n  feComposite.setAttribute('in2', 'f2')\n  feComposite.setAttribute('result', 'f3')\n  filter.appendChild(feComposite)\n\n  return filter\n}\n"
  },
  {
    "path": "src/svg-units.ts",
    "content": "import { getParentElement } from './dom-helpers'\nimport { Size } from './geom/primitives'\nimport { getEffectiveAttribute } from './styles/effective-attributes'\nimport { RenderContext } from './types'\n\n/**\n * Dimension parsing regexp.\n *\n * https://www.w3.org/TR/css3-values/#numbers\n * \"a number is either an integer, or zero or more decimal digits\n * followed by a dot (.) followed by one or more decimal digits and\n * optionally an exponent composed of \"e\" or \"E\" and an integer.\"\n *\n * Don't forget the signs though...\n * => ([+-]?(?:\\d+|\\d*\\.\\d+(?:[eE][+-]?\\d+)?))\n *\n * To get the unit, itself, just allow any alphabetic sequence and the '%' char.\n * => ([a-z]*|%)\n */\nconst DIMENSION_REGEX = /^([+-]?(?:\\d+|\\d*\\.\\d+(?:[eE][+-]?\\d+)?))([a-z]*|%)$/\n\n/**\n * Commonly used dpi for unit conversion.\n */\nconst DPI = 96\n\n/**\n * Conversion factors for absolute units.\n * https://developer.mozilla.org/en-US/docs/web/css/length\n */\nconst ABSOLUTE_UNITS: Record<string, number> = {\n  in: DPI,\n  cm: DPI / 2.54,\n  mm: DPI / 25.4,\n  pt: DPI / 72,\n  pc: DPI / 6,\n  px: 1\n}\n\n// pre-calculated factor for % conversion\nconst SQRT2 = Math.sqrt(2)\n\n/**\n * https://www.w3.org/TR/css3-values/#dimensions\n */\ntype Dimension = { value: number; unit: string }\n\n/**\n * Converts the given string to px unit. May be either a\n * [length](https://developer.mozilla.org/de/docs/Web/SVG/Content_type#Length)\n * or a [percentage](https://developer.mozilla.org/de/docs/Web/SVG/Content_type#Percentage).\n * @returns The value in px unit\n */\nexport function convertToPixelUnit(\n  context: RenderContext,\n  element: SVGElement,\n  dimensionValue: string,\n  attribute: string\n): number {\n  const { value, unit } = parseDimension(dimensionValue)\n  if (isAbsoluteUnit(unit)) {\n    return absToPixel(value, unit)\n  }\n  return relToPixel(context, element, attribute, value, unit)\n}\n\n/**\n * Parses the given string and returns a dimension, which is a\n * [number](https://www.w3.org/TR/css3-values/#numbers) followed\n * by a unit identifier.\n */\nfunction parseDimension(dimension: string): Dimension {\n  const match = dimension.match(DIMENSION_REGEX)\n  if (match === null || match.length !== 3) {\n    throw new Error(`Cannot parse dimension: ${dimension}`)\n  }\n  return { value: parseFloat(match[1]), unit: match[2].toLowerCase() || 'px' }\n}\n\n/**\n * unit-css converts per HTML spec, which is differently for percentages in SVG\n * https://www.w3.org/TR/SVG/coords.html#Units\n * https://oreillymedia.github.io/Using_SVG/guide/units.html\n * @param percentage [0, 100]\n * @param viewBox The coordinate system to evaluate the percentage against\n */\nfunction percentageToPixel(\n  attribute: string,\n  percentage: number,\n  { w: width, h: height }: Size = { w: 0, h: 0 }\n): number {\n  const fraction = percentage / 100\n\n  // x and y are relative to the coordinate system's width or height\n  if (attribute === 'x') {\n    return fraction * width\n  }\n  if (attribute === 'y') {\n    return fraction * height\n  }\n\n  return fraction * (Math.sqrt(width * width + height * height) / SQRT2)\n}\n\n/**\n * Converts an absolute unit to pixels.\n */\nfunction absToPixel(value: number, unit: string): number {\n  const conversion = ABSOLUTE_UNITS[unit] ?? 1\n  return value * conversion\n}\n\n/**\n * Converts a relative unit to pixels.\n */\nfunction relToPixel(\n  context: RenderContext,\n  element: SVGElement,\n  attribute: string,\n  value: number,\n  unit: string\n): number {\n  const coordinateSystemSize = context.viewBox ?? { w: 0, h: 0 }\n\n  if (unit === '%') {\n    return percentageToPixel(attribute, value, coordinateSystemSize)\n  }\n\n  if (unit === 'vw' || unit === 'vh' || unit === 'vmin' || unit === 'vmax') {\n    return viewportLengthToPixel(value, unit, coordinateSystemSize)\n  }\n\n  if (unit === 'em' || unit === 'ex' || unit === 'ch' || unit === 'rem') {\n    return fontRelativeToPixel(context, element, value, unit)\n  }\n\n  throw new Error(`Unsupported relative length unit: ${unit}`)\n}\n\n/**\n * https://oreillymedia.github.io/Using_SVG/guide/units.html#units-viewport-reference\n */\nfunction viewportLengthToPixel(\n  value: number,\n  unit: string,\n  { w: width, h: height }: Size = { w: 0, h: 0 }\n): number {\n  const fraction = value / 100\n  const refWidth = window.innerWidth ?? width\n  const refHeight = window.innerHeight ?? height\n\n  if (unit === 'vw') {\n    return fraction * refWidth\n  }\n\n  if (unit === 'vh') {\n    return fraction * refHeight\n  }\n\n  if (unit === 'vmin') {\n    return fraction * Math.min(refWidth, refHeight)\n  }\n\n  if (unit === 'vmax') {\n    return fraction * Math.max(refWidth, refHeight)\n  }\n\n  throw new Error(`Not a viewport length unit: ${unit}`)\n}\n\n/**\n * https://oreillymedia.github.io/Using_SVG/guide/units.html#units-relative-reference\n */\nfunction fontRelativeToPixel(\n  context: RenderContext,\n  element: SVGElement,\n  value: number,\n  unit: string\n): number {\n  if (unit === 'rem') {\n    const rootElement = document.documentElement\n    const fontSizeDimension = parseDimension(getComputedStyle(rootElement).fontSize)\n    const fontSizePx = fontSizeDimension.unit === 'px' ? fontSizeDimension.value : 16\n    return value * fontSizePx\n  }\n\n  if (unit === 'ch') {\n    const zeroCharWidth = measureZeroCharacter(element)\n    return value * zeroCharWidth\n  }\n\n  // this should return a px font-size due to the getComputedStyle, otherwise use 16px as default fallback\n  const effectiveFontSize =\n    getEffectiveAttribute(context, element, 'font-size', context.useElementContext) ?? '16px'\n  const fontSizeDimension = parseDimension(effectiveFontSize)\n  const fontSizePx = fontSizeDimension.unit === 'px' ? fontSizeDimension.value : 16\n\n  if (unit === 'em') {\n    return value * fontSizePx\n  }\n\n  if (unit === 'ex') {\n    return value * fontSizePx * 0.5\n  }\n\n  throw new Error(`Not a font relative unit: ${unit}`)\n}\n\n/**\n * Whether the given unit is an absolute unit.\n */\nfunction isAbsoluteUnit(unit: string): boolean {\n  return !!ABSOLUTE_UNITS[unit]\n}\n\n/**\n * Returns the width of the '0' character in the context of the element.\n */\nfunction measureZeroCharacter(element: SVGElement): number {\n  const parent = getParentElement(element)\n  if (!parent) {\n    return 1\n  }\n  const measureContainer = document.createElementNS('http://www.w3.org/2000/svg', 'text')\n  measureContainer.style.visibility = 'hidden'\n  measureContainer.appendChild(document.createTextNode('0'))\n  parent.appendChild(measureContainer)\n  const bbox = measureContainer.getBBox()\n  parent.removeChild(measureContainer)\n  return bbox.width\n}\n"
  },
  {
    "path": "src/transformation.ts",
    "content": "import { Point } from './geom/primitives'\nimport { RenderContext } from './types'\n\n/**\n * Whether the given SVGTransform resembles an identity transform.\n * @returns Whether the transform is an identity transform.\n *  Returns true if transform is undefined.\n */\nexport function isIdentityTransform(svgTransform: SVGTransform | null): boolean {\n  if (!svgTransform) {\n    return true\n  }\n  const matrix = svgTransform.matrix\n  return (\n    !matrix ||\n    (matrix.a === 1 &&\n      matrix.b === 0 &&\n      matrix.c === 0 &&\n      matrix.d === 1 &&\n      matrix.e === 0 &&\n      matrix.f === 0)\n  )\n}\n\n/**\n * Whether the given SVGTransform does not scale nor skew.\n * @returns Whether the given SVGTransform does not scale nor skew.\n *  Returns true if transform is undefined.\n */\nexport function isTranslationTransform(svgTransform: SVGTransform | null): boolean {\n  if (!svgTransform) {\n    return true\n  }\n  const matrix = svgTransform.matrix\n  return !matrix || (matrix.a === 1 && matrix.b === 0 && matrix.c === 0 && matrix.d === 1)\n}\n\n/**\n * Applies a given `SVGTransform` to the point.\n *\n * [a c e] [x] = (a*x + c*y + e)\n * [b d f] [y] = (b*x + d*y + f)\n * [0 0 1] [1] = (0 + 0 + 1)\n */\nexport function applyMatrix(point: Point, svgTransform: SVGTransform | null): Point {\n  if (!svgTransform) {\n    return point\n  }\n  const matrix = svgTransform.matrix\n  const x = matrix.a * point.x + matrix.c * point.y + matrix.e\n  const y = matrix.b * point.x + matrix.d * point.y + matrix.f\n  return { x, y }\n}\n\n/**\n * Returns the consolidated transform of the given element.\n */\nexport function getSvgTransform(element: SVGGraphicsElement): SVGTransform | null {\n  if (element.transform && element.transform.baseVal.numberOfItems > 0) {\n    return element.transform.baseVal.consolidate()\n  }\n  return null\n}\n\n/**\n * Combines the given transform with the element's transform.\n * If no transform is given, it returns the SVGTransform of the element.\n */\nexport function getCombinedTransform(\n  context: RenderContext,\n  element: SVGGraphicsElement,\n  transform: SVGTransform | null\n): SVGTransform | null {\n  if (!transform) {\n    return getSvgTransform(element)\n  }\n\n  const elementTransform = getSvgTransform(element)\n  if (elementTransform) {\n    const elementTransformMatrix = elementTransform.matrix\n    const combinedMatrix = transform.matrix.multiply(elementTransformMatrix)\n    return context.sourceSvg.createSVGTransformFromMatrix(combinedMatrix)\n  }\n  return transform\n}\n\n/**\n * Applies the given svgTransform to the given element.\n * @param element The element to which the transform should be applied.\n */\nexport function applyTransform(\n  context: RenderContext,\n  svgTransform: SVGTransform | null,\n  element: SVGGraphicsElement\n): void {\n  if (svgTransform && svgTransform.matrix && !isIdentityTransform(svgTransform)) {\n    const matrix = svgTransform.matrix\n    if (element.transform.baseVal.numberOfItems > 0) {\n      element.transform.baseVal.getItem(0).setMatrix(matrix)\n    } else {\n      element.transform.baseVal.appendItem(svgTransform)\n    }\n  }\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "import { Options } from 'roughjs/bin/core'\nimport { RoughSVG } from 'roughjs/bin/svg'\nimport { Rectangle } from './geom/primitives'\nimport { RandomNumberGenerator } from './RandomNumberGenerator'\n\n/**\n * A context that represents the current state of the rendering,\n * which is used in the rendering functions.\n */\nexport type RenderContext = {\n  rc: RoughSVG\n  roughConfig: Options\n  fontFamily: string | null\n  pencilFilter: boolean\n  randomize: boolean\n  rng: RandomNumberGenerator\n  sketchPatterns: boolean\n  idElements: Record<string, SVGElement | string>\n  sourceSvg: SVGSVGElement\n  svgSketch: SVGSVGElement\n  svgSketchIsInDOM: boolean\n  svgSketchDefs?: SVGDefsElement\n  useElementContext?: UseContext | null\n  viewBox?: Rectangle\n  styleSheets: CSSStyleSheet[]\n  processElement: (\n    context: RenderContext,\n    root: SVGSVGElement | SVGGElement | SVGSymbolElement | SVGMarkerElement | SVGElement,\n    svgTransform: SVGTransform | null,\n    width?: number,\n    height?: number\n  ) => void\n}\n\n/**\n * The context for rendering use elements.\n */\nexport type UseContext = {\n  referenced: SVGElement\n  root: Element | null\n  parentContext: UseContext | null\n}\n"
  },
  {
    "path": "src/utils.ts",
    "content": "import { Options } from 'roughjs/bin/core'\nimport { reparentNodes } from './dom-helpers'\nimport { Point, Size } from './geom/primitives'\nimport { RenderContext } from './types'\n\n/**\n * Attribute for storing the new clip-path IDs for the sketch output.\n */\nexport const SKETCH_CLIP_ATTRIBUTE = 'data-sketchy-clip-path'\n\n/**\n * Regexp that detects curved commands in path data.\n */\nconst PATH_CURVES_REGEX = /[acsqt]/i\n\n/**\n * Returns the <defs> element of the output SVG sketch.\n */\nexport function getDefsElement(context: RenderContext): SVGDefsElement {\n  if (context.svgSketchDefs) {\n    return context.svgSketchDefs\n  }\n\n  const parent = context.svgSketch\n  const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs')\n  if (parent.childElementCount > 0) {\n    parent.insertBefore(defs, parent.firstElementChild)\n  } else {\n    parent.appendChild(defs)\n  }\n\n  context.svgSketchDefs = defs\n\n  return defs\n}\n\nexport function getPointsArray(element: SVGPolygonElement | SVGPolylineElement): Array<Point> {\n  const pointsAttr = element.getAttribute('points')\n  if (!pointsAttr) {\n    return []\n  }\n\n  let coordinateRegexp\n  if (pointsAttr.indexOf(' ') > 0) {\n    // just assume that the coordinates (or pairs) are separated with space\n    coordinateRegexp = /\\s+/g\n  } else {\n    // there are no spaces, so assume comma separators\n    coordinateRegexp = /,/g\n  }\n\n  const pointList = pointsAttr.split(coordinateRegexp)\n  const points: Point[] = []\n  for (let i = 0; i < pointList.length; i++) {\n    const currentEntry = pointList[i]\n    const coordinates = currentEntry.split(',')\n    if (coordinates.length === 2) {\n      points.push({ x: parseFloat(coordinates[0]), y: parseFloat(coordinates[1]) })\n    } else {\n      // space as separators, take next entry as y coordinate\n      const next = i + 1\n      if (next < pointList.length) {\n        points.push({ x: parseFloat(currentEntry), y: parseFloat(pointList[next]) })\n        // skip the next entry\n        i = next\n      }\n    }\n  }\n  return points\n}\n\n/**\n * Helper method to append the returned `SVGGElement` from Rough.js which\n * also post processes the result e.g. by applying the clip.\n */\nexport function appendSketchElement(\n  context: RenderContext,\n  element: SVGElement,\n  sketchElement: SVGElement\n): void {\n  let sketch = sketchElement\n\n  // original element may have a clip-path\n  const sketchClipPathId = element.getAttribute(SKETCH_CLIP_ATTRIBUTE)\n  const applyPencilFilter = context.pencilFilter && element.tagName !== 'text'\n\n  // wrap it in another container to safely apply post-processing attributes,\n  // though avoid no-op <g> containers\n  const isPlainContainer = sketch.tagName === 'g' && sketch.attributes.length === 0\n  if (!isPlainContainer && (sketchClipPathId || applyPencilFilter)) {\n    const g = document.createElementNS('http://www.w3.org/2000/svg', 'g')\n    g.appendChild(sketch)\n    sketch = g\n  }\n\n  if (sketchClipPathId) {\n    sketch.setAttribute('clip-path', `url(#${sketchClipPathId})`)\n    element.removeAttribute(SKETCH_CLIP_ATTRIBUTE)\n  }\n\n  if (applyPencilFilter) {\n    sketch.setAttribute('filter', 'url(#pencilTextureFilter)')\n  }\n\n  context.svgSketch.appendChild(sketch)\n}\n\n/**\n * Helper method to sketch a path.\n * Paths with curves should utilize the preserverVertices option to avoid line disjoints.\n * For non-curved paths it looks nicer to actually allow these diskoints.\n * @returns Returns the sketched SVGElement\n */\nexport function sketchPath(context: RenderContext, path: string, options?: Options): SVGElement {\n  if (PATH_CURVES_REGEX.test(path)) {\n    options = options ? { ...options, preserveVertices: true } : { preserveVertices: true }\n  }\n  return context.rc.path(path, options)\n}\n\n/**\n * Helper funtion to sketch a DOM fragment.\n * Wraps the given element in an SVG and runs the processor on it to sketch the fragment.\n * The result is then unpacked and returned.\n */\nexport function sketchFragment(\n  context: RenderContext,\n  g: SVGGElement,\n  roughOverwrites?: Options\n): SVGGElement {\n  const proxySource = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n  proxySource.appendChild(g)\n  const proxyContext: RenderContext = {\n    ...context,\n    sourceSvg: proxySource,\n    svgSketch: document.createElementNS('http://www.w3.org/2000/svg', 'svg'),\n    roughConfig: { ...context.roughConfig, ...roughOverwrites }\n  }\n  proxyContext.processElement(proxyContext, g, null)\n  return reparentNodes(\n    document.createElementNS('http://www.w3.org/2000/svg', 'g'),\n    proxyContext.svgSketch\n  )\n}\n\n/**\n * Measures the text in the context of the sketchSvg to account for inherited text\n * attributes.\n * The given text element must be a child of the svgSketch.\n */\nexport function measureText(\n  { svgSketch, svgSketchIsInDOM }: RenderContext,\n  text: SVGTextElement\n): Size {\n  const hiddenElementStyle = 'visibility:hidden;position:absolute;left:-100%;top-100%;'\n  const origStyle = svgSketch.getAttribute('style')\n  if (origStyle) {\n    svgSketch.setAttribute('style', `${origStyle};${hiddenElementStyle}`)\n  } else {\n    svgSketch.setAttribute('style', hiddenElementStyle)\n  }\n\n  // the element must be in the DOM for getBBox\n  const body = document.body\n  const previousParent = svgSketch.parentElement\n  if (!svgSketchIsInDOM) {\n    body.appendChild(svgSketch)\n  }\n  const { width, height } = text.getBBox()\n\n  // make sure to not change the DOM hierarchy of the element\n  if (!svgSketchIsInDOM) {\n    body.removeChild(svgSketch)\n    if (previousParent) {\n      previousParent.appendChild(svgSketch)\n    }\n  }\n\n  if (origStyle) {\n    svgSketch.setAttribute('style', origStyle)\n  } else {\n    svgSketch.removeAttribute('style')\n  }\n\n  return { w: width, h: height }\n}\n"
  },
  {
    "path": "test/complex/bpmn-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/computer-network-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/flowchart-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/hierarchical1-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/hierarchical2-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/mindmap-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/movies-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/organic1-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/organic2-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/tree-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/complex/venn-diagram/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/runner/complex.test.js",
    "content": "import { expect, fixture } from '@open-wc/testing'\nimport { OutputType, Svg2Roughjs } from '../../out-tsc/index'\nimport { compareRootElements, loadConfig, loadSvg, repackage } from './utils'\nimport { complexTests } from '../tests'\n\nfor (const name of complexTests) {\n  describe(name, () => {\n    it(`Testing complex SVG ${name}`, async () => {\n      const svgTestText = loadSvg(`/test/complex/${name}/test.svg`)\n      const svgExpectedText = loadSvg(`/test/complex/${name}/expect.svg`)\n      const testConfig = loadConfig(`/test/complex/${name}/config.json`)\n\n      const svgTestElement = await fixture(svgTestText)\n      const svgExpectedElement = await fixture(svgExpectedText)\n\n      const svgSketchResult = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n      const svg2roughjs = new Svg2Roughjs(svgSketchResult, OutputType.SVG, testConfig.roughConfig)\n      svg2roughjs.randomize = false\n      svg2roughjs.pencilFilter = false\n      svg2roughjs.backgroundColor = testConfig.backgroundColor\n      svg2roughjs.svg = svgTestElement\n      await svg2roughjs.sketch()\n\n      // diff the <svg> attributes\n      compareRootElements(svgSketchResult, svgExpectedElement)\n\n      // <svg> tags are not supported and ignore entirely, so move children into a div\n      // https://github.com/open-wc/open-wc/issues/1229\n      const sketchElement = repackage(svgSketchResult)\n      const expectElement = repackage(svgExpectedElement)\n\n      // Diff the DOMs\n      // https://github.com/open-wc/open-wc/blob/master/docs/docs/testing/helpers.md\n      //\n      // Ensure that the expected element is provided as string, otherwise the internal\n      // 'getDiffableHTML' results in different casing from the expect element (which is\n      // provided as string internally as well).\n      expect(sketchElement).dom.to.equal(expectElement.outerHTML, {\n        ignoreAttributes: [\n          'xmlns:xlink' // the downloaded expect file may have this attribute, while the generated one doesn't have it\n        ]\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "test/runner/spec.test.js",
    "content": "import { expect, fixture } from '@open-wc/testing'\nimport { OutputType, Svg2Roughjs } from '../../out-tsc/index'\nimport { compareRootElements, loadConfig, loadSvg, repackage } from './utils'\nimport { specTests } from '../tests'\n\nfor (const name of specTests) {\n  describe(name, () => {\n    it(`Testing render spec ${name}`, async () => {\n      const svgTestText = loadSvg(`/test/specs/${name}/test.svg`)\n      const svgExpectedText = loadSvg(`/test/specs/${name}/expect.svg`)\n      const testConfig = loadConfig(`/test/specs/${name}/config.json`)\n\n      const svgTestElement = await fixture(svgTestText)\n      const svgExpectedElement = await fixture(svgExpectedText)\n\n      const svgSketchResult = document.createElementNS('http://www.w3.org/2000/svg', 'svg')\n      const svg2roughjs = new Svg2Roughjs(svgSketchResult, OutputType.SVG, testConfig.roughConfig)\n      svg2roughjs.randomize = false\n      svg2roughjs.pencilFilter = false\n      svg2roughjs.backgroundColor = testConfig.backgroundColor\n      svg2roughjs.svg = svgTestElement\n      await svg2roughjs.sketch()\n\n      // diff the <svg> attributes\n      compareRootElements(svgSketchResult, svgExpectedElement)\n\n      // <svg> tags are not supported and ignore entirely, so move children into a div\n      // https://github.com/open-wc/open-wc/issues/1229\n      const sketchElement = repackage(svgSketchResult)\n      const expectElement = repackage(svgExpectedElement)\n\n      // Diff the DOMs\n      // https://github.com/open-wc/open-wc/blob/master/docs/docs/testing/helpers.md\n      //\n      // Ensure that the expected element is provided as string, otherwise the internal\n      // 'getDiffableHTML' results in different casing from the expect element (which is\n      // provided as string internally as well).\n      expect(sketchElement).dom.to.equal(expectElement.outerHTML, {\n        ignoreAttributes: [\n          'xmlns:xlink' // the downloaded expect file may have this attribute, while the generated one doesn't have it\n        ]\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "test/runner/utils.js",
    "content": "import { expect } from '@open-wc/testing'\n\nexport function loadSvg(url) {\n  const request = new XMLHttpRequest()\n  request.open('GET', url, false)\n  request.overrideMimeType('text/plain; charset=utf-8')\n  request.send()\n  if (request.status !== 200) {\n    throw new Error(`Unable to fetch ${url}, status code: ${request.status}`)\n  }\n  return request.responseText\n}\n\nexport function loadConfig(url) {\n  const request = new XMLHttpRequest()\n  request.open('GET', url, false)\n  request.send()\n  if (request.status !== 200) {\n    throw new Error(`Unable to fetch ${url}, status code: ${request.status}`)\n  }\n  return JSON.parse(request.responseText)\n}\n\n/**\n * Moves all children of the given SVG into a div to workaround\n * https://github.com/open-wc/open-wc/issues/1229\n * @param {SVGSVGElement} svg\n * @returns {HTMLDivElement}\n */\nexport function repackage(svg) {\n  const newParent = document.createElement('div')\n  newParent.className = 'svg-surrogate'\n  while (svg.childNodes.length > 0) {\n    newParent.appendChild(svg.childNodes[0])\n  }\n  return newParent\n}\n\n/**\n * Compares the attributes of the svg root elements\n * @param {SVGSVGElement} result\n * @param {SVGSVGElement} expected\n */\nexport function compareRootElements(result, expected) {\n  const checkAttributes = ['width', 'height', 'viewBox', 'stroke-linecap']\n  for (const attr of checkAttributes) {\n    expect(result.getAttribute(attr)).to.equal(\n      expected.getAttribute(attr),\n      `<svg> attribute ${attr} not matching`\n    )\n  }\n}\n"
  },
  {
    "path": "test/specs/circle-transform/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-circle/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-circle-transformed/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-ellipse/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-ellipse-transformed/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-g-element/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-path/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-path-transformed/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-polygon/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-rect/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-rect-rounded/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-rect-rounded-transformed/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-rect-rounded-transformed2/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-rect-text/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clippath-rect-transformed/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/clipped-text-scaling/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/css-units/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/dotted-stroke/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/ellipse-transform/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-attribute/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-attribute-ancestor-g/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-attribute-ancestor-svg/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-css-attribute-precedence/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-css-attribute-precedence2/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-css-class/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-css-inline/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-css-selector/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/fill-missing/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/foreign-object-mermaid/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/icons/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/markers/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/markers-fixed-orientation/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/markers-line/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/markers-on-line/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/markers-paths/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/markers-polygon/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/markers-polyline/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/nested-svg-translate/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/path-transform/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/path-transform2/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/pattern-circle/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/pattern-ellipse/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/pattern-line/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/pattern-path/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/pattern-polygon/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/pattern-polyline/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/pattern-rect/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-not-rounded/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-plain/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-large-rx/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-large-rx-ry/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-large-ry/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-rx/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-rx-ry/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-ry/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-transform/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-rounded-transform-mirror/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-transform/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/rect-transform-from-g/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-attribute/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-attribute-ancestor-g/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-attribute-ancestor-g-override/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-attribute-ancestor-g2/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-attribute-ancestor-svg/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-missing-is-transparent/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-none-is-transparent/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-width-attribute/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"hachure\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/stroke-width-scale-transform/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/svg-image-element/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/symbols/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/symbols-non-uniform-scale/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/symbols2/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-css/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-dominant-baseline-basic/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-glyph-positioning/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-rotated-glyphs/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-simple-tspans/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-stroked-and-decorated/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-tspan-styling/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-tspans-mixed/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-tspans-repositioned/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-whitespace/config.json",
    "content": "{\"roughConfig\":{\"bowing\":0,\"roughness\":0,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/text-width-custom-font/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/uml-node-style/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/use-element-styling/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/use-reference-group/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/viewbox-negative/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/viewbox-non-uniform/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/specs/viewbox-non-uniform-translated/config.json",
    "content": "{\"roughConfig\":{\"bowing\":1,\"roughness\":1,\"fillStyle\":\"solid\",\"fixedDecimalPlaceDigits\":3,\"seed\":4242},\"outputType\":0,\"pencilFilter\":false,\"sketchPatterns\":true,\"backgroundColor\":\"white\"}"
  },
  {
    "path": "test/tests.js",
    "content": "export const specTests = [\n  'circle-transform',\n  'clippath-circle',\n  'clippath-circle-transformed',\n  'clippath-ellipse',\n  'clippath-ellipse-transformed',\n  'clippath-g-element',\n  'clippath-path',\n  'clippath-path-transformed',\n  'clippath-polygon',\n  'clippath-rect',\n  'clippath-rect-rounded',\n  'clippath-rect-rounded-transformed',\n  'clippath-rect-rounded-transformed2',\n  'clippath-rect-text',\n  'clippath-rect-transformed',\n  'clipped-text-scaling',\n  'css-units',\n  'dotted-stroke',\n  'ellipse-transform',\n  'fill-attribute',\n  'fill-attribute-ancestor-g',\n  'fill-attribute-ancestor-svg',\n  'fill-css-attribute-precedence',\n  'fill-css-attribute-precedence2',\n  'fill-css-class',\n  'fill-css-inline',\n  'fill-css-selector',\n  'fill-missing',\n  'foreign-object-mermaid',\n  'icons',\n  'markers',\n  'markers-fixed-orientation',\n  'markers-line',\n  'markers-on-line',\n  'markers-paths',\n  'markers-polygon',\n  'markers-polyline',\n  'nested-svg-translate',\n  'path-transform',\n  'path-transform2',\n  'pattern-circle',\n  'pattern-ellipse',\n  'pattern-line',\n  'pattern-path',\n  'pattern-polygon',\n  'pattern-polyline',\n  'pattern-rect',\n  'rect-not-rounded',\n  'rect-plain',\n  'rect-rounded-large-rx',\n  'rect-rounded-large-rx-ry',\n  'rect-rounded-large-ry',\n  'rect-rounded-rx',\n  'rect-rounded-rx-ry',\n  'rect-rounded-ry',\n  'rect-rounded-transform',\n  'rect-rounded-transform-mirror',\n  'rect-transform',\n  'rect-transform-from-g',\n  'stroke-attribute',\n  'stroke-attribute-ancestor-g',\n  'stroke-attribute-ancestor-g-override',\n  'stroke-attribute-ancestor-g2',\n  'stroke-attribute-ancestor-svg',\n  'stroke-missing-is-transparent',\n  'stroke-none-is-transparent',\n  'stroke-width-attribute',\n  'stroke-width-scale-transform',\n  'svg-image-element',\n  'symbols',\n  'symbols-non-uniform-scale',\n  'symbols2',\n  'text-css',\n  'text-dominant-baseline-basic',\n  'text-glyph-positioning',\n  'text-rotated-glyphs',\n  'text-simple-tspans',\n  'text-stroked-and-decorated',\n  'text-tspan-styling',\n  'text-tspans-mixed',\n  'text-tspans-repositioned',\n  'text-width-custom-font',\n  'text-whitespace',\n  'uml-node-style',\n  'use-element-styling',\n  'use-reference-group',\n  'viewbox-negative',\n  'viewbox-non-uniform',\n  'viewbox-non-uniform-translated'\n]\n\nexport const complexTests = [\n  'bpmn-diagram',\n  'computer-network-diagram',\n  'flowchart-diagram',\n  'hierarchical1-diagram',\n  'hierarchical2-diagram',\n  'mindmap-diagram',\n  'movies-diagram',\n  'organic1-diagram',\n  'organic2-diagram',\n  'tree-diagram',\n  'venn-diagram'\n]\n"
  },
  {
    "path": "test/umd-bundle/umd-bundle-test.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>svg2roughjs</title>\n  <script src=\"../../dist/svg2roughjs.umd.min.js\"></script>\n</head>\n\n<body>\n  <div style=\"display: flex;\">\n    <svg id=\"input-svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"970px\"\n      height=\"340px\" viewBox=\"0 0 970 340\" style=\"overflow: hidden; display: block; width: 970px; height: 340px;\">\n      <defs>\n        <style xmlns=\"http://www.w3.org/1999/xhtml\" type=\"text/css\">\n          @font-face {\n            font-family: 'Roboto';\n            src: url(data:application/font-ttf;charset=utf-8;base64,undefined) format('truetype');\n          }\n        </style>\n      </defs>\n      <g style=\"pointer-events:visiblePainted\" transform=\"translate(75 305.7396825396825)\" image-rendering=\"auto\"\n        shape-rendering=\"auto\">\n        <g>\n          <g transform=\"translate(-50 -280.7396825396825)\">\n            <rect fill=\"rgb(224,224,224)\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"butt\"\n              stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill-opacity=\"1\" x=\"0\" height=\"113.357\" y=\"0\"\n              width=\"920\" />\n            <g transform=\"translate(0 0)\">\n              <g>\n                <g transform=\"translate(20 0)\">\n                  <g transform=\"translate(0 0)\">\n                    <rect x=\"0\" y=\"0\" width=\"900\" height=\"113.35714285714283\" stroke=\"none\" fill=\"rgb(196,215,237)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"0\" width=\"20\" height=\"113.35714285714283\" stroke=\"none\" fill=\"rgb(224,224,224)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"0\" width=\"900\" height=\"0\" stroke=\"none\" fill=\"rgb(224,224,224)\" fill-opacity=\"1\" />\n                    <rect x=\"900\" y=\"0\" width=\"0\" height=\"113.35714285714283\" stroke=\"none\" fill=\"rgb(224,224,224)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"113.35714285714283\" width=\"900\" height=\"0\" stroke=\"none\" fill=\"rgb(224,224,224)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"0\" width=\"900\" height=\"113.35714285714283\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n                      stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n                      fill=\"none\" />\n                  </g>\n                </g>\n                <g transform=\"matrix(0 -1 1 0 20 113.35714285714283)\">\n                  <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\"\n                    text-anchor=\"middle\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\" dy=\"0.93359375em\"\n                    transform=\"translate(56.678571428571416 3.1953125)\">Applicant</text>\n                </g>\n              </g>\n              <g>\n                <g>\n                  <rect fill=\"rgb(255,255,255)\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\"\n                    stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill-opacity=\"0\" x=\"40\"\n                    height=\"113.357\" y=\"0\" width=\"880\" />\n                </g>\n              </g>\n            </g>\n          </g>\n        </g>\n        <g>\n          <path d=\"M 50,-225.73968253968255 L 81,-225.73968253968255\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(89 -225.73968253968255)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 170,-225.73968253968255 L 201,-225.73968253968258\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(1 -9.168293364646454e-16 9.168293364646454e-16 1 209 -225.73968253968258)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 290,-225.73968253968258 L 321,-225.73968253968258\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(329 -225.73968253968258)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 410,-225.73968253968258 L 441,-225.73968253968255\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(1 9.168293364646454e-16 -9.168293364646454e-16 1 449 -225.73968253968255)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 530,-225.73968253968255 L 561,-225.73968253968258\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(1 -9.168293364646454e-16 9.168293364646454e-16 1 569 -225.73968253968258)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 650,-225.73968253968258 L 681,-225.73968253968258\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(689 -225.73968253968258)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 770,-225.73968253968258 L 801,-225.73968253968255\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(1 9.168293364646454e-16 -9.168293364646454e-16 1 809 -225.73968253968255)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g transform=\"translate(0 -250.73968253968255)\">\n          <path\n            d=\"M 25,50 C 11.195,50 0,38.805 0,25 C 0,11.195 11.195,0 25,0 C 38.805,0 50,11.195 50,25 C 50,38.805 38.805,50 25,50 Z\"\n            stroke=\"rgb(0,128,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"rgb(250,250,250)\" fill-opacity=\"1\" transform=\"translate(0 0)\" />\n        </g>\n        <g transform=\"translate(90 -250.73968253968255)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"50\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(210 -260.7396825396826)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"70\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(330 -260.7396825396826)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"70\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(450 -250.73968253968255)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"50\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(570 -260.7396825396826)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"70\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(690 -260.7396825396826)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"70\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(810 -250.73968253968255)\">\n          <g transform=\"translate(0 0)\">\n            <path\n              d=\"M 25,50 C 11.195,50 0,38.805 0,25 C 0,11.195 11.195,0 25,0 C 38.805,0 50,11.195 50,25 C 50,38.805 38.805,50 25,50 Z\"\n              stroke=\"rgb(255,0,0)\" stroke-opacity=\"1\" stroke-width=\"3\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n              stroke-miterlimit=\"10\" fill=\"rgb(250,250,250)\" fill-opacity=\"1\" transform=\"translate(0 0)\" />\n            <path\n              d=\"M 19.799999999999997,39.599999999999994 C 8.866439999999999,39.599999999999994 0,30.733559999999997 0,19.799999999999997 C 0,8.866439999999999 8.866439999999999,0 19.799999999999997,0 C 30.733559999999997,0 39.599999999999994,8.866439999999999 39.599999999999994,19.799999999999997 C 39.599999999999994,30.733559999999997 30.733559999999997,39.599999999999994 19.799999999999997,39.599999999999994 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n              stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(5.199999999999999 5.200000000000003)\" />\n          </g>\n        </g>\n        <g>\n          <g transform=\"translate(-50 -117.38253968253969)\">\n            <rect fill=\"rgb(224,224,224)\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"butt\"\n              stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill-opacity=\"1\" x=\"0\" height=\"127.383\" y=\"0\"\n              width=\"920\" />\n            <g transform=\"translate(0 0)\">\n              <g>\n                <g transform=\"translate(20 0)\">\n                  <g transform=\"translate(0 0)\">\n                    <rect x=\"0\" y=\"0\" width=\"900\" height=\"127.38253968253969\" stroke=\"none\" fill=\"rgb(196,215,237)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"0\" width=\"20\" height=\"127.38253968253969\" stroke=\"none\" fill=\"rgb(224,224,224)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"0\" width=\"900\" height=\"0\" stroke=\"none\" fill=\"rgb(224,224,224)\" fill-opacity=\"1\" />\n                    <rect x=\"900\" y=\"0\" width=\"0\" height=\"127.38253968253969\" stroke=\"none\" fill=\"rgb(224,224,224)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"127.38253968253969\" width=\"900\" height=\"0\" stroke=\"none\" fill=\"rgb(224,224,224)\"\n                      fill-opacity=\"1\" />\n                    <rect x=\"0\" y=\"0\" width=\"900\" height=\"127.38253968253969\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n                      stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n                      fill=\"none\" />\n                  </g>\n                </g>\n                <g transform=\"matrix(0 -1 1 0 20 127.38253968253969)\">\n                  <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\"\n                    text-anchor=\"middle\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\" dy=\"0.93359375em\"\n                    transform=\"translate(63.69126984126984 3.1953125)\">Enterprise</text>\n                </g>\n              </g>\n              <g>\n                <g>\n                  <rect fill=\"rgb(255,255,255)\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\"\n                    stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill-opacity=\"0\" x=\"40\"\n                    height=\"127.383\" y=\"0\" width=\"880\" />\n                </g>\n              </g>\n            </g>\n          </g>\n        </g>\n        <g>\n          <path d=\"M 50,-52.01269841269841 L 81,-52.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(89 -52.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 170,-52.01269841269841 L 201,-52.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(209 -52.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 290,-52.01269841269841 L 321,-52.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(329 -52.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 410,-52.01269841269841 L 441,-52.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(449 -52.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 530,-52.01269841269841 L 561,-52.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(569 -52.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 650,-52.01269841269841 L 681,-52.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(689 -52.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 770,-52.01269841269841 L 801,-52.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"translate(809 -52.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g transform=\"translate(0 -77.01269841269841)\">\n          <path\n            d=\"M 25,50 C 11.195,50 0,38.805 0,25 C 0,11.195 11.195,0 25,0 C 38.805,0 50,11.195 50,25 C 50,38.805 38.805,50 25,50 Z\"\n            stroke=\"rgb(0,128,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"rgb(250,250,250)\" fill-opacity=\"1\" transform=\"translate(0 0)\" />\n        </g>\n        <g transform=\"translate(90 -77.01269841269841)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"50\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(210 -77.01269841269841)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"50\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(330 -77.01269841269841)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"50\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(450 -77.01269841269841)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"50\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(570 -87.01269841269841)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"70\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(690 -94.02539682539683)\">\n          <rect x=\"0\" y=\"0\" width=\"80\" height=\"84.02539682539683\" rx=\"6\" ry=\"6\" stroke=\"rgb(0,0,139)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\" stroke-miterlimit=\"10\"\n            fill=\"rgb(250,250,250)\" fill-opacity=\"1\" />\n        </g>\n        <g transform=\"translate(810 -77.01269841269841)\">\n          <g transform=\"translate(0 0)\">\n            <path\n              d=\"M 25,50 C 11.195,50 0,38.805 0,25 C 0,11.195 11.195,0 25,0 C 38.805,0 50,11.195 50,25 C 50,38.805 38.805,50 25,50 Z\"\n              stroke=\"rgb(255,0,0)\" stroke-opacity=\"1\" stroke-width=\"3\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n              stroke-miterlimit=\"10\" fill=\"rgb(250,250,250)\" fill-opacity=\"1\" transform=\"translate(0 0)\" />\n            <path\n              d=\"M 19.799999999999997,39.599999999999994 C 8.866439999999999,39.599999999999994 0,30.733559999999997 0,19.799999999999997 C 0,8.866439999999999 8.866439999999999,0 19.799999999999997,0 C 30.733559999999997,0 39.599999999999994,8.866439999999999 39.599999999999994,19.799999999999997 C 39.599999999999994,30.733559999999997 30.733559999999997,39.599999999999994 19.799999999999997,39.599999999999994 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n              stroke-miterlimit=\"10\" fill=\"rgb(0,0,0)\" fill-opacity=\"1\"\n              transform=\"translate(5.199999999999999 5.200000000000003)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 130,-193.73968253968255 L 130,-86.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-dasharray=\"2,2\" stroke-dashoffset=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(0 -1 1 0 130 -199.73968253968255)\">\n            <path\n              d=\"M 3,6 C 1.3434,6 0,4.6566 0,3 C 0,1.3434 1.3434,0 3,0 C 4.6566,0 6,1.3434 6,3 C 6,4.6566 4.6566,6 3,6 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n              stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" transform=\"translate(-6 -3)\" />\n          </g>\n          <g transform=\"matrix(0 1 -1 0 130 -78.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 250,-84.01269841269841 L 250,-181.73968253968258\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-dasharray=\"2,2\" stroke-dashoffset=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(0 1 -1 0 250 -78.01269841269841)\">\n            <path\n              d=\"M 3,6 C 1.3434,6 0,4.6566 0,3 C 0,1.3434 1.3434,0 3,0 C 4.6566,0 6,1.3434 6,3 C 6,4.6566 4.6566,6 3,6 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n              stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" transform=\"translate(-6 -3)\" />\n          </g>\n          <g transform=\"matrix(0 -1 1 0 250 -189.73968253968258)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 370,-84.01269841269841 L 370,-181.73968253968258\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-dasharray=\"2,2\" stroke-dashoffset=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(0 1 -1 0 370 -78.01269841269841)\">\n            <path\n              d=\"M 3,6 C 1.3434,6 0,4.6566 0,3 C 0,1.3434 1.3434,0 3,0 C 4.6566,0 6,1.3434 6,3 C 6,4.6566 4.6566,6 3,6 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n              stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" transform=\"translate(-6 -3)\" />\n          </g>\n          <g transform=\"matrix(0 -1 1 0 370 -189.73968253968258)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 610,-94.01269841269841 L 610,-181.73968253968258\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-dasharray=\"2,2\" stroke-dashoffset=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(0 1 -1 0 610 -88.01269841269841)\">\n            <path\n              d=\"M 3,6 C 1.3434,6 0,4.6566 0,3 C 0,1.3434 1.3434,0 3,0 C 4.6566,0 6,1.3434 6,3 C 6,4.6566 4.6566,6 3,6 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n              stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" transform=\"translate(-6 -3)\" />\n          </g>\n          <g transform=\"matrix(0 -1 1 0 610 -189.73968253968258)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 730,-183.73968253968258 L 730,-103.02539682539683\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-dasharray=\"2,2\" stroke-dashoffset=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(0 -1 1 0 730 -189.73968253968258)\">\n            <path\n              d=\"M 3,6 C 1.3434,6 0,4.6566 0,3 C 0,1.3434 1.3434,0 3,0 C 4.6566,0 6,1.3434 6,3 C 6,4.6566 4.6566,6 3,6 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n              stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" transform=\"translate(-6 -3)\" />\n          </g>\n          <g transform=\"matrix(0 1 -1 0 730 -95.02539682539683)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 470,-84.01269841269841 L 470,-191.73968253968255\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-dasharray=\"2,2\" stroke-dashoffset=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(0 1 -1 0 470 -78.01269841269841)\">\n            <path\n              d=\"M 3,6 C 1.3434,6 0,4.6566 0,3 C 0,1.3434 1.3434,0 3,0 C 4.6566,0 6,1.3434 6,3 C 6,4.6566 4.6566,6 3,6 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n              stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" transform=\"translate(-6 -3)\" />\n          </g>\n          <g transform=\"matrix(0 -1 1 0 470 -199.73968253968255)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g>\n          <path d=\"M 510,-193.73968253968255 L 510,-86.01269841269841\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\"\n            stroke-width=\"1\" stroke-dasharray=\"2,2\" stroke-dashoffset=\"1\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"\n            stroke-miterlimit=\"10\" fill=\"none\" />\n          <g transform=\"matrix(0 -1 1 0 510 -199.73968253968255)\">\n            <path\n              d=\"M 3,6 C 1.3434,6 0,4.6566 0,3 C 0,1.3434 1.3434,0 3,0 C 4.6566,0 6,1.3434 6,3 C 6,4.6566 4.6566,6 3,6 Z\"\n              stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n              stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" transform=\"translate(-6 -3)\" />\n          </g>\n          <g transform=\"matrix(0 1 -1 0 510 -78.01269841269841)\">\n            <path d=\"M 0,0 L 8,3 L 0,6 Z\" stroke=\"rgb(0,0,0)\" stroke-opacity=\"1\" stroke-width=\"1\" stroke-linecap=\"round\"\n              stroke-linejoin=\"round\" stroke-miterlimit=\"10\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\"\n              transform=\"translate(-8 -3)\" />\n          </g>\n        </g>\n        <g\n          transform=\"matrix(1.6081226496766366e-16 -1 1 1.6081226496766366e-16 122.99999999999999 -109.37619047619049)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" dy=\"0.93359375em\"\n            transform=\"translate(29.5 0.1953125)\">Application</text>\n        </g>\n        <g transform=\"matrix(6.123233995736766e-17 -1 1 6.123233995736766e-17 234 -99.37619047619049)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(34.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Confirmation</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">of Receipt</tspan>\n          </text>\n        </g>\n        <g transform=\"matrix(6.123233995736766e-17 -1 1 6.123233995736766e-17 363 -109.37619047619049)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" dy=\"0.93359375em\" transform=\"translate(24.5 0.1953125)\">Invitation</text>\n        </g>\n        <g transform=\"matrix(6.123233995736766e-17 -1 1 6.123233995736766e-17 594 -105.37619047619049)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(33.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Employment</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Contract</tspan>\n          </text>\n        </g>\n        <g transform=\"matrix(1.6081226496766366e-16 -1 1 1.6081226496766366e-16 705 -108.88253968253971)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(33.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Signed</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Employment</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Contract</tspan>\n          </text>\n        </g>\n        <g transform=\"matrix(6.123233995736766e-17 -1 1 6.123233995736766e-17 463 -114.37619047619049)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" dy=\"0.93359375em\" transform=\"translate(24.5 0.1953125)\">Question</text>\n        </g>\n        <g transform=\"matrix(1.6081226496766366e-16 -1 1 1.6081226496766366e-16 503 -118.37619047619049)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" dy=\"0.93359375em\" transform=\"translate(20.5 0.1953125)\">Answer</text>\n        </g>\n        <g transform=\"translate(100.5 -241.73968253968255)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(29.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Write Job</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Application</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(215.5 -250.73968253968258)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(34.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Receive</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Confirmation</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">of Receipt</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(339 -250.73968253968258)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(31 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Receive </tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Invitation to</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Interview</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(458 -241.73968253968255)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(32 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Take Part in</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Interview</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(576.5 -250.73968253968258)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(33.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Receive</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Employment</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Contract</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(693.5 -250.73968253968258)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(36.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Sign Contract</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">and Send It</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Back</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(100.5 -68.01269841269841)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(29.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Receive</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Application</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(228.5 -68.01269841269841)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(21.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Confirm</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Receipt</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(345.5 -68.01269841269841)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(24.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Invite to</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Interview</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(463.5 -68.01269841269841)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(26.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Carry Out</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Interview</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(576.5 -77.01269841269841)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(33.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Send</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Employment</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Contract</tspan>\n          </text>\n        </g>\n        <g transform=\"translate(696.5 -86.01269841269841)\">\n          <text font-family=\"'Arial'\" font-size=\"12px\" font-style=\"normal\" font-weight=\"normal\" text-anchor=\"middle\"\n            fill=\"rgb(0,0,0)\" fill-opacity=\"1\" transform=\"translate(33.5 0.1953125)\">\n            <tspan x=\"0\" dy=\"0.93359375em\">Receive</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Signed</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Employment</tspan>\n            <tspan x=\"0\" dy=\"1.5em\">Contract</tspan>\n          </text>\n        </g>\n      </g>\n    </svg>\n    <div id=\"output-div\"></div>\n  </div>\n\n  <script>\n    const { Svg2Roughjs, OutputType } = svg2roughjs\n    const svgConverter = new Svg2Roughjs('#output-div', OutputType.SVG, { roughness: 5 })\n    svgConverter.svg = document.getElementById('input-svg')\n    svgConverter.sketch()\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es6\",\n    \"target\": \"es2015\",\n    \"allowJs\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"moduleResolution\": \"node\",\n    \"lib\": [\"dom\", \"es2015\"],\n    \"outDir\": \"out-tsc\",\n    \"declaration\": true,\n\n    /* Linting */\n    \"strict\": true,\n    \"noImplicitAny\": false,\n    \"noUnusedLocals\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src/**/*.ts\"]\n}\n"
  },
  {
    "path": "web-test-runner.config.mjs",
    "content": "import { defaultReporter, summaryReporter } from '@web/test-runner'\nimport { rollupBundlePlugin } from '@web/dev-server-rollup'\nimport { nodeResolve } from '@rollup/plugin-node-resolve'\nimport commonjs from '@rollup/plugin-commonjs'\n\nconst filteredLogs = ['Running in dev mode', 'lit-html is in dev mode', 'Lit is in dev mode']\n\n// https://modern-web.dev/docs/test-runner/cli-and-configuration/\nexport default /** @type {import(\"@web/test-runner\").TestRunnerConfig} */ ({\n  /** Test files to run */\n  files: ['test/runner/spec.test.js', 'test/runner/complex.test.js'],\n\n  plugins: [\n    // We need to use rollup here unfortunately, because esbuild in the open-wc test-runner\n    // only works on esbuild's single file transform API that doesn't bundle dependencies\n    // automatically. This does not work for the external dependencies here (e.g. rough,\n    // etc.), which cannot be resolved then.\n    //\n    // So use rollup with a configuration that creates one big-fat bundle without external deps.\n    rollupBundlePlugin({\n      rollupConfig: {\n        input: 'out-tsc/index.js',\n        plugins: [nodeResolve(), commonjs()]\n      }\n    })\n  ],\n\n  reporters: [\n    // use the default reporter only for reporting test progress\n    // enable reportTestResults to see the actual diff of the results\n    defaultReporter({ reportTestResults: false, reportTestProgress: true }),\n    summaryReporter()\n  ],\n\n  /** Whether to analyze code coverage */\n  coverage: false,\n\n  /** Run tests manually in the browser (e.g. useful for debugging) */\n  manual: false,\n\n  /** Resolve bare module imports */\n  nodeResolve: {\n    exportConditions: ['browser', 'development']\n  },\n\n  rootDir: './',\n\n  /** Filter out lit dev mode logs */\n  filterBrowserLogs(log) {\n    for (const arg of log.args) {\n      if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) {\n        return false\n      }\n    }\n    return true\n  },\n\n  /** Amount of browsers to run concurrently */\n  // concurrentBrowsers: 2,\n\n  /** Amount of test files per browser to test concurrently */\n  concurrency: 1\n\n  /** Browsers to run tests on */\n  // browsers: [\n  //   playwrightLauncher({ product: 'chromium' }),\n  //   playwrightLauncher({ product: 'firefox' }),\n  //   playwrightLauncher({ product: 'webkit' }),\n  // ],\n\n  // See documentation for all available options\n})\n"
  }
]