[
  {
    "path": "README.md",
    "content": "# Clip-path converter\n\nThe converter is just preparing SVG path to be used in CSS clip-path.\n\nIf we'll take a path from editor or ordinary SVG icon, it'll have absolute coordinates and [will not be stretched](https://codepen.io/yoksel/pen/QWWWRqv). There are some [funny](https://twitter.com/AlaricBaraou/status/1180958279570280448) [hacks](https://twitter.com/paullebeau/status/1181169153287155712) to solve this problem, but I think, it will be easier to translate coordinates from absolute to relative and just use path.\n\nRead about clip-path:\n\n* [Clip-path on MDN](https://developer.mozilla.org/ru/docs/Web/CSS/clip-path)\n* [Clipping, Clipping, and More Clipping!](https://css-tricks.com/clipping-clipping-and-more-clipping/)\n* [Animating with Clip-Path](https://css-tricks.com/animating-with-clip-path/)\n* [and more...](https://css-tricks.com/tag/clip-path/)\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>Clip-path converter</title>\n    <link rel=\"stylesheet\" href=\"style.css\">\n  </head>\n  <body>\n    <div class=\"wrapper\">\n      <header class=\"page-header\">\n        <h1>Convert SVG absolute clip-path to relative</h1>\n      </header>\n\n      <main class=\"main\">\n        <section class=\"section\">\n          <h2 class=\"visuallyhidden\">Codes</h2>\n\n          <div class=\"container\">\n            <header class=\"container__header\">\n              <h3>\n                <label for=\"src-text\">\n                  Insert clip-path coordinates for <code>userSpaceOnUse</code>:\n                </label>\n              </h3>\n            </header>\n\n            <textarea\n              id=\"src-text\"\n              placeholder=\"M 20,20 h 160 v 160 h -80 v -80 h -80 z\"\n              spellcheck=\"false\"\n            ></textarea>\n          </div>\n\n          <div class=\"container\">\n            <header class=\"container__header\">\n              <h3>\n                <label for=\"result-text\">\n                  Take clip-path coordinates for <code>objectBoundingBox</code>:\n                </label>\n              </h3>\n\n              <label class=\"container__label\">\n                <input\n                  type=\"checkbox\"\n                  id=\"remove-offsets-control\"\n                >\n                  Remove offset\n              </label>\n            </header>\n\n            <textarea\n              id=\"result-text\"\n              placeholder=\"\"\n              spellcheck=\"false\"></textarea>\n          </div>\n        </section>\n\n        <section class=\"section\">\n          <h2 class=\"\">Live demos</h2>\n\n          <div class=\"result\"></div>\n        </section>\n\n        <section class=\"section\">\n          <h2 class=\"\">All together now</h2>\n\n          <div class=\"full-codes\">\n            <div class=\"\n              full-codes__item\"\n              >\n              <h3>\n                <label for=\"css-code\">CSS</label>\n              </h3>\n              <textarea\n                id=\"css-code\"\n                class=\"\n                  full-codes__css-code\"\n                ></textarea>\n\n              <h3>\n                <label for=\"html-code\">SVG + HTML</label>\n              </h3>\n              <textarea\n                id=\"html-code\"\n                class=\"\n                  full-codes__html-code\"\n                ></textarea>\n            </div>\n\n            <div class=\"\n              full-codes__result\n              full-codes__item\"\n              >\n              <style>\n                .svg {\n                  position: absolute;\n                  width: 0;\n                  height: 0;\n                }\n                .clipped {\n                  width: 100%;\n                  height: 350px;\n                  background: turquoise url(https://source.unsplash.com/600x600?summer);\n                  background-size: cover;\n                  -webkit-clip-path: url(#my-clip-path);\n                  clip-path: url(#my-clip-path);\n                }\n              </style>\n\n              <svg class=\"svg\">\n                <clipPath id=\"my-clip-path\" clipPathUnits=\"objectBoundingBox\"><path d=\"M0.5,0, L1,1, H0\"></path></clipPath>\n              </svg>\n\n              <div class=\"clipped\"></div>\n\n            </div>\n          </div>\n        </section>\n\n        <section class=\"section\">\n          <h2 class=\"\">About</h2>\n\n          <p>The converter is just preparing SVG path to be used in CSS clip-path.</p>\n\n          <p>If we'll take a path from editor or ordinary SVG icon, it'll have absolute coordinates and <a href=\"https://codepen.io/yoksel/pen/QWWWRqv\">will not be stretched</a>. There are some <a href=\"https://twitter.com/AlaricBaraou/status/1180958279570280448\">funny</a> <a href=\"https://twitter.com/paullebeau/status/1181169153287155712\">hacks</a> to solve this problem, but I think, it will be easier to translate coordinates from absolute to relative and just use path.</p>\n\n          <p>Read about clip-path:</p>\n\n          <ul>\n            <li>\n              <a href=\"https://developer.mozilla.org/ru/docs/Web/CSS/clip-path\">Clip-path on MDN</a>\n            </li>\n            <li>\n              <a href=\"https://css-tricks.com/clipping-clipping-and-more-clipping/\">Clipping, Clipping, and More Clipping!</a>\n            </li>\n            <li>\n              <a href=\"https://css-tricks.com/animating-with-clip-path/\">Animating with Clip-Path</a>\n            </li>\n            <li>\n              <a href=\"https://css-tricks.com/tag/clip-path/\">and more...</a>\n            </li>\n          </ul>\n        </section>\n      </main>\n\n      <footer class=\"footer\">\n        <a href=\"https://github.com/yoksel/relative-clip-path/\">Project on GitHub</a>\n        <a href=\"https://twitter.com/yoksel_en\">@yoksel_en</a>\n      </footer>\n    </div>\n\n    <template id=\"demos-tmpl\">\n      <div class=\"demos\">\n        <div class=\"demos__svg-container\">\n          <svg>\n            <clipPath id=\"clip-path-before\" clipPathUnits=\"userSpaceOnUse\"></clipPath>\n            <clipPath id=\"clip-path-after\" clipPathUnits=\"objectBoundingBox\"></clipPath>\n          </svg>\n        </div>\n\n        <div class=\"demos__item\">\n          <h3>Svg with initial path</h3>\n          <div class=\"src-container\">\n            <svg>\n\n            </svg>\n          </div>\n        </div>\n\n        <div class=\"demos__item\">\n          <h3>Clip-path with initial path</h3>\n          <div class=\"demo\">\n            <div class=\"demo__target demo__target--before\"></div>\n          </div>\n        </div>\n\n        <div class=\"demos__item\">\n          <h3>Clip-path with relative path</h3>\n          <div class=\"demo\">\n            <div class=\"demo__target demo__target--after\"></div>\n          </div>\n        </div>\n      </div>\n    </template>\n\n    <script src=\"script.js\"></script>\n    <script>\n      const pathConverter = new PathConverter({\n        srcTextElem: document.getElementById('src-text'),\n        resultTextElem: document.getElementById('result-text'),\n        demoTargetElem: document.querySelector('.result'),\n        addExamples: true\n      });\n    </script>\n\n    <!-- Yandex.Metrika counter --> <script type=\"text/javascript\" > (function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; m[i].l=1*new Date();k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) (window, document, \"script\", \"https://mc.yandex.ru/metrika/tag.js\", \"ym\"); ym(55838086, \"init\", { clickmap:true, trackLinks:true, accurateTrackBounce:true }); </script> <noscript><div><img src=\"https://mc.yandex.ru/watch/55838086\" style=\"position:absolute; left:-9999px;\" alt=\"\" /></div></noscript> <!-- /Yandex.Metrika counter -->\n  </body>\n</html>\n"
  },
  {
    "path": "script.js",
    "content": "'use strict';\n\nconst keysListByCommand = {\n  'm': ['x', 'y'],\n  'l': ['x', 'y'],\n\n  'h': ['x'],\n  'v': ['y'],\n\n  'c': ['x', 'y', 'x1', 'y1', 'x2', 'y2'],\n\n  's': ['x', 'y', 'x1', 'y1'],\n  'q': ['x', 'y', 'x1', 'y1'],\n  't': ['x', 'y', 'x1', 'y1'],\n\n  'a': ['rx', 'ry', 'x-axis-rotation', 'large-arc-flag', 'sweep-flag', 'x', 'y']\n};\n\nconst examples = {\n  dev: [\n    `M 20, 20 h100 v100 h-100 v-100`,\n    `M 20,20 h 160 v 160 h -80 v -80 h -80 z`,\n    `M 10,110 L 10,10 L 40,50 L 70,10 L 100,50 L 130,10 L 130,110 z`,\n    `M 100,20 L 180,160 L 20,160 z`,\n    `M 20 20 C 150,20 150,150 20,150`,\n    `M 20 20 c 150,20 150,150 20,150`,\n    `M 20 20 S 180,100 20,180`,\n    `M 20 20 s 180,100 20,180`,\n    `M 20 20 Q 180,100 20,180`,\n    `M 20 20 q 180,100 20,180`,\n    `M 20 20 T 60,100 180,20`,\n    `M 20 20 t 60,100 180,20`,\n    `M 20 20 A20,35 0 0,0 170,2`,\n    `M 20 20 a20,35 0 0,0 170,20`,\n    `M-2.068 13.842 c.61-3.7.54-10.906 4.9-13.41C5.85-1.301 9.713-.95 12.888-2.375c4.495-2.018 20.803-14.593 24.675-2.077C42.67 12.056 11.648 19.04 5.41 23.343.868 26.476-.92 32.757-5.567 35.733-11.095 39.277-11 34.226-8.1 25.465c1.119-3.379 3.593-6.145 5.39-9.218l.641-2.404M60.637-5.517C57.199.635 55.488 8.145 50.322 12.94c-4.752 4.41-12.284 4.39-17.862 7.694-2.756 1.632-3.927 5.303-6.722 6.866C7.888 37.48 9.797 31.064-.201 43.94c-3.65 4.7-11.087 11.117-4.483 18.219C8.88 76.745 25.297 43.233 34.62 36.065c6.454-4.961 15.45 2.875 21.287-2.523 4.028-3.726 16.31-31.022 16.796-31.317 3.268-1.983 7.906 1.428 11.469.042 1.42-.552 1.063-3.34.16-4.566-1.572-2.13-4.291-3.158-6.688-4.28-5.273-2.469-7.166-1.577-12.856-1.187l-4.152 2.25m26.23 11.94c-4.02 10.025-6.343 15.697-6.967 17.017-7.364 15.555-16.236 12.045-29.24 17.303-5.971 2.414-13.054 5.302-20.772 12.712-5.713 5.485-10.439 11.97-16.356 17.234-1.056.94-32.052 12.4-15.503 19.463 28.385 12.116 48.63-5.97 69.156-23.08 10.697-8.918 19.352-11.049 25.972-23.488 6.448-12.117 20.54-32.072 8.968-45.586-.47-.548 9.656 2.11-2.102-.53-7.839-1.76-12.224 1.225-13.156 8.955m20.918 33.558c-17.304 6.067-9.489 32.207-22.927 41.13-7.084 4.703-15.023 8.21-23.146 10.726-12.302 3.81-25.412 4.51-37.768 8.14-4.412 1.297-10.616 2.194-12.153 6.528-1.045 2.946 6.079 1.692 9.203 1.793 5.958.191 11.971.005 17.86-.922 21.751-3.424 2.407-3.568 23.942-11.005 18.237-6.297 15.973 2.3 32.313-15.927 5.787-6.454 24.376-27.448 15.073-37.41l-2.397-3.053m-7.426 47.405c-3.55 3.957-15.246 9.203-10.648 11.87l-1.446 6.479c2.938 1.705 26.775-3.52 21.259-10.682-2.214-2.874-5.65-4.552-8.475-6.828l-.69-.84z`,\n    `M89.118.892l23.177 83.96 62.052-61.123.657 87.098 75.757-42.98-21.908 84.3 84.3-21.908-42.98 75.757 87.098.657-61.123 62.052 83.96 23.177-75.1 44.118 75.1 44.118-3.195.882H0V4.087L.882.892 45 75.992z`,\n    // negative coordinates will be broken\n    `M-18113-1279h14v4.01h4v10.22a.4.4 0 0 1-.14.32l-5.32 5.32a.43.43 0 0 1-.34.14`,\n  ],\n  prod: [\n    `M281.5 0L563 563H0z`,\n    `M276.5 444.707l-170.752 80.812 24.093-187.367L.218 200.731l185.642-34.987L276.5 0l90.64 165.744 185.642 34.987-129.623 137.421 24.093 187.367z`,\n    `M269 0l190.212 78.788L538 269l-78.788 190.212L269 538 78.788 459.212 0 269 78.788 78.788z`,\n    `M381.5 585.822L279.396 762.558l-.057-204.11-176.793 102.006L204.552 483.66l-204.11-.057L177.178 381.5.442 279.396l204.11-.057-102.006-176.793L279.34 204.552l.057-204.11L381.5 177.178 483.604.442l.057 204.11 176.793-102.006L558.448 279.34l204.11.057L585.822 381.5l176.736 102.104-204.11.057 102.006 176.793L483.66 558.448l-.057 204.11z`,\n    `M32 18.451l-16-12.42-16 12.42v-5.064l16-12.42 16 12.42zM28 18v12h-8v-8h-8v8h-8v-12l12-9z`,\n    `M15 0v0c8.284 0 15 5.435 15 12.139s-6.716 12.139-15 12.139c-0.796 0-1.576-0.051-2.339-0.147-3.222 3.209-6.943 3.785-10.661 3.869v-0.785c2.008-0.98 3.625-2.765 3.625-4.804 0-0.285-0.022-0.564-0.063-0.837-3.392-2.225-5.562-5.625-5.562-9.434 0-6.704 6.716-12.139 15-12.139zM31.125 27.209c0 1.748 1.135 3.278 2.875 4.118v0.673c-3.223-0.072-6.181-0.566-8.973-3.316-0.661 0.083-1.337 0.126-2.027 0.126-2.983 0-5.732-0.805-7.925-2.157 4.521-0.016 8.789-1.464 12.026-4.084 1.631-1.32 2.919-2.87 3.825-4.605 0.961-1.84 1.449-3.799 1.449-5.825 0-0.326-0.014-0.651-0.039-0.974 2.268 1.873 3.664 4.426 3.664 7.24 0 3.265-1.88 6.179-4.82 8.086-0.036 0.234-0.055 0.474-0.055 0.718z`,\n    `M12 0l-12 16h12l-8 16 28-20h-16l12-12z`,\n    `M31.604 4.203c-3.461-2.623-8.787-4.189-14.247-4.189-6.754 0-12.257 2.358-15.099 6.469-1.335 1.931-2.073 4.217-2.194 6.796-0.108 2.296 0.278 4.835 1.146 7.567 2.965-8.887 11.244-15.847 20.79-15.847 0 0-8.932 2.351-14.548 9.631-0.003 0.004-0.078 0.097-0.207 0.272-1.128 1.509-2.111 3.224-2.846 5.166-1.246 2.963-2.4 7.030-2.4 11.931h4c0 0-0.607-3.819 0.449-8.212 1.747 0.236 3.308 0.353 4.714 0.353 3.677 0 6.293-0.796 8.231-2.504 1.736-1.531 2.694-3.587 3.707-5.764 1.548-3.325 3.302-7.094 8.395-10.005 0.292-0.167 0.48-0.468 0.502-0.804s-0.126-0.659-0.394-0.862z`,\n    `M31 16l-15-15v9h-16v12h16v9z`,\n    `M 0 0 C 130,0 130,130 0,130`,\n    'M0 0c4.255 136.009 8.278 212.553 12.07 229.633 4.147 18.678 7.825 103.926 65.305 103.926 57.48 0 82.23-25.727 69.855-100.141s-17.414-105.914 41.024-105.914c58.437 0 35.827 89.276 30.43 107.129-15.67 51.827-30.43 154.945 34.535 163.484 64.965 8.54 73.97-46.797 75.738-86.5 1.717-38.571-18.478-164.61 35.828-164.61 56.871 0 70.925 63.998 51.457 116.333-41.035 110.316-19.335 193.898 63.137 193.898 58.195 0 88.983-73.722 92.363-221.168L580.578 0H0z',\n    `M1.335-.013l13.688 32.028 27.425-21.472 9.566 33.49 29.9-17.865 5.293 34.426 31.902-13.977.937 34.818 33.403-9.868-3.434 34.66 34.376-5.604-7.751 33.957 34.808-1.251-11.947 32.717 34.69 3.121-15.952 30.963 34.025 7.444-19.707 28.719 32.824 11.65-23.151 26.022 31.105 15.672-26.23 22.916L276 332l-28.896 19.447 26.23 22.916-31.104 15.672 23.151 26.022-5.475 1.943H0V1.333z`,\n  ]\n};\n\nconst PathConverter = function (params) {\n  this.srcTextElem = params.srcTextElem;\n  this.resultTextElem = params.resultTextElem;\n  const demoTargetElem = params.demoTargetElem;\n  const removeOffsetControl = document.getElementById('remove-offsets-control');\n\n  this.fullCodes = {\n    spaces: {},\n    path: document.querySelector('.full-codes__result path'),\n\n    css: document.querySelector('.full-codes__result style'),\n    svg: document.querySelector('.full-codes__result svg'),\n    html: document.querySelector('.full-codes__result div'),\n\n    cssCode: document.querySelector('.full-codes__css-code'),\n    htmlCode: document.querySelector('.full-codes__html-code'),\n  };\n\n  this.fullCodes.cssCode.value = this.removeStartSpaces(this.fullCodes.css.innerHTML, 'css');\n\n  this.isRemoveOffset = removeOffsetControl.checked;\n  this.isDebug = location.search.includes('debug');\n  this.lastUsedCoords = {};\n\n  // Add examples if needed\n\n  const isAddExamples = params.addExamples;\n  this.examples = examples.prod;\n\n  this.currentExamplePos = Math.floor(Math.random() * this.examples.length);\n  const randomExample = this.examples[this.currentExamplePos];\n\n  if(isAddExamples) {\n    this.addExamples();\n  }\n\n  // Prepare DOM\n\n  const demosTmpl = document.getElementById('demos-tmpl');\n  demoTargetElem.appendChild(demosTmpl.content.cloneNode(true));\n\n  const demoSVG = demoTargetElem.querySelector('.src-container svg');\n  this.demoPath = document.createElementNS(\"http://www.w3.org/2000/svg\", 'path');\n  demoSVG.appendChild(this.demoPath);\n\n  this.demoClipPathBefore = demoTargetElem.querySelector('#clip-path-before');\n  this.demoClipPathAfter = demoTargetElem.querySelector('#clip-path-after');\n\n  // Add demo code\n\n  this.srcTextElem.value = randomExample;\n  this.coords = randomExample;\n\n  // Actions\n\n  this.updateView();\n\n  this.srcTextElem.addEventListener('input', () => {\n    this.coords = this.srcTextElem.value;\n    this.updateView();\n  })\n\n  removeOffsetControl.addEventListener('change', () => {\n    this.isRemoveOffset = removeOffsetControl.checked;\n    this.updateView();\n  });\n}\n\n\n\n// ---------------------------------------------\n// Add examples\n// ---------------------------------------------\n\nPathConverter.prototype.addExamples = function () {\n  const examplesWrapper = document.createElement('div');\n  examplesWrapper.classList.add('examples');\n  const examplesContainer = document.createElement('div');\n  examplesContainer.classList.add('examples__container');\n  examplesWrapper.appendChild(examplesContainer);\n\n  examplesWrapper.classList.add('visuallyhidden');\n  this.srcTextElem.parentNode.appendChild(examplesWrapper);\n  let currentControl = null;\n\n  this.examples.forEach((item, index) => {\n    const control = document.createElement('button');\n    control.innerHTML = `<svg><path d='${item}'/></svg>`;\n    examplesContainer.appendChild(control);\n    const svg = control.querySelector('svg');\n    const path = control.querySelector('path');\n    const pathSizes = path.getBBox();\n    const viewBox = `0 0 ${pathSizes.width} ${pathSizes.height}`;\n    svg.setAttribute('viewBox', viewBox);\n    control.classList.add('examples__control');\n\n    if(this.currentExamplePos === index) {\n      control.classList.add('examples__control--current');\n      currentControl = control;\n    }\n\n    control.addEventListener('click', () => {\n      if(this.currentExamplePos === index) {\n        return;\n      }\n\n      currentControl.classList.remove('examples__control--current');\n\n      this.srcTextElem.value = item;\n      this.coords = this.srcTextElem.value;\n      this.updateView();\n\n      this.currentExamplePos = index;\n      control.classList.add('examples__control--current');\n      currentControl = control;\n    });\n  })\n\n  examplesWrapper.classList.remove('visuallyhidden');\n}\n\n\n\n// ---------------------------------------------\n// Update view\n// ---------------------------------------------\n\nPathConverter.prototype.updateView = function () {\n  this.demoPath.setAttribute('d', this.coords);\n  this.pathSizes = this.demoPath.getBBox();\n\n  // Show initial path\n  this.demoClipPathBefore.innerHTML = '';\n  this.demoClipPathBefore.appendChild(this.demoPath.cloneNode(true));\n\n  // Normalize coordinates list formating\n  const coordsNormalized = normalizePathCoords(this.coords);\n  // Collect all cordinates set from char to next char (next char not includes)\n  const coordsListSrc = [...coordsNormalized.matchAll(/[a-z][^(a-z)]{1,}/gi)]\n    .map(item => item[0]);\n  let coordsList = coordsListSrc.slice();\n  // Add omitted commands for more correct parsing\n  coordsList = this.addOmittedCommands(coordsListSrc.slice());\n\n  if(this.isRemoveOffset) {\n    // Remove path offset\n    coordsList = this.removeOffset(coordsList);\n  }\n\n  // Convert coordinates to relative\n  const coordsTransformed = this.transformCoords(coordsList);\n\n  let resultPath = coordsTransformed.join(' ');\n  this.demoClipPathAfter.innerHTML = '';\n\n  if(resultPath.includes('Infinity')) {\n    this.resultTextElem.value = 'Source path is not correct';\n    return;\n  }\n\n  const pathAfter = this.demoClipPathAfter.appendChild(this.demoPath.cloneNode(true));\n  pathAfter.setAttribute('d', resultPath);\n  this.resultTextElem.value = resultPath;\n\n  this.fullCodes.path.setAttribute('d', resultPath);\n\n  const svgOutputCode = this.removeStartSpaces(this.fullCodes.svg.outerHTML, 'svg');\n  const htmlOutputCode = this.removeStartSpaces(this.fullCodes.html.outerHTML, 'html');\n  this.fullCodes.htmlCode.value = `${svgOutputCode}\\n\\n${htmlOutputCode}`;\n}\n\n\n\n// ---------------------------------------------\n// Add ommitted command letters for easy parsing\n// ---------------------------------------------\n\nPathConverter.prototype.addOmittedCommands = function (srcCoordsList) {\n  srcCoordsList = srcCoordsList.slice();\n  const coordsFixed = [];\n  const max = 5000;\n  let counter = 0;\n  const handledCommands = {\n    'a': true,\n    't': true,\n    'c': true,\n    's': true,\n    'q': true,\n  }\n\n  while(srcCoordsList.length > 0 && counter < max) {\n    let value = srcCoordsList.shift();\n    let {commandSrc, command, coordsList, keysList} = parseCoordsItem(value);\n\n    if(keysList) {\n      let coords;\n\n      if(handledCommands[command] && coordsList.length > keysList.length) {\n        // Fix problem with long commands like A\n        const cuttedTail = coordsList.splice(keysList.length);\n        coords = coordsList.join(',');\n\n        if(cuttedTail.length % keysList.length === 0) {\n          // Move part of command to the next item\n          cuttedTail[0] = `${commandSrc}${cuttedTail[0]}`;\n          srcCoordsList.unshift(cuttedTail.join(','));\n        }\n        else {\n          console.log('\\nCommand is broken, check params:', coordsList);\n        }\n      }\n      else {\n        coords = coordsList.join(',');\n      }\n\n      value = `${commandSrc}${coords}`;\n    }\n    else {\n      console.log('Unrecognized command: ', command);\n    }\n\n    coordsFixed.push(value);\n    counter++;\n  }\n\n  return coordsFixed;\n}\n\n\n\n// ---------------------------------------------\n// Translate relative commands to absolute\n// (l -> L, a -> A, ...)\n// ---------------------------------------------\n\nPathConverter.prototype.relCommandsToAbs = function(initialCoordsList) {\n  initialCoordsList = initialCoordsList.slice();\n\n  const coordsAbs = initialCoordsList.reduce((prev, value, index) => {\n    let {commandSrc, command, coordsList, keysList, isCommandUpperCase} = parseCoordsItem(value);\n\n    if(command === 'm') {\n      const [x, y] = coordsList;\n      this.lastUsedCoords.x = x;\n      this.lastUsedCoords.y = y;\n    }\n\n    if(!isCommandUpperCase) {\n      const prevCoords = prev[index - 1];\n      const absItemCoords = this.relItemCoordToAbs(keysList, coordsList, prevCoords, command);\n      value = `${commandSrc.toUpperCase()}${absItemCoords.join(',')}`;\n    }\n\n    prev.push(value);\n\n    return prev;\n  }, []);\n\n  return coordsAbs;\n}\n\n// ---------------------------------------------\n\nPathConverter.prototype.relItemCoordToAbs = function (keysList, coordsList, prevCoords, itemCommand) {\n  const valuesList = coordsList;\n  const prevCoordsData = parseCoordsItem(prevCoords);\n  const prevCoordsList = prevCoordsData.coordsList;\n  let [prevX, prevY] = prevCoordsList.splice(-2);\n\n  if(prevCoordsData.command === 'v') {\n    prevY = prevX;\n    prevX = this.lastUsedCoords.x;\n  }\n  else if(prevCoordsData.command === 'h') {\n    prevY = this.lastUsedCoords.y;\n  }\n\n  const transformedValuesList = valuesList.map((item, index) => {\n    const key = keysList[index];\n\n    if(!isFinite(item)) {\n      console.log('Not finite item:', item);\n      return item;\n    }\n\n    if(key && (key.includes('rotation')\n      || key.includes('flag')\n      || key === 'rx'\n      || key === 'ry')) {\n      return item;\n    }\n\n    if(!key && itemCommand !== 'a') {\n      // Commands can use more than two coords\n      if(index % 2 == 0) {\n        // x\n        const newX = item + prevX;\n        if(itemCommand === 'l') {\n          prevX = newX;\n        }\n        this.lastUsedCoords.x = newX;\n        return newX;\n      }\n      else {\n        // y\n        const newY = item + prevY;\n        if(itemCommand === 'l') {\n          prevY = newY;\n        }\n        this.lastUsedCoords.y = newY;\n        return newY;\n      }\n    }\n\n    if(key.includes('x')) {\n      const newX = item + prevX;\n      if(itemCommand === 'l') {\n        prevX = newX;\n      }\n      this.lastUsedCoords.x = newX;\n      return newX;\n    }\n\n    if(key.includes('y')) {\n      const newY = item + prevY;\n      if(itemCommand === 'l') {\n        prevY = newY;\n      }\n      this.lastUsedCoords.y = newY;\n      return newY;\n    }\n\n    return item;\n  });\n\n  return transformedValuesList;\n}\n\n\n\n// ---------------------------------------------\n// Removing path offset\n// ---------------------------------------------\n\nPathConverter.prototype.removeOffset = function (srcCoordsList) {\n  // Find minimal value\n  srcCoordsList = srcCoordsList.slice();\n  // Make easier to get offset\n  const absCoordsList = this.relCommandsToAbs(srcCoordsList.slice());\n\n  srcCoordsList = absCoordsList;\n  this.minXY = this.findOffset(absCoordsList);\n  const coordsWithoutOffset = [];\n\n  const max = 5000;\n  let counter = 0;\n\n  while(srcCoordsList.length > 0 && counter < max) {\n    let value = srcCoordsList.shift();\n    let {commandSrc, command, coordsList, keysList, isCommandUpperCase} = parseCoordsItem(value);\n\n    if(keysList) {\n      if(isCommandUpperCase) {\n        const transformedValsList = this.removeOffsetFromValues(keysList, coordsList, command)\n        value = `${commandSrc}${transformedValsList.join(',')}`;\n      }\n\n      coordsWithoutOffset.push(value);\n    }\n    else {\n      console.log('Unrecognized command: ', command);\n    }\n    counter++;\n  }\n\n  return coordsWithoutOffset;\n}\n\n// ---------------------------------------------\n\nPathConverter.prototype.removeOffsetFromValues = function (keysList, coordsList, itemCommand) {\n  const valuesList = coordsList;\n\n  const transformedValuesList = valuesList.map((item, index) => {\n    if(!keysList[index] && itemCommand !== 'a') {\n      // L lets use more than two coords\n      if(index % 2 == 0) {\n        // x\n        return item - this.minXY.x;\n      }\n      else {\n        // y\n        return item - this.minXY.y;\n      }\n    }\n\n    if(keysList[index].includes('rotation')\n      || keysList[index].includes('flag')\n      || keysList[index] === 'rx'\n      || keysList[index] === 'ry') {\n      return item;\n    }\n\n    if(keysList[index].includes('x')) {\n      return item - this.minXY.x;\n    }\n\n    if(keysList[index].includes('y')) {\n      return item - this.minXY.y;\n    }\n\n    return item;\n  });\n\n  return transformedValuesList;\n}\n\n// ---------------------------------------------\n\nPathConverter.prototype.findOffset = function (srcCoordsList) {\n  // Find minimal value\n  srcCoordsList = srcCoordsList.slice();\n  let minXY = { x: null, y: null};\n  const max = 5000;\n  let counter = 0;\n\n  while(srcCoordsList.length > 0 && counter < max) {\n    let value = srcCoordsList.shift();\n    let {commandSrc, command, coordsList, keysList, isCommandUpperCase} = parseCoordsItem(value);\n\n    if(!isCommandUpperCase) {\n      continue;\n    }\n\n    if(command == 'm' && minXY.x === null) {\n      const [x, y] = coordsList;\n      minXY = {x, y};\n\n      // For correct handling v & h\n      this.lastUsedCoords = {x, y};\n      continue;\n    }\n\n    const itemMinXY = this.findItemMinXY(keysList, coordsList, command);\n\n    if(itemMinXY.x >= 0 && itemMinXY.x < minXY.x) {\n      minXY.x = itemMinXY.x;\n    }\n    if(itemMinXY.y >= 0 && itemMinXY.y < minXY.y) {\n      minXY.y = itemMinXY.y;\n    }\n\n    counter++;\n  }\n\n  return minXY;\n}\n\n// ---------------------------------------------\n\nPathConverter.prototype.findItemMinXY = function(keysList, coordsList, itemCommand) {\n  let valuesList = coordsList;\n  let minXY = {x: null, y: null};\n  const max = 10000;\n  let counter = 0;\n\n  // Handling short paths\n  if(itemCommand === 'v') {\n    valuesList = [\n      this.lastUsedCoords.x,\n      valuesList[0]\n    ]\n  }\n  else if(itemCommand === 'h') {\n    valuesList = [\n      valuesList[0],\n      this.lastUsedCoords.y\n    ]\n  }\n  else if(itemCommand === 'a') {\n    valuesList = valuesList.splice(-2);\n  }\n\n  if(valuesList.length === 2) {\n    const [x, y] = valuesList;\n    minXY = {x, y};\n\n    this.lastUsedCoords = {x, y};\n\n    return minXY;\n  }\n\n  // Handling long paths\n  while(valuesList.length > 0 && counter < max) {\n    let [x, y] = valuesList.splice(0,2);\n    if(minXY.x === null) {\n      minXY = {x, y};\n\n      continue;\n    }\n\n    if(x >= 0 && x < minXY.x) {\n      minXY.x = x;\n    }\n    if(y >= 0 && y < minXY.y) {\n      minXY.y = y;\n    }\n\n    counter++;\n  }\n\n  return minXY;\n}\n\n\n\n// ---------------------------------------------\n// Transforming coordinates from userSpaceOnUse\n// coordinate system to objectBoundingBox\n// M281.5 0L563 563H0z -> M0.5,0, L1,1, H0\n// ---------------------------------------------\n\nPathConverter.prototype.transformCoords = function (srcCoordsList) {\n  srcCoordsList = srcCoordsList.slice();\n  const coordsTransformed = [];\n  const max = 5000;\n  let counter = 0;\n\n  while(srcCoordsList.length > 0 && counter < max) {\n    let value = srcCoordsList.shift();\n    let {commandSrc, command, coordsList, keysList} = parseCoordsItem(value);\n\n    if(keysList) {\n      const transformedValsList = this.transformValuesByKeys(keysList, coordsList, command)\n      value = `${commandSrc}${transformedValsList.join(',')}`;\n    }\n    else {\n      console.log('Unrecognized command: ', command);\n    }\n\n    coordsTransformed.push(value);\n    counter++;\n  }\n\n  return coordsTransformed;\n}\n\n// ---------------------------------------------\n\nPathConverter.prototype.transformValuesByKeys = function (keysList, coordsList, itemCommand) {\n  const valuesList = coordsList;\n\n  const transformedValuesList = valuesList.map((item, index) => {\n    if(!keysList[index] && itemCommand !== 'a') {\n      // L lets use more than two coords\n      if(index % 2 == 0) {\n        return this.getTransformedByKey('width', item);\n      }\n      else {\n        return this.getTransformedByKey('height', item);\n      }\n    }\n\n    if(keysList[index].includes('rotation')|| keysList[index].includes('flag')) {\n      return item;\n    }\n\n    if(keysList[index].includes('x')) {\n      return this.getTransformedByKey('width', item);\n    }\n\n    if(keysList[index].includes('y')) {\n      return this.getTransformedByKey('height', item);\n    }\n\n    return item;\n  });\n\n  return transformedValuesList;\n}\n\n// ---------------------------------------------\n\nPathConverter.prototype.getTransformedByKey = function (key = 'height', value) {\n  let result = 0;\n  if(key === 'width') {\n    result = round(value/this.pathSizes.width);\n  }\n  else {\n    result = round(value/this.pathSizes.height);\n  }\n\n  // Reduce of maximum coordinates to 1\n  if(result > 1) {\n    result = Math.floor(result);\n  }\n\n  return result;\n}\n\n\n\n// ---------------------------------------------\n// Removing start spaces from output codes\n// ---------------------------------------------\n\nPathConverter.prototype.removeStartSpaces = function (str, key) {\n  str = str.trim();\n  let minSpace = this.fullCodes.spaces[key];\n\n  if(minSpace === undefined) {\n    minSpace = findMinSpaces(str);\n    this.fullCodes.spaces[key] = minSpace;\n  }\n\n  const regexp = new RegExp(`^\\\\s{${minSpace}}`,'gm');\n\n  return str.replace(regexp,'');\n}\n\n\n\n// ---------------------------------------------\n// Helpers\n// ---------------------------------------------\n\nfunction findMinSpaces(str) {\n  const spaces = str\n    .match(/(^\\s{1,})/gm);\n\n  if(!spaces) {\n    return 0;\n  }\n  const minSpace = spaces\n    .reduce((prev, item) => {\n      const spacesLength = item.length;\n      if(prev == null || spacesLength < prev) {\n        prev = spacesLength;\n        return prev;\n      }\n\n      return prev;\n    }, null);\n\n  return minSpace;\n}\n\n// ---------------------------------------------\n\nfunction parseCoordsItem(item) {\n  const commandSrc = item.substring(0,1);\n  const command = commandSrc.toLowerCase();\n  const isCommandUpperCase = command !== commandSrc;\n  const keysList = keysListByCommand[command];\n  let coordsList = item\n    .substring(1)\n    .replace(/,$/,'')\n    .split(',')\n    .map(item => +item);\n\n  return {\n    commandSrc,\n    command,\n    isCommandUpperCase,\n    keysList,\n    coordsList\n  }\n}\n\n// ---------------------------------------------\n\nfunction normalizePathCoords(coords) {\n  let result = coords\n    .replace(/([a-z]) /gi, '$1')\n    .replace(/([a-z])/gi, ' $1')\n    .trim()\n\n    .replace(/(\\d{1,})(-)/gi, '$1 $2')\n    .replace(/\\s00/gi, ' 0 0 ')\n    .replace(/z/gi, ' ')\n\n    .replace(/,\\s{1,}/gi, ',')\n    .replace(/\\s{1,},/gi, ',')\n    .replace(/\\s{1,}/gi, ',');\n\n  // .345.279\n  while(result.match(/\\.\\d{1,}\\.\\d{1,}/gi)) {\n    result = result.replace(/(\\.\\d{1,})(\\.\\d{1,})/gi, '$1,$2');\n  }\n\n  return result;\n}\n\n// ---------------------------------------------\n\nfunction round(num) {\n  return Math.round(num * 1000) / 1000;\n}\n"
  },
  {
    "path": "style.css",
    "content": "/* Base\n------------------------------ */\n\nBODY {\n  margin: 0;\n  font: 16px/1.4 \"Trebuchet MS\", Arial, sans-serif;\n  color: #000;\n}\n\nA {\n  color: steelblue;\n}\n\nH1, H2, H3, H4 {\n  font-family: Georgia, serif;\n  font-weight: normal;\n}\n\nH1 {\n  font-size: 2.5em;\n  text-align: center;\n}\n\nH2 {\n  margin: 0;\n  margin-bottom: .5em;\n  font-size: 2em;\n}\n\nH3 {\n  margin: 0;\n  margin-bottom: .35em;\n  font-size: 1.25em;\n}\n\nH4 {\n  margin: 0;\n  margin-bottom: .5em;\n  font-size: 1.1em;\n}\n\nP {\n  margin: 1.2rem 0;\n}\n\nTEXTAREA {\n  width: 100%;\n  height: 150px;\n  margin: 0;\n  padding: .5em;\n  border: 1px solid #CCC;\n  border-radius: 8px;\n  font-size: 13px;\n  font-family: monospace;\n}\n\nCODE {\n  padding: 0 3px;\n  background: #EEE;\n  border-radius: 5px;\n  text-shadow: 1px 1px 0 white;\n  font-family: monospace;\n  color: #333;\n}\n\n\n\n/* Helpers\n------------------------------ */\n\n.hidden {\n  display: none;\n  }\n\n.visuallyhidden {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  border: 0;\n  padding: 0;\n  white-space: nowrap;\n  clip-path: inset(100%);\n  clip: rect(0 0 0 0);\n  overflow: hidden;\n}\n\n\n\n/* Layout\n------------------------------ */\n\n.wrapper {\n  min-width: 375px;\n  max-width: 900px;\n  margin: auto;\n  padding: 1.5rem 2rem 4rem;\n  box-sizing: border-box;\n}\n\n.header {\n  position: relative;\n  margin-bottom: 2rem;\n  padding-bottom: 1rem;\n  border-bottom: 1px solid #CCC;\n}\n\n.footer {\n  position: relative;\n  display: flex;\n  justify-content: space-between;\n  margin-top: 3rem;\n  padding-top: 2rem;\n  border-top: 1px solid #CCC;\n}\n\n.section + .section {\n  margin-top: 2.5em;\n}\n\n\n\n/* Examples\n------------------------------ */\n.examples {\n  width: 100%;\n}\n\n.examples__container {\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 -4px;\n}\n\n.examples__control {\n  flex-basis: 40px;\n  height: 40px;\n  flex-shrink: 0;\n  margin: 4px;\n  padding: 4px;\n  background: #FFF;\n  border: 1px solid #CCC;\n  border-radius: 8px;\n  cursor: pointer;\n}\n\n.examples__control:focus {\n  outline: none;\n  box-shadow:\n    0 0 0 2px yellowgreen,\n    0 0 0 1px teal inset;\n  border-color: teal;\n}\n\n.examples__control--current {\n  box-shadow: 0 0 0 2px yellowgreen;\n  border-color: yellowgreen;\n}\n\n.examples__control svg {\n  width: 100%;\n  height: 100%;\n}\n\n\n\n/* Containers\n------------------------------ */\n\n.containers {\n  margin-bottom: 32px;\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n  }\n\n.container {\n  position: relative;\n  margin-bottom: 1em;\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n}\n\n.container__header {\n  display: flex;\n  width: 100%;\n  align-items: center;\n  justify-content: space-between;\n}\n\n.container__label {\n  display: flex;\n  align-items: center;\n  white-space: nowrap;\n  padding-left: 1em;\n}\n\n\n\n/* Demos\n------------------------------ */\n\n.demos {\n  position: relative;\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: space-between;\n}\n\n.demos svg {\n  display: block;\n  width: 100%;\n  height: 300px;\n  outline: 1px solid #CCC;\n}\n\n@media (max-width: 700px) {\n  .demos svg {\n    height: 200px;\n  }\n}\n\n.demos__svg-container {\n  position: absolute;\n  width: 0;\n  height: 0;\n  overflow: hidden;\n}\n\n.demos__item {\n  width: calc((100% - 3rem) / 3);\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n}\n\n@media (max-width: 700px) {\n  .demos__item {\n    width: 100%;\n  }\n}\n\n.demo {\n  width: 100%;\n  outline: 1px solid #CCC;\n}\n\n.demo__target {\n  width: 100%;\n  height: 300px;\n  background: url(https://source.unsplash.com/600x600?summer);\n  background-size: cover;\n}\n\n@media (max-width: 700px) {\n  .demo__target {\n    height: 200px;\n  }\n}\n\n.demo__target--before {\n  background-color: teal;\n  -webkit-clip-path: url(#clip-path-before);\n  clip-path: url(#clip-path-before);\n}\n.demo__target--after {\n  background-color: turquoise;\n  -webkit-clip-path: url(#clip-path-after);\n  clip-path: url(#clip-path-after);\n}\n\n\n\n/* Full code\n------------------------------ */\n\n.full-codes {\n  display: flex;\n  justify-content: space-between;\n}\n\n.full-codes__item {\n  width: calc((100% - 3rem) / 2);\n  height: auto;\n}\n\n.full-codes TEXTAREA + H3 {\n  margin-top: 1.5em;\n}\n\n.full-codes__result {\n  display: flex;\n  align-items: center;\n}\n"
  }
]