[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: pshihn\nopen_collective: rough\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nbin\ndist\nlib\ndemo\n"
  },
  {
    "path": ".npmignore",
    "content": "node_modules\ndemo\nsrc\ntsconfig.json\ntslint.json\n.gitignore\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Preet Shihn\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![Rough Notation logo](https://roughnotation.com/images/social.png)\n\n# Rough Notation\n\nA small JavaScript library to create and animate annotations on a web page.\n\nRough Notation uses [RoughJS](https://roughjs.com) to create a hand-drawn look and feel. Elements can be annotated in a number of different styles. Animation duration can be configured, or just turned off.\n\nRough Notation is 3.83kb in size when gzipped.\n\n[Visit website to see it in action](https://roughnotation.com/) and check out the [source code](https://github.com/pshihn/rough-notation-web) for the website\n\n## Installation\n\nYou can add rough-notation to your project via npm\n\n```\nnpm install --save rough-notation\n```\n\nOr load the ES module directly\n\n```html\n<script type=\"module\" src=\"https://unpkg.com/rough-notation?module\"></script>\n```\n\nOr load the IIFE version which created a `RoughNotation` object in your scope.\n\n```html\n<script src=\"https://unpkg.com/rough-notation/lib/rough-notation.iife.js\"></script>\n```\n\n## Usage\n\nCreate an `annotation` object by passing the element to annotate, and a config to describe the annotation style. \nOnce you have the annotation object, you can call `show()` on it to show the annotation\n\n```javascript\nimport { annotate } from 'rough-notation';\n// Or using unpkg\n// import { annotate } from 'https://unpkg.com/rough-notation?module';\n\nconst e = document.querySelector('#myElement');\nconst annotation = annotate(e, { type: 'underline' });\nannotation.show();\n```\n\n*Note: This will add an SVG element as a sibling to the element, which may be troublesome in certain situations like in a `<table>`. You may want to create an inner `<span>` or `<div>` for the content to annotate.*\n\n## Annotation Group\n\nrough-notation provides a way to order the animation of annotations by creating an annotation-group. Pass the list of annotations to create a group. When show is called on the group, the annotations are animated in order.\n\n```javascript\nimport { annotate, annotationGroup } from 'rough-notation';\n\nconst a1 = annotate(document.querySelector('#e1'), { type: 'underline' });\nconst a2 = annotate(document.querySelector('#e3'), { type: 'box' });\nconst a3 = annotate(document.querySelector('#e3'), { type: 'circle' });\n\nconst ag = annotationGroup([a3, a1, a2]);\nag.show();\n```\n\n## Live examples\nI have created some basic examples on Glitch for you to remix and play with the code:\n\n[Basic demo](https://glitch.com/~basic-rough-notation)\n\n[Annotation group demo](https://glitch.com/~annotation-group)\n\n## Configuring the Annotation\n\nWhen you create an annotation object, you pass in a config. The config only has one mandatory field, which is the `type` of the annotation. But you can configure the annotation in many ways. \n\n#### type\nThis is a mandatory field. It sets the annotation style. Following are the list of supported annotation types:\n\n* __underline__: This style creates a sketchy underline below an element.\n* __box__: This style draws a box around the element.\n* __circle__: This style draws a circle around the element.\n* __highlight__: This style creates a highlight effect as if marked by a highlighter.\n* __strike-through__: This style draws horizontal lines through the element.\n* __crossed-off__: This style draws an 'X' across the element.\n* __bracket__: This style draws a bracket around an element, usually a paragraph of text. By default on the right side, but can be configured to any or all of *left, right, top, bottom*.\n\n#### animate\nBoolean property to turn on/off animation when annotating. Default value is `true`.\n\n#### animationDuration\nDuration of the animation in milliseconds. Default is `800ms`.\n\n#### color\nString value representing the color of the annotation sketch. Default value is `currentColor`.\n\n#### strokeWidth\nWidth of the annotation strokes. Default value is `1`. \n\n#### padding\nPadding between the element and roughly where the annotation is drawn. Default value is `5` (in pixels).\nIf you wish to specify different `top`, `left`, `right`, `bottom` paddings, you can set the value to an array akin to CSS style padding `[top, right, bottom, left]` or just `[top & bottom, left & right]`.\n\n#### multiline\nThis property only applies to inline text. To annotate multiline text (each line separately), set this property to `true`. \n\n#### iterations\nBy default annotations are drawn in two iterations, e.g. when underlining, drawing from left to right and then back from right to left. Setting this property can let you configure the number of iterations. \n\n#### brackets\nValue could be a string or an array of strings, each string being one of these values: **left, right, top, bottom**. When drawing a bracket, this configures which side(s) of the element to bracket. Default value is `right`.\n\n#### rtl\nBy default annotations are drawn from left to right. To start with right to left, set this property to `true`. \n\n## Annotation Object\n\nWhen you call the `annotate` function, you get back an annotation object, which has the following methods:\n\n#### isShowing(): boolean\nReturns if the annotation is showing\n\n#### show()\nDraws the annotation. If the annotation is set to animate (default), it will animate the drawing. If called again, it will re-render the annotation, updating any size or location changes. \n\n*Note: to reanimate the annotation, call `hide()` and then `show()` again.\n\n#### hide()\nHides the annotation if showing. This is not animated. \n\n#### remove()\nUnlinks the annotation from the element. \n\n#### Updating styles\nAll the properties in the configuration are also exposed in this object. e.g. if you'd like to change the color, you can do that after the annotation has been drawn.\n\n```javascript\nconst e = document.querySelector('#myElement');\nconst annotation = annotate(e, { type: 'underline', color: 'red' });\nannotation.show();\nannotation.color = 'green';\n```\n\n*Note: the type of the annotation cannot be changed. Create a new annotation for that.*\n\n## Annotation Group Object\n\nWhen you call the `annotationGroup` function, you get back an annotation group object, which has the following methods:\n\n#### show()\nDraws all the annotations in order. If the annotation is set to animate (default), it will animate the drawing. \n\n#### hide()\nHides all the annotations if showing. This is not animated.\n\n## Wrappers\n\nOthers have created handy Rough Notation wrappers for multiple libraries and frameworks:\n\n-   [React Rough Notation](https://github.com/linkstrifer/react-rough-notation)\n-   [Svelte Rough Notation](https://github.com/dimfeld/svelte-rough-notation)\n-   [Vue Rough Notation](https://github.com/Leecason/vue-rough-notation)\n-   [Web Component Rough Notation](https://github.com/Matsuuu/vanilla-rough-notation)\n-   [Angular Rough Notation](https://github.com/mikyaj/ngx-rough-notation)\n\n## Contributors\n\n### Financial Contributors\n\nBecome a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/rough/contribute)]\n\n#### Individuals\n\n<a href=\"https://opencollective.com/rough\"><img src=\"https://opencollective.com/rough/individuals.svg?width=890\"></a>\n\n#### Organizations\n\nSupport this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/rough/contribute)]\n\n<a href=\"https://opencollective.com/rough/organization/0/website\"><img src=\"https://opencollective.com/rough/organization/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/1/website\"><img src=\"https://opencollective.com/rough/organization/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/2/website\"><img src=\"https://opencollective.com/rough/organization/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/3/website\"><img src=\"https://opencollective.com/rough/organization/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/4/website\"><img src=\"https://opencollective.com/rough/organization/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/5/website\"><img src=\"https://opencollective.com/rough/organization/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/6/website\"><img src=\"https://opencollective.com/rough/organization/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/7/website\"><img src=\"https://opencollective.com/rough/organization/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/8/website\"><img src=\"https://opencollective.com/rough/organization/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/rough/organization/9/website\"><img src=\"https://opencollective.com/rough/organization/9/avatar.svg\"></a>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"rough-notation\",\n  \"version\": \"0.5.1\",\n  \"description\": \"Create and animate hand-drawn annotations on a web page\",\n  \"main\": \"lib/rough-notation.cjs.js\",\n  \"module\": \"lib/rough-notation.esm.js\",\n  \"types\": \"lib/rough-notation.d.ts\",\n  \"scripts\": {\n    \"build\": \"rm -rf lib && tsc && rollup -c\",\n    \"lint\": \"tslint -p tsconfig.json\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/pshihn/rough-notation.git\"\n  },\n  \"keywords\": [\n    \"annotate\",\n    \"rough\",\n    \"sketchy\"\n  ],\n  \"author\": \"Preet Shihn\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/pshihn/rough-notation/issues\"\n  },\n  \"homepage\": \"https://github.com/pshihn/rough-notation#readme\",\n  \"devDependencies\": {\n    \"rollup\": \"^2.32.1\",\n    \"rollup-plugin-node-resolve\": \"^5.2.0\",\n    \"rollup-plugin-terser\": \"^6.1.0\",\n    \"roughjs\": \"^4.3.1\",\n    \"tslint\": \"^6.1.3\",\n    \"typescript\": \"^4.0.5\"\n  }\n}\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import resolve from 'rollup-plugin-node-resolve';\nimport { terser } from \"rollup-plugin-terser\";\n\nconst input = 'lib/rough-notation.js';\n\nexport default [\n  {\n    input,\n    output: {\n      file: 'lib/rough-notation.iife.js',\n      format: 'iife',\n      name: 'RoughNotation'\n    },\n    plugins: [resolve(), terser()]\n  },\n  {\n    input,\n    output: {\n      file: 'lib/rough-notation.esm.js',\n      format: 'esm'\n    },\n    plugins: [resolve(), terser()]\n  },\n  {\n    input,\n    output: {\n      file: 'lib/rough-notation.cjs.js',\n      format: 'cjs'\n    },\n    plugins: [resolve(), terser()]\n  },\n];"
  },
  {
    "path": "src/keyframes.ts",
    "content": "export function ensureKeyframes() {\n  if (!(window as any).__rno_kf_s) {\n    const style = (window as any).__rno_kf_s = document.createElement('style');\n    style.textContent = `@keyframes rough-notation-dash { to { stroke-dashoffset: 0; } }`;\n    document.head.appendChild(style);\n  }\n}"
  },
  {
    "path": "src/model.ts",
    "content": "export const SVG_NS = 'http://www.w3.org/2000/svg';\n\nexport const DEFAULT_ANIMATION_DURATION = 800;\n\nexport interface Rect {\n  x: number;\n  y: number;\n  w: number;\n  h: number;\n}\n\nexport type RoughAnnotationType = 'underline' | 'box' | 'circle' | 'highlight' | 'strike-through' | 'crossed-off' | 'bracket';\nexport type FullPadding = [number, number, number, number];\nexport type RoughPadding = number | [number, number] | FullPadding;\nexport type BracketType = 'left' | 'right' | 'top' | 'bottom';\n\nexport interface RoughAnnotationConfig extends RoughAnnotationConfigBase {\n  type: RoughAnnotationType;\n  multiline?: boolean;\n  rtl?: boolean;\n}\n\nexport interface RoughAnnotationConfigBase {\n  animate?: boolean; // defaults to true\n  animationDuration?: number; // defaulst to 1000ms\n  color?: string; // defaults to currentColor\n  strokeWidth?: number; // default based on type\n  padding?: RoughPadding; // defaults to 5px\n  iterations?: number; // defaults to 2\n  brackets?: BracketType | BracketType[]; // defaults to 'right'\n}\n\nexport interface RoughAnnotation extends RoughAnnotationConfigBase {\n  isShowing(): boolean;\n  show(): void;\n  hide(): void;\n  remove(): void;\n}\n\nexport interface RoughAnnotationGroup {\n  show(): void;\n  hide(): void;\n}"
  },
  {
    "path": "src/render.ts",
    "content": "import { Rect, RoughAnnotationConfig, SVG_NS, FullPadding, BracketType } from './model.js';\nimport { ResolvedOptions, OpSet } from 'roughjs/bin/core';\nimport { line, rectangle, ellipse, linearPath } from 'roughjs/bin/renderer';\nimport { Point } from 'roughjs/bin/geometry';\n\ntype RoughOptionsType = 'highlight' | 'single' | 'double';\n\nfunction getOptions(type: RoughOptionsType, seed: number): ResolvedOptions {\n  return {\n    maxRandomnessOffset: 2,\n    roughness: type === 'highlight' ? 3 : 1.5,\n    bowing: 1,\n    stroke: '#000',\n    strokeWidth: 1.5,\n    curveTightness: 0,\n    curveFitting: 0.95,\n    curveStepCount: 9,\n    fillStyle: 'hachure',\n    fillWeight: -1,\n    hachureAngle: -41,\n    hachureGap: -1,\n    dashOffset: -1,\n    dashGap: -1,\n    zigzagOffset: -1,\n    combineNestedSvgPaths: false,\n    disableMultiStroke: type !== 'double',\n    disableMultiStrokeFill: false,\n    seed\n  };\n}\n\nfunction parsePadding(config: RoughAnnotationConfig): FullPadding {\n  const p = config.padding;\n  if (p || (p === 0)) {\n    if (typeof p === 'number') {\n      return [p, p, p, p];\n    } else if (Array.isArray(p)) {\n      const pa = p as number[];\n      if (pa.length) {\n        switch (pa.length) {\n          case 4:\n            return [...pa] as FullPadding;\n          case 1:\n            return [pa[0], pa[0], pa[0], pa[0]];\n          case 2:\n            return [...pa, ...pa] as FullPadding;\n          case 3:\n            return [...pa, pa[1]] as FullPadding;\n          default:\n            return [pa[0], pa[1], pa[2], pa[3]];\n        }\n      }\n    }\n  }\n  return [5, 5, 5, 5];\n}\n\nexport function renderAnnotation(svg: SVGSVGElement, rect: Rect, config: RoughAnnotationConfig, animationGroupDelay: number, animationDuration: number, seed: number) {\n  const opList: OpSet[] = [];\n  let strokeWidth = config.strokeWidth || 2;\n  const padding = parsePadding(config);\n  const animate = (config.animate === undefined) ? true : (!!config.animate);\n  const iterations = config.iterations || 2;\n  const rtl = config.rtl ? 1 : 0;\n  const o = getOptions('single', seed);\n\n  switch (config.type) {\n    case 'underline': {\n      const y = rect.y + rect.h + padding[2];\n      for (let i = rtl; i < iterations + rtl; i++) {\n        if (i % 2) {\n          opList.push(line(rect.x + rect.w, y, rect.x, y, o));\n        } else {\n          opList.push(line(rect.x, y, rect.x + rect.w, y, o));\n        }\n      }\n      break;\n    }\n    case 'strike-through': {\n      const y = rect.y + (rect.h / 2);\n      for (let i = rtl; i < iterations + rtl; i++) {\n        if (i % 2) {\n          opList.push(line(rect.x + rect.w, y, rect.x, y, o));\n        } else {\n          opList.push(line(rect.x, y, rect.x + rect.w, y, o));\n        }\n      }\n      break;\n    }\n    case 'box': {\n      const x = rect.x - padding[3];\n      const y = rect.y - padding[0];\n      const width = rect.w + (padding[1] + padding[3]);\n      const height = rect.h + (padding[0] + padding[2]);\n      for (let i = 0; i < iterations; i++) {\n        opList.push(rectangle(x, y, width, height, o));\n      }\n      break;\n    }\n    case 'bracket': {\n      const brackets: BracketType[] = Array.isArray(config.brackets) ? config.brackets : (config.brackets ? [config.brackets] : ['right']);\n      const lx = rect.x - padding[3] * 2;\n      const rx = rect.x + rect.w + padding[1] * 2;\n      const ty = rect.y - padding[0] * 2;\n      const by = rect.y + rect.h + padding[2] * 2;\n      for (const br of brackets) {\n        let points: Point[];\n        switch (br) {\n          case 'bottom':\n            points = [\n              [lx, rect.y + rect.h],\n              [lx, by],\n              [rx, by],\n              [rx, rect.y + rect.h]\n            ];\n            break;\n          case 'top':\n            points = [\n              [lx, rect.y],\n              [lx, ty],\n              [rx, ty],\n              [rx, rect.y]\n            ];\n            break;\n          case 'left':\n            points = [\n              [rect.x, ty],\n              [lx, ty],\n              [lx, by],\n              [rect.x, by]\n            ];\n            break;\n          case 'right':\n            points = [\n              [rect.x + rect.w, ty],\n              [rx, ty],\n              [rx, by],\n              [rect.x + rect.w, by]\n            ];\n            break;\n        }\n        if (points) {\n          opList.push(linearPath(points, false, o));\n        }\n      }\n      break;\n    }\n    case 'crossed-off': {\n      const x = rect.x;\n      const y = rect.y;\n      const x2 = x + rect.w;\n      const y2 = y + rect.h;\n      for (let i = rtl; i < iterations + rtl; i++) {\n        if (i % 2) {\n          opList.push(line(x2, y2, x, y, o));\n        } else {\n          opList.push(line(x, y, x2, y2, o));\n        }\n      }\n      for (let i = rtl; i < iterations + rtl; i++) {\n        if (i % 2) {\n          opList.push(line(x, y2, x2, y, o));\n        } else {\n          opList.push(line(x2, y, x, y2, o));\n        }\n      }\n      break;\n    }\n    case 'circle': {\n      const doubleO = getOptions('double', seed);\n      const width = rect.w + (padding[1] + padding[3]);\n      const height = rect.h + (padding[0] + padding[2]);\n      const x = rect.x - padding[3] + (width / 2);\n      const y = rect.y - padding[0] + (height / 2);\n      const fullItr = Math.floor(iterations / 2);\n      const singleItr = iterations - (fullItr * 2);\n      for (let i = 0; i < fullItr; i++) {\n        opList.push(ellipse(x, y, width, height, doubleO));\n      }\n      for (let i = 0; i < singleItr; i++) {\n        opList.push(ellipse(x, y, width, height, o));\n      }\n      break;\n    }\n    case 'highlight': {\n      const o = getOptions('highlight', seed);\n      strokeWidth = rect.h * 0.95;\n      const y = rect.y + (rect.h / 2);\n      for (let i = rtl; i < iterations + rtl; i++) {\n        if (i % 2) {\n          opList.push(line(rect.x + rect.w, y, rect.x, y, o));\n        } else {\n          opList.push(line(rect.x, y, rect.x + rect.w, y, o));\n        }\n      }\n      break;\n    }\n  }\n\n  if (opList.length) {\n    const pathStrings = opsToPath(opList);\n    const lengths: number[] = [];\n    const pathElements: SVGPathElement[] = [];\n    let totalLength = 0;\n    const setAttr = (p: SVGPathElement, an: string, av: string) => p.setAttribute(an, av);\n\n    for (const d of pathStrings) {\n      const path = document.createElementNS(SVG_NS, 'path');\n      setAttr(path, 'd', d);\n      setAttr(path, 'fill', 'none');\n      setAttr(path, 'stroke', config.color || 'currentColor');\n      setAttr(path, 'stroke-width', `${strokeWidth}`);\n      if (animate) {\n        const length = path.getTotalLength();\n        lengths.push(length);\n        totalLength += length;\n      }\n      svg.appendChild(path);\n      pathElements.push(path);\n    }\n\n    if (animate) {\n      let durationOffset = 0;\n      for (let i = 0; i < pathElements.length; i++) {\n        const path = pathElements[i];\n        const length = lengths[i];\n        const duration = totalLength ? (animationDuration * (length / totalLength)) : 0;\n        const delay = animationGroupDelay + durationOffset;\n        const style = path.style;\n        style.strokeDashoffset = `${length}`;\n        style.strokeDasharray = `${length}`;\n        style.animation = `rough-notation-dash ${duration}ms ease-out ${delay}ms forwards`;\n        durationOffset += duration;\n      }\n    }\n  }\n}\n\nfunction opsToPath(opList: OpSet[]): string[] {\n  const paths: string[] = [];\n  for (const drawing of opList) {\n    let path = '';\n    for (const item of drawing.ops) {\n      const data = item.data;\n      switch (item.op) {\n        case 'move':\n          if (path.trim()) {\n            paths.push(path.trim());\n          }\n          path = `M${data[0]} ${data[1]} `;\n          break;\n        case 'bcurveTo':\n          path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;\n          break;\n        case 'lineTo':\n          path += `L${data[0]} ${data[1]} `;\n          break;\n      }\n    }\n    if (path.trim()) {\n      paths.push(path.trim());\n    }\n  }\n  return paths;\n}"
  },
  {
    "path": "src/rough-notation.ts",
    "content": "import { Rect, RoughAnnotationConfig, RoughAnnotation, SVG_NS, RoughAnnotationGroup, DEFAULT_ANIMATION_DURATION } from './model.js';\nimport { renderAnnotation } from './render.js';\nimport { ensureKeyframes } from './keyframes.js';\nimport { randomSeed } from 'roughjs/bin/math';\n\ntype AnnotationState = 'unattached' | 'not-showing' | 'showing';\n\nclass RoughAnnotationImpl implements RoughAnnotation {\n  private _state: AnnotationState = 'unattached';\n  private _config: RoughAnnotationConfig;\n  private _resizing = false;\n  private _ro?: any; // ResizeObserver is not supported in typescript std lib yet\n  private _seed = randomSeed();\n\n  private _e: HTMLElement;\n  private _svg?: SVGSVGElement;\n  private _lastSizes: Rect[] = [];\n\n  _animationDelay = 0;\n\n  constructor(e: HTMLElement, config: RoughAnnotationConfig) {\n    this._e = e;\n    this._config = JSON.parse(JSON.stringify(config));\n    this.attach();\n  }\n\n  get animate() { return this._config.animate; }\n  set animate(value) { this._config.animate = value; }\n\n  get animationDuration() { return this._config.animationDuration; }\n  set animationDuration(value) { this._config.animationDuration = value; }\n\n  get iterations() { return this._config.iterations; }\n  set iterations(value) { this._config.iterations = value; }\n\n  get color() { return this._config.color; }\n  set color(value) {\n    if (this._config.color !== value) {\n      this._config.color = value;\n      this.refresh();\n    }\n  }\n\n  get strokeWidth() { return this._config.strokeWidth; }\n  set strokeWidth(value) {\n    if (this._config.strokeWidth !== value) {\n      this._config.strokeWidth = value;\n      this.refresh();\n    }\n  }\n\n  get padding() { return this._config.padding; }\n  set padding(value) {\n    if (this._config.padding !== value) {\n      this._config.padding = value;\n      this.refresh();\n    }\n  }\n\n  private _resizeListener = () => {\n    if (!this._resizing) {\n      this._resizing = true;\n      setTimeout(() => {\n        this._resizing = false;\n        if (this._state === 'showing') {\n          if (this.haveRectsChanged()) {\n            this.show();\n          }\n        }\n      }, 400);\n    }\n  }\n\n  private attach() {\n    if (this._state === 'unattached' && this._e.parentElement) {\n      ensureKeyframes();\n      const svg = this._svg = document.createElementNS(SVG_NS, 'svg');\n      svg.setAttribute('class', 'rough-annotation');\n      const style = svg.style;\n      style.position = 'absolute';\n      style.top = '0';\n      style.left = '0';\n      style.overflow = 'visible';\n      style.pointerEvents = 'none';\n      style.width = '100px';\n      style.height = '100px';\n      const prepend = this._config.type === 'highlight';\n      this._e.insertAdjacentElement(prepend ? 'beforebegin' : 'afterend', svg);\n      this._state = 'not-showing';\n\n      // ensure e is positioned\n      if (prepend) {\n        const computedPos = window.getComputedStyle(this._e).position;\n        const unpositioned = (!computedPos) || (computedPos === 'static');\n        if (unpositioned) {\n          this._e.style.position = 'relative';\n        }\n      }\n      this.attachListeners();\n    }\n  }\n\n  private detachListeners() {\n    window.removeEventListener('resize', this._resizeListener);\n    if (this._ro) {\n      this._ro.unobserve(this._e);\n    }\n  }\n\n  private attachListeners() {\n    this.detachListeners();\n    window.addEventListener('resize', this._resizeListener, { passive: true });\n    if ((!this._ro) && ('ResizeObserver' in window)) {\n      this._ro = new (window as any).ResizeObserver((entries: any) => {\n        for (const entry of entries) {\n          if (entry.contentRect) {\n            this._resizeListener();\n          }\n        }\n      });\n    }\n    if (this._ro) {\n      this._ro.observe(this._e);\n    }\n  }\n\n  private haveRectsChanged(): boolean {\n    if (this._lastSizes.length) {\n      const newRects = this.rects();\n      if (newRects.length === this._lastSizes.length) {\n        for (let i = 0; i < newRects.length; i++) {\n          if (!this.isSameRect(newRects[i], this._lastSizes[i])) {\n            return true;\n          }\n        }\n      } else {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private isSameRect(rect1: Rect, rect2: Rect): boolean {\n    const si = (a: number, b: number) => Math.round(a) === Math.round(b);\n    return (\n      si(rect1.x, rect2.x) &&\n      si(rect1.y, rect2.y) &&\n      si(rect1.w, rect2.w) &&\n      si(rect1.h, rect2.h)\n    );\n  }\n\n  isShowing(): boolean {\n    return (this._state !== 'not-showing');\n  }\n\n  private pendingRefresh?: Promise<void>;\n  private refresh() {\n    if (this.isShowing() && (!this.pendingRefresh)) {\n      this.pendingRefresh = Promise.resolve().then(() => {\n        if (this.isShowing()) {\n          this.show();\n        }\n        delete this.pendingRefresh;\n      });\n    }\n  }\n\n  show(): void {\n    switch (this._state) {\n      case 'unattached':\n        break;\n      case 'showing':\n        this.hide();\n        if (this._svg) {\n          this.render(this._svg, true);\n        }\n        break;\n      case 'not-showing':\n        this.attach();\n        if (this._svg) {\n          this.render(this._svg, false);\n        }\n        break;\n    }\n  }\n\n  hide(): void {\n    if (this._svg) {\n      while (this._svg.lastChild) {\n        this._svg.removeChild(this._svg.lastChild);\n      }\n    }\n    this._state = 'not-showing';\n  }\n\n  remove(): void {\n    if (this._svg && this._svg.parentElement) {\n      this._svg.parentElement.removeChild(this._svg);\n    }\n    this._svg = undefined;\n    this._state = 'unattached';\n    this.detachListeners();\n  }\n\n  private render(svg: SVGSVGElement, ensureNoAnimation: boolean) {\n    let config = this._config;\n    if (ensureNoAnimation) {\n      config = JSON.parse(JSON.stringify(this._config));\n      config.animate = false;\n    }\n    const rects = this.rects();\n    let totalWidth = 0;\n    rects.forEach((rect) => totalWidth += rect.w);\n    const totalDuration = (config.animationDuration || DEFAULT_ANIMATION_DURATION);\n    let delay = 0;\n    for (let i = 0; i < rects.length; i++) {\n      const rect = rects[i];\n      const ad = totalDuration * (rect.w / totalWidth);\n      renderAnnotation(svg, rects[i], config, delay + this._animationDelay, ad, this._seed);\n      delay += ad;\n    }\n    this._lastSizes = rects;\n    this._state = 'showing';\n  }\n\n  private rects(): Rect[] {\n    const ret: Rect[] = [];\n    if (this._svg) {\n      if (this._config.multiline) {\n        const elementRects = this._e.getClientRects();\n        for (let i = 0; i < elementRects.length; i++) {\n          ret.push(this.svgRect(this._svg, elementRects[i]));\n        }\n      } else {\n        ret.push(this.svgRect(this._svg, this._e.getBoundingClientRect()));\n      }\n    }\n    return ret;\n  }\n\n  private svgRect(svg: SVGSVGElement, bounds: DOMRect | DOMRectReadOnly): Rect {\n    const rect1 = svg.getBoundingClientRect();\n    const rect2 = bounds;\n    return {\n      x: (rect2.x || rect2.left) - (rect1.x || rect1.left),\n      y: (rect2.y || rect2.top) - (rect1.y || rect1.top),\n      w: rect2.width,\n      h: rect2.height\n    };\n  }\n}\n\nexport function annotate(element: HTMLElement, config: RoughAnnotationConfig): RoughAnnotation {\n  return new RoughAnnotationImpl(element, config);\n}\n\nexport function annotationGroup(annotations: RoughAnnotation[]): RoughAnnotationGroup {\n  let delay = 0;\n  for (const a of annotations) {\n    const ai = a as RoughAnnotationImpl;\n    ai._animationDelay = delay;\n    const duration = ai.animationDuration === 0 ? 0 : (ai.animationDuration || DEFAULT_ANIMATION_DURATION);\n    delay += duration;\n  }\n  const list = [...annotations];\n  return {\n    show() {\n      for (const a of list) {\n        a.show();\n      }\n    },\n    hide() {\n      for (const a of list) {\n        a.hide();\n      }\n    }\n  };\n}"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\n      \"es2017\",\n      \"dom\"\n    ],\n    \"declaration\": true,\n    \"outDir\": \"./lib\",\n    \"baseUrl\": \".\",\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"noImplicitAny\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\n    \"src/**/*.ts\"\n  ]\n}"
  },
  {
    "path": "tslint.json",
    "content": "{\n  \"rules\": {\n    \"arrow-parens\": true,\n    \"class-name\": true,\n    \"indent\": [\n      true,\n      \"spaces\",\n      2\n    ],\n    \"prefer-const\": true,\n    \"no-duplicate-variable\": true,\n    \"no-eval\": true,\n    \"no-internal-module\": true,\n    \"no-trailing-whitespace\": false,\n    \"no-var-keyword\": true,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-whitespace\"\n    ],\n    \"quotemark\": [\n      true,\n      \"single\",\n      \"avoid-escape\"\n    ],\n    \"semicolon\": [\n      true,\n      \"always\"\n    ],\n    \"trailing-comma\": [\n      true,\n      \"multiline\"\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"variable-name\": [\n      true,\n      \"ban-keywords\"\n    ],\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-operator\",\n      \"check-separator\",\n      \"check-type\"\n    ]\n  }\n}"
  }
]