Full Code of rough-stuff/rough-notation for AI

master 668ba82ac89c cached
13 files
29.7 KB
8.2k tokens
47 symbols
1 requests
Download .txt
Repository: rough-stuff/rough-notation
Branch: master
Commit: 668ba82ac89c
Files: 13
Total size: 29.7 KB

Directory structure:
gitextract_6iuybk5h/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│   ├── keyframes.ts
│   ├── model.ts
│   ├── render.ts
│   └── rough-notation.ts
├── tsconfig.json
└── tslint.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
github: pshihn
open_collective: rough


================================================
FILE: .gitignore
================================================
node_modules
bin
dist
lib
demo


================================================
FILE: .npmignore
================================================
node_modules
demo
src
tsconfig.json
tslint.json
.gitignore


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Preet Shihn

Permission 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:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE 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.


================================================
FILE: README.md
================================================
![Rough Notation logo](https://roughnotation.com/images/social.png)

# Rough Notation

A small JavaScript library to create and animate annotations on a web page.

Rough 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.

Rough Notation is 3.83kb in size when gzipped.

[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

## Installation

You can add rough-notation to your project via npm

```
npm install --save rough-notation
```

Or load the ES module directly

```html
<script type="module" src="https://unpkg.com/rough-notation?module"></script>
```

Or load the IIFE version which created a `RoughNotation` object in your scope.

```html
<script src="https://unpkg.com/rough-notation/lib/rough-notation.iife.js"></script>
```

## Usage

Create an `annotation` object by passing the element to annotate, and a config to describe the annotation style. 
Once you have the annotation object, you can call `show()` on it to show the annotation

```javascript
import { annotate } from 'rough-notation';
// Or using unpkg
// import { annotate } from 'https://unpkg.com/rough-notation?module';

const e = document.querySelector('#myElement');
const annotation = annotate(e, { type: 'underline' });
annotation.show();
```

*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.*

## Annotation Group

rough-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.

```javascript
import { annotate, annotationGroup } from 'rough-notation';

const a1 = annotate(document.querySelector('#e1'), { type: 'underline' });
const a2 = annotate(document.querySelector('#e3'), { type: 'box' });
const a3 = annotate(document.querySelector('#e3'), { type: 'circle' });

const ag = annotationGroup([a3, a1, a2]);
ag.show();
```

## Live examples
I have created some basic examples on Glitch for you to remix and play with the code:

[Basic demo](https://glitch.com/~basic-rough-notation)

[Annotation group demo](https://glitch.com/~annotation-group)

## Configuring the Annotation

When 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. 

#### type
This is a mandatory field. It sets the annotation style. Following are the list of supported annotation types:

* __underline__: This style creates a sketchy underline below an element.
* __box__: This style draws a box around the element.
* __circle__: This style draws a circle around the element.
* __highlight__: This style creates a highlight effect as if marked by a highlighter.
* __strike-through__: This style draws horizontal lines through the element.
* __crossed-off__: This style draws an 'X' across the element.
* __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*.

#### animate
Boolean property to turn on/off animation when annotating. Default value is `true`.

#### animationDuration
Duration of the animation in milliseconds. Default is `800ms`.

#### color
String value representing the color of the annotation sketch. Default value is `currentColor`.

#### strokeWidth
Width of the annotation strokes. Default value is `1`. 

#### padding
Padding between the element and roughly where the annotation is drawn. Default value is `5` (in pixels).
If 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]`.

#### multiline
This property only applies to inline text. To annotate multiline text (each line separately), set this property to `true`. 

#### iterations
By 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. 

#### brackets
Value 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`.

#### rtl
By default annotations are drawn from left to right. To start with right to left, set this property to `true`. 

## Annotation Object

When you call the `annotate` function, you get back an annotation object, which has the following methods:

#### isShowing(): boolean
Returns if the annotation is showing

#### show()
Draws 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. 

*Note: to reanimate the annotation, call `hide()` and then `show()` again.

#### hide()
Hides the annotation if showing. This is not animated. 

#### remove()
Unlinks the annotation from the element. 

#### Updating styles
All 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.

```javascript
const e = document.querySelector('#myElement');
const annotation = annotate(e, { type: 'underline', color: 'red' });
annotation.show();
annotation.color = 'green';
```

*Note: the type of the annotation cannot be changed. Create a new annotation for that.*

## Annotation Group Object

When you call the `annotationGroup` function, you get back an annotation group object, which has the following methods:

#### show()
Draws all the annotations in order. If the annotation is set to animate (default), it will animate the drawing. 

#### hide()
Hides all the annotations if showing. This is not animated.

## Wrappers

Others have created handy Rough Notation wrappers for multiple libraries and frameworks:

-   [React Rough Notation](https://github.com/linkstrifer/react-rough-notation)
-   [Svelte Rough Notation](https://github.com/dimfeld/svelte-rough-notation)
-   [Vue Rough Notation](https://github.com/Leecason/vue-rough-notation)
-   [Web Component Rough Notation](https://github.com/Matsuuu/vanilla-rough-notation)
-   [Angular Rough Notation](https://github.com/mikyaj/ngx-rough-notation)

## Contributors

### Financial Contributors

Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/rough/contribute)]

#### Individuals

<a href="https://opencollective.com/rough"><img src="https://opencollective.com/rough/individuals.svg?width=890"></a>

#### Organizations

Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/rough/contribute)]

<a href="https://opencollective.com/rough/organization/0/website"><img src="https://opencollective.com/rough/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/1/website"><img src="https://opencollective.com/rough/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/2/website"><img src="https://opencollective.com/rough/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/3/website"><img src="https://opencollective.com/rough/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/4/website"><img src="https://opencollective.com/rough/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/5/website"><img src="https://opencollective.com/rough/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/6/website"><img src="https://opencollective.com/rough/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/7/website"><img src="https://opencollective.com/rough/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/8/website"><img src="https://opencollective.com/rough/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/rough/organization/9/website"><img src="https://opencollective.com/rough/organization/9/avatar.svg"></a>


================================================
FILE: package.json
================================================
{
  "name": "rough-notation",
  "version": "0.5.1",
  "description": "Create and animate hand-drawn annotations on a web page",
  "main": "lib/rough-notation.cjs.js",
  "module": "lib/rough-notation.esm.js",
  "types": "lib/rough-notation.d.ts",
  "scripts": {
    "build": "rm -rf lib && tsc && rollup -c",
    "lint": "tslint -p tsconfig.json",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/pshihn/rough-notation.git"
  },
  "keywords": [
    "annotate",
    "rough",
    "sketchy"
  ],
  "author": "Preet Shihn",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/pshihn/rough-notation/issues"
  },
  "homepage": "https://github.com/pshihn/rough-notation#readme",
  "devDependencies": {
    "rollup": "^2.32.1",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-terser": "^6.1.0",
    "roughjs": "^4.3.1",
    "tslint": "^6.1.3",
    "typescript": "^4.0.5"
  }
}


================================================
FILE: rollup.config.js
================================================
import resolve from 'rollup-plugin-node-resolve';
import { terser } from "rollup-plugin-terser";

const input = 'lib/rough-notation.js';

export default [
  {
    input,
    output: {
      file: 'lib/rough-notation.iife.js',
      format: 'iife',
      name: 'RoughNotation'
    },
    plugins: [resolve(), terser()]
  },
  {
    input,
    output: {
      file: 'lib/rough-notation.esm.js',
      format: 'esm'
    },
    plugins: [resolve(), terser()]
  },
  {
    input,
    output: {
      file: 'lib/rough-notation.cjs.js',
      format: 'cjs'
    },
    plugins: [resolve(), terser()]
  },
];

================================================
FILE: src/keyframes.ts
================================================
export function ensureKeyframes() {
  if (!(window as any).__rno_kf_s) {
    const style = (window as any).__rno_kf_s = document.createElement('style');
    style.textContent = `@keyframes rough-notation-dash { to { stroke-dashoffset: 0; } }`;
    document.head.appendChild(style);
  }
}

================================================
FILE: src/model.ts
================================================
export const SVG_NS = 'http://www.w3.org/2000/svg';

export const DEFAULT_ANIMATION_DURATION = 800;

export interface Rect {
  x: number;
  y: number;
  w: number;
  h: number;
}

export type RoughAnnotationType = 'underline' | 'box' | 'circle' | 'highlight' | 'strike-through' | 'crossed-off' | 'bracket';
export type FullPadding = [number, number, number, number];
export type RoughPadding = number | [number, number] | FullPadding;
export type BracketType = 'left' | 'right' | 'top' | 'bottom';

export interface RoughAnnotationConfig extends RoughAnnotationConfigBase {
  type: RoughAnnotationType;
  multiline?: boolean;
  rtl?: boolean;
}

export interface RoughAnnotationConfigBase {
  animate?: boolean; // defaults to true
  animationDuration?: number; // defaulst to 1000ms
  color?: string; // defaults to currentColor
  strokeWidth?: number; // default based on type
  padding?: RoughPadding; // defaults to 5px
  iterations?: number; // defaults to 2
  brackets?: BracketType | BracketType[]; // defaults to 'right'
}

export interface RoughAnnotation extends RoughAnnotationConfigBase {
  isShowing(): boolean;
  show(): void;
  hide(): void;
  remove(): void;
}

export interface RoughAnnotationGroup {
  show(): void;
  hide(): void;
}

================================================
FILE: src/render.ts
================================================
import { Rect, RoughAnnotationConfig, SVG_NS, FullPadding, BracketType } from './model.js';
import { ResolvedOptions, OpSet } from 'roughjs/bin/core';
import { line, rectangle, ellipse, linearPath } from 'roughjs/bin/renderer';
import { Point } from 'roughjs/bin/geometry';

type RoughOptionsType = 'highlight' | 'single' | 'double';

function getOptions(type: RoughOptionsType, seed: number): ResolvedOptions {
  return {
    maxRandomnessOffset: 2,
    roughness: type === 'highlight' ? 3 : 1.5,
    bowing: 1,
    stroke: '#000',
    strokeWidth: 1.5,
    curveTightness: 0,
    curveFitting: 0.95,
    curveStepCount: 9,
    fillStyle: 'hachure',
    fillWeight: -1,
    hachureAngle: -41,
    hachureGap: -1,
    dashOffset: -1,
    dashGap: -1,
    zigzagOffset: -1,
    combineNestedSvgPaths: false,
    disableMultiStroke: type !== 'double',
    disableMultiStrokeFill: false,
    seed
  };
}

function parsePadding(config: RoughAnnotationConfig): FullPadding {
  const p = config.padding;
  if (p || (p === 0)) {
    if (typeof p === 'number') {
      return [p, p, p, p];
    } else if (Array.isArray(p)) {
      const pa = p as number[];
      if (pa.length) {
        switch (pa.length) {
          case 4:
            return [...pa] as FullPadding;
          case 1:
            return [pa[0], pa[0], pa[0], pa[0]];
          case 2:
            return [...pa, ...pa] as FullPadding;
          case 3:
            return [...pa, pa[1]] as FullPadding;
          default:
            return [pa[0], pa[1], pa[2], pa[3]];
        }
      }
    }
  }
  return [5, 5, 5, 5];
}

export function renderAnnotation(svg: SVGSVGElement, rect: Rect, config: RoughAnnotationConfig, animationGroupDelay: number, animationDuration: number, seed: number) {
  const opList: OpSet[] = [];
  let strokeWidth = config.strokeWidth || 2;
  const padding = parsePadding(config);
  const animate = (config.animate === undefined) ? true : (!!config.animate);
  const iterations = config.iterations || 2;
  const rtl = config.rtl ? 1 : 0;
  const o = getOptions('single', seed);

  switch (config.type) {
    case 'underline': {
      const y = rect.y + rect.h + padding[2];
      for (let i = rtl; i < iterations + rtl; i++) {
        if (i % 2) {
          opList.push(line(rect.x + rect.w, y, rect.x, y, o));
        } else {
          opList.push(line(rect.x, y, rect.x + rect.w, y, o));
        }
      }
      break;
    }
    case 'strike-through': {
      const y = rect.y + (rect.h / 2);
      for (let i = rtl; i < iterations + rtl; i++) {
        if (i % 2) {
          opList.push(line(rect.x + rect.w, y, rect.x, y, o));
        } else {
          opList.push(line(rect.x, y, rect.x + rect.w, y, o));
        }
      }
      break;
    }
    case 'box': {
      const x = rect.x - padding[3];
      const y = rect.y - padding[0];
      const width = rect.w + (padding[1] + padding[3]);
      const height = rect.h + (padding[0] + padding[2]);
      for (let i = 0; i < iterations; i++) {
        opList.push(rectangle(x, y, width, height, o));
      }
      break;
    }
    case 'bracket': {
      const brackets: BracketType[] = Array.isArray(config.brackets) ? config.brackets : (config.brackets ? [config.brackets] : ['right']);
      const lx = rect.x - padding[3] * 2;
      const rx = rect.x + rect.w + padding[1] * 2;
      const ty = rect.y - padding[0] * 2;
      const by = rect.y + rect.h + padding[2] * 2;
      for (const br of brackets) {
        let points: Point[];
        switch (br) {
          case 'bottom':
            points = [
              [lx, rect.y + rect.h],
              [lx, by],
              [rx, by],
              [rx, rect.y + rect.h]
            ];
            break;
          case 'top':
            points = [
              [lx, rect.y],
              [lx, ty],
              [rx, ty],
              [rx, rect.y]
            ];
            break;
          case 'left':
            points = [
              [rect.x, ty],
              [lx, ty],
              [lx, by],
              [rect.x, by]
            ];
            break;
          case 'right':
            points = [
              [rect.x + rect.w, ty],
              [rx, ty],
              [rx, by],
              [rect.x + rect.w, by]
            ];
            break;
        }
        if (points) {
          opList.push(linearPath(points, false, o));
        }
      }
      break;
    }
    case 'crossed-off': {
      const x = rect.x;
      const y = rect.y;
      const x2 = x + rect.w;
      const y2 = y + rect.h;
      for (let i = rtl; i < iterations + rtl; i++) {
        if (i % 2) {
          opList.push(line(x2, y2, x, y, o));
        } else {
          opList.push(line(x, y, x2, y2, o));
        }
      }
      for (let i = rtl; i < iterations + rtl; i++) {
        if (i % 2) {
          opList.push(line(x, y2, x2, y, o));
        } else {
          opList.push(line(x2, y, x, y2, o));
        }
      }
      break;
    }
    case 'circle': {
      const doubleO = getOptions('double', seed);
      const width = rect.w + (padding[1] + padding[3]);
      const height = rect.h + (padding[0] + padding[2]);
      const x = rect.x - padding[3] + (width / 2);
      const y = rect.y - padding[0] + (height / 2);
      const fullItr = Math.floor(iterations / 2);
      const singleItr = iterations - (fullItr * 2);
      for (let i = 0; i < fullItr; i++) {
        opList.push(ellipse(x, y, width, height, doubleO));
      }
      for (let i = 0; i < singleItr; i++) {
        opList.push(ellipse(x, y, width, height, o));
      }
      break;
    }
    case 'highlight': {
      const o = getOptions('highlight', seed);
      strokeWidth = rect.h * 0.95;
      const y = rect.y + (rect.h / 2);
      for (let i = rtl; i < iterations + rtl; i++) {
        if (i % 2) {
          opList.push(line(rect.x + rect.w, y, rect.x, y, o));
        } else {
          opList.push(line(rect.x, y, rect.x + rect.w, y, o));
        }
      }
      break;
    }
  }

  if (opList.length) {
    const pathStrings = opsToPath(opList);
    const lengths: number[] = [];
    const pathElements: SVGPathElement[] = [];
    let totalLength = 0;
    const setAttr = (p: SVGPathElement, an: string, av: string) => p.setAttribute(an, av);

    for (const d of pathStrings) {
      const path = document.createElementNS(SVG_NS, 'path');
      setAttr(path, 'd', d);
      setAttr(path, 'fill', 'none');
      setAttr(path, 'stroke', config.color || 'currentColor');
      setAttr(path, 'stroke-width', `${strokeWidth}`);
      if (animate) {
        const length = path.getTotalLength();
        lengths.push(length);
        totalLength += length;
      }
      svg.appendChild(path);
      pathElements.push(path);
    }

    if (animate) {
      let durationOffset = 0;
      for (let i = 0; i < pathElements.length; i++) {
        const path = pathElements[i];
        const length = lengths[i];
        const duration = totalLength ? (animationDuration * (length / totalLength)) : 0;
        const delay = animationGroupDelay + durationOffset;
        const style = path.style;
        style.strokeDashoffset = `${length}`;
        style.strokeDasharray = `${length}`;
        style.animation = `rough-notation-dash ${duration}ms ease-out ${delay}ms forwards`;
        durationOffset += duration;
      }
    }
  }
}

function opsToPath(opList: OpSet[]): string[] {
  const paths: string[] = [];
  for (const drawing of opList) {
    let path = '';
    for (const item of drawing.ops) {
      const data = item.data;
      switch (item.op) {
        case 'move':
          if (path.trim()) {
            paths.push(path.trim());
          }
          path = `M${data[0]} ${data[1]} `;
          break;
        case 'bcurveTo':
          path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;
          break;
        case 'lineTo':
          path += `L${data[0]} ${data[1]} `;
          break;
      }
    }
    if (path.trim()) {
      paths.push(path.trim());
    }
  }
  return paths;
}

================================================
FILE: src/rough-notation.ts
================================================
import { Rect, RoughAnnotationConfig, RoughAnnotation, SVG_NS, RoughAnnotationGroup, DEFAULT_ANIMATION_DURATION } from './model.js';
import { renderAnnotation } from './render.js';
import { ensureKeyframes } from './keyframes.js';
import { randomSeed } from 'roughjs/bin/math';

type AnnotationState = 'unattached' | 'not-showing' | 'showing';

class RoughAnnotationImpl implements RoughAnnotation {
  private _state: AnnotationState = 'unattached';
  private _config: RoughAnnotationConfig;
  private _resizing = false;
  private _ro?: any; // ResizeObserver is not supported in typescript std lib yet
  private _seed = randomSeed();

  private _e: HTMLElement;
  private _svg?: SVGSVGElement;
  private _lastSizes: Rect[] = [];

  _animationDelay = 0;

  constructor(e: HTMLElement, config: RoughAnnotationConfig) {
    this._e = e;
    this._config = JSON.parse(JSON.stringify(config));
    this.attach();
  }

  get animate() { return this._config.animate; }
  set animate(value) { this._config.animate = value; }

  get animationDuration() { return this._config.animationDuration; }
  set animationDuration(value) { this._config.animationDuration = value; }

  get iterations() { return this._config.iterations; }
  set iterations(value) { this._config.iterations = value; }

  get color() { return this._config.color; }
  set color(value) {
    if (this._config.color !== value) {
      this._config.color = value;
      this.refresh();
    }
  }

  get strokeWidth() { return this._config.strokeWidth; }
  set strokeWidth(value) {
    if (this._config.strokeWidth !== value) {
      this._config.strokeWidth = value;
      this.refresh();
    }
  }

  get padding() { return this._config.padding; }
  set padding(value) {
    if (this._config.padding !== value) {
      this._config.padding = value;
      this.refresh();
    }
  }

  private _resizeListener = () => {
    if (!this._resizing) {
      this._resizing = true;
      setTimeout(() => {
        this._resizing = false;
        if (this._state === 'showing') {
          if (this.haveRectsChanged()) {
            this.show();
          }
        }
      }, 400);
    }
  }

  private attach() {
    if (this._state === 'unattached' && this._e.parentElement) {
      ensureKeyframes();
      const svg = this._svg = document.createElementNS(SVG_NS, 'svg');
      svg.setAttribute('class', 'rough-annotation');
      const style = svg.style;
      style.position = 'absolute';
      style.top = '0';
      style.left = '0';
      style.overflow = 'visible';
      style.pointerEvents = 'none';
      style.width = '100px';
      style.height = '100px';
      const prepend = this._config.type === 'highlight';
      this._e.insertAdjacentElement(prepend ? 'beforebegin' : 'afterend', svg);
      this._state = 'not-showing';

      // ensure e is positioned
      if (prepend) {
        const computedPos = window.getComputedStyle(this._e).position;
        const unpositioned = (!computedPos) || (computedPos === 'static');
        if (unpositioned) {
          this._e.style.position = 'relative';
        }
      }
      this.attachListeners();
    }
  }

  private detachListeners() {
    window.removeEventListener('resize', this._resizeListener);
    if (this._ro) {
      this._ro.unobserve(this._e);
    }
  }

  private attachListeners() {
    this.detachListeners();
    window.addEventListener('resize', this._resizeListener, { passive: true });
    if ((!this._ro) && ('ResizeObserver' in window)) {
      this._ro = new (window as any).ResizeObserver((entries: any) => {
        for (const entry of entries) {
          if (entry.contentRect) {
            this._resizeListener();
          }
        }
      });
    }
    if (this._ro) {
      this._ro.observe(this._e);
    }
  }

  private haveRectsChanged(): boolean {
    if (this._lastSizes.length) {
      const newRects = this.rects();
      if (newRects.length === this._lastSizes.length) {
        for (let i = 0; i < newRects.length; i++) {
          if (!this.isSameRect(newRects[i], this._lastSizes[i])) {
            return true;
          }
        }
      } else {
        return true;
      }
    }
    return false;
  }

  private isSameRect(rect1: Rect, rect2: Rect): boolean {
    const si = (a: number, b: number) => Math.round(a) === Math.round(b);
    return (
      si(rect1.x, rect2.x) &&
      si(rect1.y, rect2.y) &&
      si(rect1.w, rect2.w) &&
      si(rect1.h, rect2.h)
    );
  }

  isShowing(): boolean {
    return (this._state !== 'not-showing');
  }

  private pendingRefresh?: Promise<void>;
  private refresh() {
    if (this.isShowing() && (!this.pendingRefresh)) {
      this.pendingRefresh = Promise.resolve().then(() => {
        if (this.isShowing()) {
          this.show();
        }
        delete this.pendingRefresh;
      });
    }
  }

  show(): void {
    switch (this._state) {
      case 'unattached':
        break;
      case 'showing':
        this.hide();
        if (this._svg) {
          this.render(this._svg, true);
        }
        break;
      case 'not-showing':
        this.attach();
        if (this._svg) {
          this.render(this._svg, false);
        }
        break;
    }
  }

  hide(): void {
    if (this._svg) {
      while (this._svg.lastChild) {
        this._svg.removeChild(this._svg.lastChild);
      }
    }
    this._state = 'not-showing';
  }

  remove(): void {
    if (this._svg && this._svg.parentElement) {
      this._svg.parentElement.removeChild(this._svg);
    }
    this._svg = undefined;
    this._state = 'unattached';
    this.detachListeners();
  }

  private render(svg: SVGSVGElement, ensureNoAnimation: boolean) {
    let config = this._config;
    if (ensureNoAnimation) {
      config = JSON.parse(JSON.stringify(this._config));
      config.animate = false;
    }
    const rects = this.rects();
    let totalWidth = 0;
    rects.forEach((rect) => totalWidth += rect.w);
    const totalDuration = (config.animationDuration || DEFAULT_ANIMATION_DURATION);
    let delay = 0;
    for (let i = 0; i < rects.length; i++) {
      const rect = rects[i];
      const ad = totalDuration * (rect.w / totalWidth);
      renderAnnotation(svg, rects[i], config, delay + this._animationDelay, ad, this._seed);
      delay += ad;
    }
    this._lastSizes = rects;
    this._state = 'showing';
  }

  private rects(): Rect[] {
    const ret: Rect[] = [];
    if (this._svg) {
      if (this._config.multiline) {
        const elementRects = this._e.getClientRects();
        for (let i = 0; i < elementRects.length; i++) {
          ret.push(this.svgRect(this._svg, elementRects[i]));
        }
      } else {
        ret.push(this.svgRect(this._svg, this._e.getBoundingClientRect()));
      }
    }
    return ret;
  }

  private svgRect(svg: SVGSVGElement, bounds: DOMRect | DOMRectReadOnly): Rect {
    const rect1 = svg.getBoundingClientRect();
    const rect2 = bounds;
    return {
      x: (rect2.x || rect2.left) - (rect1.x || rect1.left),
      y: (rect2.y || rect2.top) - (rect1.y || rect1.top),
      w: rect2.width,
      h: rect2.height
    };
  }
}

export function annotate(element: HTMLElement, config: RoughAnnotationConfig): RoughAnnotation {
  return new RoughAnnotationImpl(element, config);
}

export function annotationGroup(annotations: RoughAnnotation[]): RoughAnnotationGroup {
  let delay = 0;
  for (const a of annotations) {
    const ai = a as RoughAnnotationImpl;
    ai._animationDelay = delay;
    const duration = ai.animationDuration === 0 ? 0 : (ai.animationDuration || DEFAULT_ANIMATION_DURATION);
    delay += duration;
  }
  const list = [...annotations];
  return {
    show() {
      for (const a of list) {
        a.show();
      }
    },
    hide() {
      for (const a of list) {
        a.hide();
      }
    }
  };
}

================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "es2017",
    "module": "es2015",
    "moduleResolution": "node",
    "lib": [
      "es2017",
      "dom"
    ],
    "declaration": true,
    "outDir": "./lib",
    "baseUrl": ".",
    "strict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": [
    "src/**/*.ts"
  ]
}

================================================
FILE: tslint.json
================================================
{
  "rules": {
    "arrow-parens": true,
    "class-name": true,
    "indent": [
      true,
      "spaces",
      2
    ],
    "prefer-const": true,
    "no-duplicate-variable": true,
    "no-eval": true,
    "no-internal-module": true,
    "no-trailing-whitespace": false,
    "no-var-keyword": true,
    "one-line": [
      true,
      "check-open-brace",
      "check-whitespace"
    ],
    "quotemark": [
      true,
      "single",
      "avoid-escape"
    ],
    "semicolon": [
      true,
      "always"
    ],
    "trailing-comma": [
      true,
      "multiline"
    ],
    "triple-equals": [
      true,
      "allow-null-check"
    ],
    "typedef-whitespace": [
      true,
      {
        "call-signature": "nospace",
        "index-signature": "nospace",
        "parameter": "nospace",
        "property-declaration": "nospace",
        "variable-declaration": "nospace"
      }
    ],
    "variable-name": [
      true,
      "ban-keywords"
    ],
    "whitespace": [
      true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type"
    ]
  }
}
Download .txt
gitextract_6iuybk5h/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│   ├── keyframes.ts
│   ├── model.ts
│   ├── render.ts
│   └── rough-notation.ts
├── tsconfig.json
└── tslint.json
Download .txt
SYMBOL INDEX (47 symbols across 4 files)

FILE: src/keyframes.ts
  function ensureKeyframes (line 1) | function ensureKeyframes() {

FILE: src/model.ts
  constant SVG_NS (line 1) | const SVG_NS = 'http://www.w3.org/2000/svg';
  constant DEFAULT_ANIMATION_DURATION (line 3) | const DEFAULT_ANIMATION_DURATION = 800;
  type Rect (line 5) | interface Rect {
  type RoughAnnotationType (line 12) | type RoughAnnotationType = 'underline' | 'box' | 'circle' | 'highlight' ...
  type FullPadding (line 13) | type FullPadding = [number, number, number, number];
  type RoughPadding (line 14) | type RoughPadding = number | [number, number] | FullPadding;
  type BracketType (line 15) | type BracketType = 'left' | 'right' | 'top' | 'bottom';
  type RoughAnnotationConfig (line 17) | interface RoughAnnotationConfig extends RoughAnnotationConfigBase {
  type RoughAnnotationConfigBase (line 23) | interface RoughAnnotationConfigBase {
  type RoughAnnotation (line 33) | interface RoughAnnotation extends RoughAnnotationConfigBase {
  type RoughAnnotationGroup (line 40) | interface RoughAnnotationGroup {

FILE: src/render.ts
  type RoughOptionsType (line 6) | type RoughOptionsType = 'highlight' | 'single' | 'double';
  function getOptions (line 8) | function getOptions(type: RoughOptionsType, seed: number): ResolvedOptio...
  function parsePadding (line 32) | function parsePadding(config: RoughAnnotationConfig): FullPadding {
  function renderAnnotation (line 58) | function renderAnnotation(svg: SVGSVGElement, rect: Rect, config: RoughA...
  function opsToPath (line 239) | function opsToPath(opList: OpSet[]): string[] {

FILE: src/rough-notation.ts
  type AnnotationState (line 6) | type AnnotationState = 'unattached' | 'not-showing' | 'showing';
  class RoughAnnotationImpl (line 8) | class RoughAnnotationImpl implements RoughAnnotation {
    method constructor (line 21) | constructor(e: HTMLElement, config: RoughAnnotationConfig) {
    method animate (line 27) | get animate() { return this._config.animate; }
    method animate (line 28) | set animate(value) { this._config.animate = value; }
    method animationDuration (line 30) | get animationDuration() { return this._config.animationDuration; }
    method animationDuration (line 31) | set animationDuration(value) { this._config.animationDuration = value; }
    method iterations (line 33) | get iterations() { return this._config.iterations; }
    method iterations (line 34) | set iterations(value) { this._config.iterations = value; }
    method color (line 36) | get color() { return this._config.color; }
    method color (line 37) | set color(value) {
    method strokeWidth (line 44) | get strokeWidth() { return this._config.strokeWidth; }
    method strokeWidth (line 45) | set strokeWidth(value) {
    method padding (line 52) | get padding() { return this._config.padding; }
    method padding (line 53) | set padding(value) {
    method attach (line 74) | private attach() {
    method detachListeners (line 103) | private detachListeners() {
    method attachListeners (line 110) | private attachListeners() {
    method haveRectsChanged (line 127) | private haveRectsChanged(): boolean {
    method isSameRect (line 143) | private isSameRect(rect1: Rect, rect2: Rect): boolean {
    method isShowing (line 153) | isShowing(): boolean {
    method refresh (line 158) | private refresh() {
    method show (line 169) | show(): void {
    method hide (line 188) | hide(): void {
    method remove (line 197) | remove(): void {
    method render (line 206) | private render(svg: SVGSVGElement, ensureNoAnimation: boolean) {
    method rects (line 227) | private rects(): Rect[] {
    method svgRect (line 242) | private svgRect(svg: SVGSVGElement, bounds: DOMRect | DOMRectReadOnly)...
  function annotate (line 254) | function annotate(element: HTMLElement, config: RoughAnnotationConfig): ...
  function annotationGroup (line 258) | function annotationGroup(annotations: RoughAnnotation[]): RoughAnnotatio...
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (32K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 38,
    "preview": "github: pshihn\nopen_collective: rough\n"
  },
  {
    "path": ".gitignore",
    "chars": 31,
    "preview": "node_modules\nbin\ndist\nlib\ndemo\n"
  },
  {
    "path": ".npmignore",
    "chars": 59,
    "preview": "node_modules\ndemo\nsrc\ntsconfig.json\ntslint.json\n.gitignore\n"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2020 Preet Shihn\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 8677,
    "preview": "![Rough Notation logo](https://roughnotation.com/images/social.png)\n\n# Rough Notation\n\nA small JavaScript library to cre"
  },
  {
    "path": "package.json",
    "chars": 980,
    "preview": "{\n  \"name\": \"rough-notation\",\n  \"version\": \"0.5.1\",\n  \"description\": \"Create and animate hand-drawn annotations on a web"
  },
  {
    "path": "rollup.config.js",
    "chars": 599,
    "preview": "import resolve from 'rollup-plugin-node-resolve';\nimport { terser } from \"rollup-plugin-terser\";\n\nconst input = 'lib/rou"
  },
  {
    "path": "src/keyframes.ts",
    "chars": 287,
    "preview": "export function ensureKeyframes() {\n  if (!(window as any).__rno_kf_s) {\n    const style = (window as any).__rno_kf_s = "
  },
  {
    "path": "src/model.ts",
    "chars": 1251,
    "preview": "export const SVG_NS = 'http://www.w3.org/2000/svg';\n\nexport const DEFAULT_ANIMATION_DURATION = 800;\n\nexport interface Re"
  },
  {
    "path": "src/render.ts",
    "chars": 8022,
    "preview": "import { Rect, RoughAnnotationConfig, SVG_NS, FullPadding, BracketType } from './model.js';\nimport { ResolvedOptions, Op"
  },
  {
    "path": "src/rough-notation.ts",
    "chars": 7786,
    "preview": "import { Rect, RoughAnnotationConfig, RoughAnnotation, SVG_NS, RoughAnnotationGroup, DEFAULT_ANIMATION_DURATION } from '"
  },
  {
    "path": "tsconfig.json",
    "chars": 471,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es2017\",\n    \"module\": \"es2015\",\n    \"moduleResolution\": \"node\",\n    \"lib\": [\n  "
  },
  {
    "path": "tslint.json",
    "chars": 1118,
    "preview": "{\n  \"rules\": {\n    \"arrow-parens\": true,\n    \"class-name\": true,\n    \"indent\": [\n      true,\n      \"spaces\",\n      2\n   "
  }
]

About this extraction

This page contains the full source code of the rough-stuff/rough-notation GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (29.7 KB), approximately 8.2k tokens, and a symbol index with 47 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!