Showing preview only (803K chars total). Download the full file or copy to clipboard to get everything.
Repository: rough-stuff/rough
Branch: master
Commit: 56a2762171b1
Files: 78
Total size: 771.1 KB
Directory structure:
gitextract_5rbjmnml/
├── .eslintrc.json
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│ ├── canvas.ts
│ ├── core.ts
│ ├── fillers/
│ │ ├── dashed-filler.ts
│ │ ├── dot-filler.ts
│ │ ├── filler-interface.ts
│ │ ├── filler.ts
│ │ ├── hachure-filler.ts
│ │ ├── hatch-filler.ts
│ │ ├── scan-line-hachure.ts
│ │ ├── zigzag-filler.ts
│ │ └── zigzag-line-filler.ts
│ ├── generator.ts
│ ├── geometry.ts
│ ├── math.ts
│ ├── renderer.ts
│ ├── rough.ts
│ └── svg.ts
├── tsconfig.json
└── visual-tests/
├── canvas/
│ ├── arc.html
│ ├── arc2.html
│ ├── curve-seed.html
│ ├── curve.html
│ ├── curve2.html
│ ├── curve3.html
│ ├── curve4.html
│ ├── dashed/
│ │ ├── arc.html
│ │ ├── curve.html
│ │ ├── ellipse.html
│ │ ├── line.html
│ │ ├── linearpath.html
│ │ ├── path-with-transform.html
│ │ ├── path.html
│ │ ├── polygon.html
│ │ └── rectangle.html
│ ├── ellipse.html
│ ├── ellipse2.html
│ ├── ellipse3.html
│ ├── line.html
│ ├── linearpath.html
│ ├── map.html
│ ├── path-with-transform.html
│ ├── path.html
│ ├── path2.html
│ ├── path3.html
│ ├── path4.html
│ ├── path5.html
│ ├── path6.html
│ ├── path7.html
│ ├── poly-seed.html
│ ├── polygon.html
│ ├── polygon2.html
│ ├── rectangle.html
│ ├── singlestroke/
│ │ ├── arc.html
│ │ ├── curve.html
│ │ ├── ellipse.html
│ │ ├── line.html
│ │ ├── path.html
│ │ ├── polygon.html
│ │ └── rectangle.html
│ └── us.json
└── svg/
├── dashed/
│ ├── ellipse.html
│ ├── line.html
│ ├── polygon.html
│ └── rectangle.html
├── ellipse.html
├── line.html
├── polygon.html
├── rectangle.html
└── rectangle2.html
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": [
"@typescript-eslint"
],
"rules": {
"arrow-parens": [
"error",
"always"
],
"prefer-const": "error",
"no-eval": "error",
"no-trailing-spaces": "error",
"no-var": "error",
"quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"semi": "error",
"comma-dangle": [
"error",
"always-multiline"
],
"eqeqeq": "error",
"no-useless-escape": "off",
"@typescript-eslint/indent": [
"error",
2
],
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}
================================================
FILE: .github/FUNDING.yml
================================================
github: pshihn
open_collective: rough
================================================
FILE: .gitignore
================================================
.cache
.DS_Store
node_modules
z
bin
dist
bundled
bug.html
================================================
FILE: .npmignore
================================================
.cache
.DS_Store
z
node_modules
src
tslint.json
rollup.config.js
.gitignore
visual-tests
================================================
FILE: CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
# [4.5.0] - 2021-05-09
* Better algorithm for nested and intersecting paths https://github.com/rough-stuff/rough/issues/183
* Improved zigzag fill for concave shapes and nested paths.
* Fixed "dots" fill when roughness was <1 Itw as creatings weird shapes https://github.com/rough-stuff/rough/issues/193
* Configure precision when rendering to canvas as well as SVG using `fixedDecimalPlaceDigits` property.
* Solid fill was broken for Arcs if arc angle was > 180 degrees
* Remove notch from ellipses when roughness = 0
# [4.4.0] - 2021-05-09
* Added `preserveVertices` option when drawing shapes. Especially useful in paths. When rendering a shape, the vertices or the end points of the shape are not randomized if this is set to TRUE. This allows connected segments to always be connected.
## [4.3.0] - 2020-05-11
* Added options to draw dashed lines - *strokeLineDash, strokeLineDashOffset, fillLineDash, fillLineDashOffset*
* Added option to disable double stroking effect - *disableMultiStroke, disableMultiStrokeFill*.
* Bug fixes to solid fill in SVG which was not obeying evenodd rules by default
## [4.1.0] - 2020-01-13
* Added ability to **fill** non-svg curves
## [4.0.0] - 2020-01-13
* Add optional seeding for randomness to ensure shapes generated with same arguments result in same vectors
* Implemented a new algorithm for hachure generation based on scanlines. Smaller in code size, and about 20% faster
* Algorithm update - adjust shape randomness and curve-step-counts based on the size of the shape
* Removed async/worker builds - can be achieved in the app level, so no need to be in the lib
* Support no-stroke sketching. `stroke: "none"` will not generate outline vectors anymore
* Removed `sunburst` fill style - it had a lot of corner cases where it did not work, and not very popular.
## [3.1.0] - 2019-03-14
* Added three new fill styles: **sunburst**, **dashed**, and **zigzag-line**
* Added three new properties in *Options* to support these fill styles:
* **dashOffset** - length of dashes in dashed fill
* **dashGap** - length of gap between dashes in dashed fill
* **zigzagOffset** - width of zigzag triangle when using zigzag-lines fill
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 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.js
<b>Rough.js</b> is a small (\<9 kB) graphics library that lets you draw in a _sketchy_, _hand-drawn-like_, style.
The library defines primitives to draw lines, curves, arcs, polygons, circles, and ellipses. It also supports drawing [SVG paths](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths).
Rough.js works with both [Canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) and [SVG](https://developer.mozilla.org/en-US/docs/Web/SVG).

[@RoughLib](https://twitter.com/RoughLib) on Twitter.
## Install
from npm:
```
npm install --save roughjs
```
Or get the latest using unpkg: https://unpkg.com/roughjs@latest/bundled/rough.js
If you are looking for bundled version in different formats, the npm package will have these in the following locations:
CommonJS: `roughjs/bundled/rough.cjs.js`
ESM: `roughjs/bundled/rough.esm.js`
Browser IIFE: `roughjs/bundled/rough.js`
## Usage

```js
const rc = rough.canvas(document.getElementById('canvas'));
rc.rectangle(10, 10, 200, 200); // x, y, width, height
```
or SVG
```js
const rc = rough.svg(svg);
let node = rc.rectangle(10, 10, 200, 200); // x, y, width, height
svg.appendChild(node);
```
### Lines and Ellipses

```js
rc.circle(80, 120, 50); // centerX, centerY, diameter
rc.ellipse(300, 100, 150, 80); // centerX, centerY, width, height
rc.line(80, 120, 300, 100); // x1, y1, x2, y2
```
### Filling

```js
rc.circle(50, 50, 80, { fill: 'red' }); // fill with red hachure
rc.rectangle(120, 15, 80, 80, { fill: 'red' });
rc.circle(50, 150, 80, {
fill: "rgb(10,150,10)",
fillWeight: 3 // thicker lines for hachure
});
rc.rectangle(220, 15, 80, 80, {
fill: 'red',
hachureAngle: 60, // angle of hachure,
hachureGap: 8
});
rc.rectangle(120, 105, 80, 80, {
fill: 'rgba(255,0,200,0.2)',
fillStyle: 'solid' // solid fill
});
```
Fill styles can be: **hachure**(default), **solid**, **zigzag**, **cross-hatch**, **dots**, **dashed**, or **zigzag-line**

### Sketching style

```js
rc.rectangle(15, 15, 80, 80, { roughness: 0.5, fill: 'red' });
rc.rectangle(120, 15, 80, 80, { roughness: 2.8, fill: 'blue' });
rc.rectangle(220, 15, 80, 80, { bowing: 6, stroke: 'green', strokeWidth: 3 });
```
### SVG Paths

```js
rc.path('M80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 Z', { fill: 'green' });
rc.path('M230 80 A 45 45, 0, 1, 0, 275 125 L 275 80 Z', { fill: 'purple' });
rc.path('M80 230 A 45 45, 0, 0, 1, 125 275 L 125 230 Z', { fill: 'red' });
rc.path('M230 230 A 45 45, 0, 1, 1, 275 275 L 275 230 Z', { fill: 'blue' });
```
SVG Path with simplification:
 
## Examples

[View examples here](https://github.com/pshihn/rough/wiki/Examples)
## API & Documentation
[Full Rough.js API](https://github.com/pshihn/rough/wiki)
## Credits
Some of the core algorithms were adapted from [handy](https://www.gicentre.net/software/#/handy/) processing lib.
Algorithm to convert SVG arcs to Canvas [described here](https://www.w3.org/TR/SVG/implnote.html) was adapted from [Mozilla codebase](https://hg.mozilla.org/mozilla-central/file/17156fbebbc8/content/svg/content/src/nsSVGPathDataParser.cpp#l887)
## 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://excalidraw.com/"><img src="https://avatars.githubusercontent.com/u/59452120?s=64&v=4"></a>
<a href="https://www.diagrams.net/"><img src="https://avatars.githubusercontent.com/u/1769238?s=64&v=4"></a>
<a href="https://terrastruct.com/"><img width="64" height="64" src="https://roughjs.com/images/sponsors/terrastruct.png"></a>
<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>
## License
[MIT License](https://github.com/pshihn/rough/blob/master/LICENSE) (c) [Preet Shihn](https://twitter.com/preetster)
================================================
FILE: package.json
================================================
{
"name": "roughjs",
"version": "4.6.6",
"description": "Create graphics using HTML Canvas or SVG with a hand-drawn, sketchy, appearance.",
"main": "bundled/rough.cjs.js",
"module": "bundled/rough.esm.js",
"types": "bin/rough.d.ts",
"scripts": {
"build": "rm -rf bin && tsc && rollup -c",
"lint": "eslint --ext ts src",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pshihn/rough.git"
},
"keywords": [
"canvas",
"svg",
"graphics",
"sketchy",
"hand drawn",
"hand-drawn"
],
"author": "Preet Shihn <preetshihn@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/pshihn/rough/issues"
},
"homepage": "https://roughjs.com",
"devDependencies": {
"@rollup/plugin-node-resolve": "^13.0.6",
"@rollup/plugin-typescript": "^8.3.0",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"eslint": "^7.32.0",
"rollup": "^2.61.0",
"rollup-plugin-terser": "^7.0.2",
"tslib": "^2.3.1",
"typescript": "^4.5.3"
},
"dependencies": {
"hachure-fill": "^0.5.2",
"path-data-parser": "^0.1.0",
"points-on-curve": "^0.2.0",
"points-on-path": "^0.2.1"
}
}
================================================
FILE: rollup.config.js
================================================
import { nodeResolve } from '@rollup/plugin-node-resolve';
import { terser } from "rollup-plugin-terser";
import typescript from '@rollup/plugin-typescript';
const input = 'bin/rough.js';
export default [
{
input,
output: {
file: 'bundled/rough.js',
format: 'iife',
name: 'rough'
},
plugins: [nodeResolve(), terser({
output: {
comments: false
}
})]
},
{
input,
output: {
file: 'bundled/rough.esm.js',
format: 'esm'
},
plugins: [nodeResolve(), terser({
output: {
comments: false
}
})]
},
{
input: 'src/rough.ts',
output: {
file: 'bundled/rough.cjs.js',
format: 'cjs'
},
plugins: [nodeResolve(), typescript({ target: "es5", importHelpers: true }), terser({
output: {
comments: false
}
})]
}
];
================================================
FILE: src/canvas.ts
================================================
import { Config, Options, ResolvedOptions, Drawable, OpSet } from './core';
import { RoughGenerator } from './generator';
import { Point } from './geometry';
export class RoughCanvas {
private gen: RoughGenerator;
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
constructor(canvas: HTMLCanvasElement, config?: Config) {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d')!;
this.gen = new RoughGenerator(config);
}
draw(drawable: Drawable): void {
const sets = drawable.sets || [];
const o = drawable.options || this.getDefaultOptions();
const ctx = this.ctx;
const precision = drawable.options.fixedDecimalPlaceDigits;
for (const drawing of sets) {
switch (drawing.type) {
case 'path':
ctx.save();
ctx.strokeStyle = o.stroke === 'none' ? 'transparent' : o.stroke;
ctx.lineWidth = o.strokeWidth;
if (o.strokeLineDash) {
ctx.setLineDash(o.strokeLineDash);
}
if (o.strokeLineDashOffset) {
ctx.lineDashOffset = o.strokeLineDashOffset;
}
this._drawToContext(ctx, drawing, precision);
ctx.restore();
break;
case 'fillPath': {
ctx.save();
ctx.fillStyle = o.fill || '';
const fillRule: CanvasFillRule = (drawable.shape === 'curve' || drawable.shape === 'polygon' || drawable.shape === 'path') ? 'evenodd' : 'nonzero';
this._drawToContext(ctx, drawing, precision, fillRule);
ctx.restore();
break;
}
case 'fillSketch':
this.fillSketch(ctx, drawing, o);
break;
}
}
}
private fillSketch(ctx: CanvasRenderingContext2D, drawing: OpSet, o: ResolvedOptions) {
let fweight = o.fillWeight;
if (fweight < 0) {
fweight = o.strokeWidth / 2;
}
ctx.save();
if (o.fillLineDash) {
ctx.setLineDash(o.fillLineDash);
}
if (o.fillLineDashOffset) {
ctx.lineDashOffset = o.fillLineDashOffset;
}
ctx.strokeStyle = o.fill || '';
ctx.lineWidth = fweight;
this._drawToContext(ctx, drawing, o.fixedDecimalPlaceDigits);
ctx.restore();
}
private _drawToContext(ctx: CanvasRenderingContext2D, drawing: OpSet, fixedDecimals?: number, rule: CanvasFillRule = 'nonzero') {
ctx.beginPath();
for (const item of drawing.ops) {
const data = ((typeof fixedDecimals === 'number') && fixedDecimals >= 0) ? (item.data.map((d) => +d.toFixed(fixedDecimals))) : item.data;
switch (item.op) {
case 'move':
ctx.moveTo(data[0], data[1]);
break;
case 'bcurveTo':
ctx.bezierCurveTo(data[0], data[1], data[2], data[3], data[4], data[5]);
break;
case 'lineTo':
ctx.lineTo(data[0], data[1]);
break;
}
}
if (drawing.type === 'fillPath') {
ctx.fill(rule);
} else {
ctx.stroke();
}
}
get generator(): RoughGenerator {
return this.gen;
}
getDefaultOptions(): ResolvedOptions {
return this.gen.defaultOptions;
}
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable {
const d = this.gen.line(x1, y1, x2, y2, options);
this.draw(d);
return d;
}
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable {
const d = this.gen.rectangle(x, y, width, height, options);
this.draw(d);
return d;
}
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable {
const d = this.gen.ellipse(x, y, width, height, options);
this.draw(d);
return d;
}
circle(x: number, y: number, diameter: number, options?: Options): Drawable {
const d = this.gen.circle(x, y, diameter, options);
this.draw(d);
return d;
}
linearPath(points: Point[], options?: Options): Drawable {
const d = this.gen.linearPath(points, options);
this.draw(d);
return d;
}
polygon(points: Point[], options?: Options): Drawable {
const d = this.gen.polygon(points, options);
this.draw(d);
return d;
}
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean = false, options?: Options): Drawable {
const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
this.draw(d);
return d;
}
curve(points: Point[] | Point[][], options?: Options): Drawable {
const d = this.gen.curve(points, options);
this.draw(d);
return d;
}
path(d: string, options?: Options): Drawable {
const drawing = this.gen.path(d, options);
this.draw(drawing);
return drawing;
}
}
================================================
FILE: src/core.ts
================================================
import { Point } from './geometry';
import { Random } from './math';
export const SVGNS = 'http://www.w3.org/2000/svg';
export interface Config {
options?: Options;
}
export interface DrawingSurface {
width: number | SVGAnimatedLength;
height: number | SVGAnimatedLength;
}
export interface Options {
maxRandomnessOffset?: number;
roughness?: number;
bowing?: number;
stroke?: string;
strokeWidth?: number;
curveFitting?: number;
curveTightness?: number;
curveStepCount?: number;
fill?: string;
fillStyle?: string;
fillWeight?: number;
hachureAngle?: number;
hachureGap?: number;
simplification?: number;
dashOffset?: number;
dashGap?: number;
zigzagOffset?: number;
seed?: number;
strokeLineDash?: number[];
strokeLineDashOffset?: number;
fillLineDash?: number[];
fillLineDashOffset?: number;
disableMultiStroke?: boolean;
disableMultiStrokeFill?: boolean;
preserveVertices?: boolean;
fixedDecimalPlaceDigits?: number;
fillShapeRoughnessGain?: number;
}
export interface ResolvedOptions extends Options {
maxRandomnessOffset: number;
roughness: number;
bowing: number;
stroke: string;
strokeWidth: number;
curveFitting: number;
curveTightness: number;
curveStepCount: number;
fillStyle: string;
fillWeight: number;
hachureAngle: number;
hachureGap: number;
dashOffset: number;
dashGap: number;
zigzagOffset: number;
seed: number;
randomizer?: Random;
disableMultiStroke: boolean;
disableMultiStrokeFill: boolean;
preserveVertices: boolean;
fillShapeRoughnessGain: number;
}
export declare type OpType = 'move' | 'bcurveTo' | 'lineTo';
export declare type OpSetType = 'path' | 'fillPath' | 'fillSketch';
export interface Op {
op: OpType;
data: number[];
}
export interface OpSet {
type: OpSetType;
ops: Op[];
size?: Point;
path?: string;
}
export interface Drawable {
shape: string;
options: ResolvedOptions;
sets: OpSet[];
}
export interface PathInfo {
d: string;
stroke: string;
strokeWidth: number;
fill?: string;
}
================================================
FILE: src/fillers/dashed-filler.ts
================================================
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line, lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class DashedFiller implements PatternFiller {
private helper: RenderHelper;
constructor(helper: RenderHelper) {
this.helper = helper;
}
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
const lines = polygonHachureLines(polygonList, o);
return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
}
private dashedLine(lines: Line[], o: ResolvedOptions): Op[] {
const offset = o.dashOffset < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashOffset;
const gap = o.dashGap < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashGap;
const ops: Op[] = [];
lines.forEach((line) => {
const length = lineLength(line);
const count = Math.floor(length / (offset + gap));
const startOffset = (length + gap - (count * (offset + gap))) / 2;
let p1 = line[0];
let p2 = line[1];
if (p1[0] > p2[0]) {
p1 = line[1];
p2 = line[0];
}
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
for (let i = 0; i < count; i++) {
const lstart = i * (offset + gap);
const lend = lstart + offset;
const start: Point = [p1[0] + (lstart * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha) + (startOffset * Math.sin(alpha))];
const end: Point = [p1[0] + (lend * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha)) + (startOffset * Math.sin(alpha))];
ops.push(...this.helper.doubleLineOps(start[0], start[1], end[0], end[1], o));
}
});
return ops;
}
}
================================================
FILE: src/fillers/dot-filler.ts
================================================
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line, lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class DotFiller implements PatternFiller {
private helper: RenderHelper;
constructor(helper: RenderHelper) {
this.helper = helper;
}
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
o = Object.assign({}, o, { hachureAngle: 0 });
const lines = polygonHachureLines(polygonList, o);
return this.dotsOnLines(lines, o);
}
private dotsOnLines(lines: Line[], o: ResolvedOptions): OpSet {
const ops: Op[] = [];
let gap = o.hachureGap;
if (gap < 0) {
gap = o.strokeWidth * 4;
}
gap = Math.max(gap, 0.1);
let fweight = o.fillWeight;
if (fweight < 0) {
fweight = o.strokeWidth / 2;
}
const ro = gap / 4;
for (const line of lines) {
const length = lineLength(line);
const dl = length / gap;
const count = Math.ceil(dl) - 1;
const offset = length - (count * gap);
const x = ((line[0][0] + line[1][0]) / 2) - (gap / 4);
const minY = Math.min(line[0][1], line[1][1]);
for (let i = 0; i < count; i++) {
const y = minY + offset + (i * gap);
const cx = (x - ro) + Math.random() * 2 * ro;
const cy = (y - ro) + Math.random() * 2 * ro;
const el = this.helper.ellipse(cx, cy, fweight, fweight, o);
ops.push(...el.ops);
}
}
return { type: 'fillSketch', ops };
}
}
================================================
FILE: src/fillers/filler-interface.ts
================================================
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point } from '../geometry';
export interface PatternFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
export interface RenderHelper {
randOffset(x: number, o: ResolvedOptions): number;
randOffsetWithRange(min: number, max: number, o: ResolvedOptions): number;
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
doubleLineOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
}
================================================
FILE: src/fillers/filler.ts
================================================
import { ResolvedOptions } from '../core';
import { PatternFiller, RenderHelper } from './filler-interface';
import { HachureFiller } from './hachure-filler';
import { ZigZagFiller } from './zigzag-filler';
import { HatchFiller } from './hatch-filler';
import { DotFiller } from './dot-filler';
import { DashedFiller } from './dashed-filler';
import { ZigZagLineFiller } from './zigzag-line-filler';
const fillers: { [name: string]: PatternFiller } = {};
export function getFiller(o: ResolvedOptions, helper: RenderHelper): PatternFiller {
let fillerName = o.fillStyle || 'hachure';
if (!fillers[fillerName]) {
switch (fillerName) {
case 'zigzag':
if (!fillers[fillerName]) {
fillers[fillerName] = new ZigZagFiller(helper);
}
break;
case 'cross-hatch':
if (!fillers[fillerName]) {
fillers[fillerName] = new HatchFiller(helper);
}
break;
case 'dots':
if (!fillers[fillerName]) {
fillers[fillerName] = new DotFiller(helper);
}
break;
case 'dashed':
if (!fillers[fillerName]) {
fillers[fillerName] = new DashedFiller(helper);
}
break;
case 'zigzag-line':
if (!fillers[fillerName]) {
fillers[fillerName] = new ZigZagLineFiller(helper);
}
break;
case 'hachure':
default:
fillerName = 'hachure';
if (!fillers[fillerName]) {
fillers[fillerName] = new HachureFiller(helper);
}
break;
}
}
return fillers[fillerName];
}
================================================
FILE: src/fillers/hachure-filler.ts
================================================
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class HachureFiller implements PatternFiller {
private helper: RenderHelper;
constructor(helper: RenderHelper) {
this.helper = helper;
}
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
return this._fillPolygons(polygonList, o);
}
protected _fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
const lines = polygonHachureLines(polygonList, o);
const ops = this.renderLines(lines, o);
return { type: 'fillSketch', ops };
}
protected renderLines(lines: Line[], o: ResolvedOptions): Op[] {
const ops: Op[] = [];
for (const line of lines) {
ops.push(...this.helper.doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
}
return ops;
}
}
================================================
FILE: src/fillers/hatch-filler.ts
================================================
import { HachureFiller } from './hachure-filler';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export class HatchFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
const set = this._fillPolygons(polygonList, o);
const o2 = Object.assign({}, o, { hachureAngle: o.hachureAngle + 90 });
const set2 = this._fillPolygons(polygonList, o2);
set.ops = set.ops.concat(set2.ops);
return set;
}
}
================================================
FILE: src/fillers/scan-line-hachure.ts
================================================
import { hachureLines } from 'hachure-fill';
import { Point, Line } from '../geometry';
import { ResolvedOptions } from '../core';
export function polygonHachureLines(polygonList: Point[][], o: ResolvedOptions): Line[] {
const angle = o.hachureAngle + 90;
let gap = o.hachureGap;
if (gap < 0) {
gap = o.strokeWidth * 4;
}
gap = Math.round(Math.max(gap, 0.1));
let skipOffset = 1;
if (o.roughness >= 1) {
if ((o.randomizer?.next() || Math.random()) > 0.7) {
skipOffset = gap;
}
}
return hachureLines(polygonList, gap, angle, skipOffset || 1);
}
================================================
FILE: src/fillers/zigzag-filler.ts
================================================
import { HachureFiller } from './hachure-filler';
import { polygonHachureLines } from './scan-line-hachure';
import { ResolvedOptions, OpSet } from '../core';
import { Point, Line, lineLength } from '../geometry';
export class ZigZagFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
let gap = o.hachureGap;
if (gap < 0) {
gap = o.strokeWidth * 4;
}
gap = Math.max(gap, 0.1);
const o2 = Object.assign({}, o, { hachureGap: gap });
const lines = polygonHachureLines(polygonList, o2);
const zigZagAngle = (Math.PI / 180) * o.hachureAngle;
const zigzagLines: Line[] = [];
const dgx = gap * 0.5 * Math.cos(zigZagAngle);
const dgy = gap * 0.5 * Math.sin(zigZagAngle);
for (const [p1, p2] of lines) {
if (lineLength([p1, p2])) {
zigzagLines.push([
[p1[0] - dgx, p1[1] + dgy],
[...p2],
], [
[p1[0] + dgx, p1[1] - dgy],
[...p2],
]);
}
}
const ops = this.renderLines(zigzagLines, o);
return { type: 'fillSketch', ops };
}
}
================================================
FILE: src/fillers/zigzag-line-filler.ts
================================================
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line, lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class ZigZagLineFiller implements PatternFiller {
private helper: RenderHelper;
constructor(helper: RenderHelper) {
this.helper = helper;
}
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
const gap = o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap;
const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
o = Object.assign({}, o, { hachureGap: gap + zo });
const lines = polygonHachureLines(polygonList, o);
return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };
}
private zigzagLines(lines: Line[], zo: number, o: ResolvedOptions): Op[] {
const ops: Op[] = [];
lines.forEach((line) => {
const length = lineLength(line);
const count = Math.round(length / (2 * zo));
let p1 = line[0];
let p2 = line[1];
if (p1[0] > p2[0]) {
p1 = line[1];
p2 = line[0];
}
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
for (let i = 0; i < count; i++) {
const lstart = i * 2 * zo;
const lend = (i + 1) * 2 * zo;
const dz = Math.sqrt(2 * Math.pow(zo, 2));
const start: Point = [p1[0] + (lstart * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha)];
const end: Point = [p1[0] + (lend * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha))];
const middle: Point = [start[0] + dz * Math.cos(alpha + Math.PI / 4), start[1] + dz * Math.sin(alpha + Math.PI / 4)];
ops.push(
...this.helper.doubleLineOps(start[0], start[1], middle[0], middle[1], o),
...this.helper.doubleLineOps(middle[0], middle[1], end[0], end[1], o)
);
}
});
return ops;
}
}
================================================
FILE: src/generator.ts
================================================
import { Config, Options, Drawable, OpSet, Op, ResolvedOptions, PathInfo } from './core.js';
import { Point } from './geometry.js';
import { line, solidFillPolygon, patternFillPolygons, rectangle, ellipseWithParams, generateEllipseParams, linearPath, arc, patternFillArc, curve, svgPath } from './renderer.js';
import { randomSeed } from './math.js';
import { curveToBezier } from 'points-on-curve/lib/curve-to-bezier.js';
import { pointsOnBezierCurves } from 'points-on-curve';
import { pointsOnPath } from 'points-on-path';
const NOS = 'none';
export class RoughGenerator {
private config: Config;
defaultOptions: ResolvedOptions = {
maxRandomnessOffset: 2,
roughness: 1,
bowing: 1,
stroke: '#000',
strokeWidth: 1,
curveTightness: 0,
curveFitting: 0.95,
curveStepCount: 9,
fillStyle: 'hachure',
fillWeight: -1,
hachureAngle: -41,
hachureGap: -1,
dashOffset: -1,
dashGap: -1,
zigzagOffset: -1,
seed: 0,
disableMultiStroke: false,
disableMultiStrokeFill: false,
preserveVertices: false,
fillShapeRoughnessGain: 0.8,
};
constructor(config?: Config) {
this.config = config || {};
if (this.config.options) {
this.defaultOptions = this._o(this.config.options);
}
}
static newSeed(): number {
return randomSeed();
}
private _o(options?: Options): ResolvedOptions {
return options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
}
private _d(shape: string, sets: OpSet[], options: ResolvedOptions): Drawable {
return { shape, sets: sets || [], options: options || this.defaultOptions };
}
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable {
const o = this._o(options);
return this._d('line', [line(x1, y1, x2, y2, o)], o);
}
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable {
const o = this._o(options);
const paths = [];
const outline = rectangle(x, y, width, height, o);
if (o.fill) {
const points: Point[] = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]];
if (o.fillStyle === 'solid') {
paths.push(solidFillPolygon([points], o));
} else {
paths.push(patternFillPolygons([points], o));
}
}
if (o.stroke !== NOS) {
paths.push(outline);
}
return this._d('rectangle', paths, o);
}
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable {
const o = this._o(options);
const paths: OpSet[] = [];
const ellipseParams = generateEllipseParams(width, height, o);
const ellipseResponse = ellipseWithParams(x, y, o, ellipseParams);
if (o.fill) {
if (o.fillStyle === 'solid') {
const shape = ellipseWithParams(x, y, o, ellipseParams).opset;
shape.type = 'fillPath';
paths.push(shape);
} else {
paths.push(patternFillPolygons([ellipseResponse.estimatedPoints], o));
}
}
if (o.stroke !== NOS) {
paths.push(ellipseResponse.opset);
}
return this._d('ellipse', paths, o);
}
circle(x: number, y: number, diameter: number, options?: Options): Drawable {
const ret = this.ellipse(x, y, diameter, diameter, options);
ret.shape = 'circle';
return ret;
}
linearPath(points: Point[], options?: Options): Drawable {
const o = this._o(options);
return this._d('linearPath', [linearPath(points, false, o)], o);
}
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean = false, options?: Options): Drawable {
const o = this._o(options);
const paths = [];
const outline = arc(x, y, width, height, start, stop, closed, true, o);
if (closed && o.fill) {
if (o.fillStyle === 'solid') {
const fillOptions: ResolvedOptions = { ...o };
fillOptions.disableMultiStroke = true;
const shape = arc(x, y, width, height, start, stop, true, false, fillOptions);
shape.type = 'fillPath';
paths.push(shape);
} else {
paths.push(patternFillArc(x, y, width, height, start, stop, o));
}
}
if (o.stroke !== NOS) {
paths.push(outline);
}
return this._d('arc', paths, o);
}
curve(points: Point[] | Point[][], options?: Options): Drawable {
const o = this._o(options);
const paths: OpSet[] = [];
const outline = curve(points, o);
if (o.fill && o.fill !== NOS) {
if (o.fillStyle === 'solid') {
const fillShape = curve(points, { ...o, disableMultiStroke: true, roughness: o.roughness ? (o.roughness + o.fillShapeRoughnessGain) : 0 });
paths.push({
type: 'fillPath',
ops: this._mergedShape(fillShape.ops),
});
} else {
const polyPoints: Point[] = [];
const inputPoints = points;
if (inputPoints.length) {
const p1 = inputPoints[0];
const pointsList = (typeof p1[0] === 'number') ? [inputPoints as Point[]] : inputPoints as Point[][];
for (const points of pointsList) {
if (points.length < 3) {
polyPoints.push(...points);
} else if (points.length === 3) {
polyPoints.push(...pointsOnBezierCurves(curveToBezier([
points[0],
points[0],
points[1],
points[2],
]), 10, (1 + o.roughness) / 2));
} else {
polyPoints.push(...pointsOnBezierCurves(curveToBezier(points), 10, (1 + o.roughness) / 2));
}
}
}
if (polyPoints.length) {
paths.push(patternFillPolygons([polyPoints], o));
}
}
}
if (o.stroke !== NOS) {
paths.push(outline);
}
return this._d('curve', paths, o);
}
polygon(points: Point[], options?: Options): Drawable {
const o = this._o(options);
const paths: OpSet[] = [];
const outline = linearPath(points, true, o);
if (o.fill) {
if (o.fillStyle === 'solid') {
paths.push(solidFillPolygon([points], o));
} else {
paths.push(patternFillPolygons([points], o));
}
}
if (o.stroke !== NOS) {
paths.push(outline);
}
return this._d('polygon', paths, o);
}
path(d: string, options?: Options): Drawable {
const o = this._o(options);
const paths: OpSet[] = [];
if (!d) {
return this._d('path', paths, o);
}
d = (d || '').replace(/\n/g, ' ').replace(/(-\s)/g, '-').replace('/(\s\s)/g', ' ');
const hasFill = o.fill && o.fill !== 'transparent' && o.fill !== NOS;
const hasStroke = o.stroke !== NOS;
const simplified = !!(o.simplification && (o.simplification < 1));
const distance = simplified ? (4 - 4 * (o.simplification || 1)) : ((1 + o.roughness) / 2);
const sets = pointsOnPath(d, 1, distance);
const shape = svgPath(d, o);
if (hasFill) {
if (o.fillStyle === 'solid') {
if (sets.length === 1) {
const fillShape = svgPath(d, { ...o, disableMultiStroke: true, roughness: o.roughness ? (o.roughness + o.fillShapeRoughnessGain) : 0 });
paths.push({
type: 'fillPath',
ops: this._mergedShape(fillShape.ops),
});
} else {
paths.push(solidFillPolygon(sets, o));
}
} else {
paths.push(patternFillPolygons(sets, o));
}
}
if (hasStroke) {
if (simplified) {
sets.forEach((set) => {
paths.push(linearPath(set, false, o));
});
} else {
paths.push(shape);
}
}
return this._d('path', paths, o);
}
opsToPath(drawing: OpSet, fixedDecimals?: number): string {
let path = '';
for (const item of drawing.ops) {
const data = ((typeof fixedDecimals === 'number') && fixedDecimals >= 0) ? (item.data.map((d) => +d.toFixed(fixedDecimals))) : item.data;
switch (item.op) {
case 'move':
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;
}
}
return path.trim();
}
toPaths(drawable: Drawable): PathInfo[] {
const sets = drawable.sets || [];
const o = drawable.options || this.defaultOptions;
const paths: PathInfo[] = [];
for (const drawing of sets) {
let path: PathInfo | null = null;
switch (drawing.type) {
case 'path':
path = {
d: this.opsToPath(drawing),
stroke: o.stroke,
strokeWidth: o.strokeWidth,
fill: NOS,
};
break;
case 'fillPath':
path = {
d: this.opsToPath(drawing),
stroke: NOS,
strokeWidth: 0,
fill: o.fill || NOS,
};
break;
case 'fillSketch':
path = this.fillSketch(drawing, o);
break;
}
if (path) {
paths.push(path);
}
}
return paths;
}
private fillSketch(drawing: OpSet, o: ResolvedOptions): PathInfo {
let fweight = o.fillWeight;
if (fweight < 0) {
fweight = o.strokeWidth / 2;
}
return {
d: this.opsToPath(drawing),
stroke: o.fill || NOS,
strokeWidth: fweight,
fill: NOS,
};
}
private _mergedShape(input: Op[]): Op[] {
return input.filter((d, i) => {
if (i === 0) {
return true;
}
if (d.op === 'move') {
return false;
}
return true;
});
}
}
================================================
FILE: src/geometry.ts
================================================
export type Point = [number, number];
export type Line = [Point, Point];
export interface Rectangle {
x: number;
y: number;
width: number;
height: number;
}
export function lineLength(line: Line): number {
const p1 = line[0];
const p2 = line[1];
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
}
================================================
FILE: src/math.ts
================================================
export function randomSeed(): number {
return Math.floor(Math.random() * 2 ** 31);
}
export class Random {
private seed: number;
constructor(seed: number) {
this.seed = seed;
}
next(): number {
if (this.seed) {
return ((2 ** 31 - 1) & (this.seed = Math.imul(48271, this.seed))) / 2 ** 31;
} else {
return Math.random();
}
}
}
================================================
FILE: src/renderer.ts
================================================
import { ResolvedOptions, Op, OpSet } from './core.js';
import { Point } from './geometry.js';
import { getFiller } from './fillers/filler.js';
import { RenderHelper } from './fillers/filler-interface.js';
import { Random } from './math.js';
import { parsePath, normalize, absolutize } from 'path-data-parser';
interface EllipseParams {
rx: number;
ry: number;
increment: number;
}
const helper: RenderHelper = {
randOffset,
randOffsetWithRange,
ellipse,
doubleLineOps: doubleLineFillOps,
};
export function line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): OpSet {
return { type: 'path', ops: _doubleLine(x1, y1, x2, y2, o) };
}
export function linearPath(points: Point[], close: boolean, o: ResolvedOptions): OpSet {
const len = (points || []).length;
if (len > 2) {
const ops: Op[] = [];
for (let i = 0; i < (len - 1); i++) {
ops.push(..._doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
}
if (close) {
ops.push(..._doubleLine(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o));
}
return { type: 'path', ops };
} else if (len === 2) {
return line(points[0][0], points[0][1], points[1][0], points[1][1], o);
}
return { type: 'path', ops: [] };
}
export function polygon(points: Point[], o: ResolvedOptions): OpSet {
return linearPath(points, true, o);
}
export function rectangle(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet {
const points: Point[] = [
[x, y],
[x + width, y],
[x + width, y + height],
[x, y + height],
];
return polygon(points, o);
}
export function curve(inputPoints: Point[] | Point[][], o: ResolvedOptions): OpSet {
if (inputPoints.length) {
const p1 = inputPoints[0];
const pointsList = (typeof p1[0] === 'number') ? [inputPoints as Point[]] : inputPoints as Point[][];
const o1 = _curveWithOffset(pointsList[0], 1 * (1 + o.roughness * 0.2), o);
const o2 = o.disableMultiStroke ? [] : _curveWithOffset(pointsList[0], 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
for (let i = 1; i < pointsList.length; i++) {
const points = pointsList[i];
if (points.length) {
const underlay = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
const overlay = o.disableMultiStroke ? [] : _curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
for (const item of underlay) {
if (item.op !== 'move') {
o1.push(item);
}
}
for (const item of overlay) {
if (item.op !== 'move') {
o2.push(item);
}
}
}
}
return { type: 'path', ops: o1.concat(o2) };
}
return { type: 'path', ops: [] };
}
export interface EllipseResult {
opset: OpSet;
estimatedPoints: Point[];
}
export function ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet {
const params = generateEllipseParams(width, height, o);
return ellipseWithParams(x, y, o, params).opset;
}
export function generateEllipseParams(width: number, height: number, o: ResolvedOptions): EllipseParams {
const psq = Math.sqrt(Math.PI * 2 * Math.sqrt((Math.pow(width / 2, 2) + Math.pow(height / 2, 2)) / 2));
const stepCount = Math.ceil(Math.max(o.curveStepCount, (o.curveStepCount / Math.sqrt(200)) * psq));
const increment = (Math.PI * 2) / stepCount;
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
const curveFitRandomness = 1 - o.curveFitting;
rx += _offsetOpt(rx * curveFitRandomness, o);
ry += _offsetOpt(ry * curveFitRandomness, o);
return { increment, rx, ry };
}
export function ellipseWithParams(x: number, y: number, o: ResolvedOptions, ellipseParams: EllipseParams): EllipseResult {
const [ap1, cp1] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1, ellipseParams.increment * _offset(0.1, _offset(0.4, 1, o), o), o);
let o1 = _curve(ap1, null, o);
if ((!o.disableMultiStroke) && (o.roughness !== 0)) {
const [ap2] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1.5, 0, o);
const o2 = _curve(ap2, null, o);
o1 = o1.concat(o2);
}
return {
estimatedPoints: cp1,
opset: { type: 'path', ops: o1 },
};
}
export function arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean, roughClosure: boolean, o: ResolvedOptions): OpSet {
const cx = x;
const cy = y;
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
rx += _offsetOpt(rx * 0.01, o);
ry += _offsetOpt(ry * 0.01, o);
let strt = start;
let stp = stop;
while (strt < 0) {
strt += Math.PI * 2;
stp += Math.PI * 2;
}
if ((stp - strt) > (Math.PI * 2)) {
strt = 0;
stp = Math.PI * 2;
}
const ellipseInc = (Math.PI * 2) / o.curveStepCount;
const arcInc = Math.min(ellipseInc / 2, (stp - strt) / 2);
const ops = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
if (!o.disableMultiStroke) {
const o2 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
ops.push(...o2);
}
if (closed) {
if (roughClosure) {
ops.push(
..._doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o),
..._doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o)
);
} else {
ops.push(
{ op: 'lineTo', data: [cx, cy] },
{ op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] }
);
}
}
return { type: 'path', ops };
}
export function svgPath(path: string, o: ResolvedOptions): OpSet {
const segments = normalize(absolutize(parsePath(path)));
const ops: Op[] = [];
let first: Point = [0, 0];
let current: Point = [0, 0];
for (const { key, data } of segments) {
switch (key) {
case 'M': {
current = [data[0], data[1]];
first = [data[0], data[1]];
break;
}
case 'L':
ops.push(..._doubleLine(current[0], current[1], data[0], data[1], o));
current = [data[0], data[1]];
break;
case 'C': {
const [x1, y1, x2, y2, x, y] = data;
ops.push(..._bezierTo(x1, y1, x2, y2, x, y, current, o));
current = [x, y];
break;
}
case 'Z':
ops.push(..._doubleLine(current[0], current[1], first[0], first[1], o));
current = [first[0], first[1]];
break;
}
}
return { type: 'path', ops };
}
// Fills
export function solidFillPolygon(polygonList: Point[][], o: ResolvedOptions): OpSet {
const ops: Op[] = [];
for (const points of polygonList) {
if (points.length) {
const offset = o.maxRandomnessOffset || 0;
const len = points.length;
if (len > 2) {
ops.push({ op: 'move', data: [points[0][0] + _offsetOpt(offset, o), points[0][1] + _offsetOpt(offset, o)] });
for (let i = 1; i < len; i++) {
ops.push({ op: 'lineTo', data: [points[i][0] + _offsetOpt(offset, o), points[i][1] + _offsetOpt(offset, o)] });
}
}
}
}
return { type: 'fillPath', ops };
}
export function patternFillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
return getFiller(o, helper).fillPolygons(polygonList, o);
}
export function patternFillArc(x: number, y: number, width: number, height: number, start: number, stop: number, o: ResolvedOptions): OpSet {
const cx = x;
const cy = y;
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
rx += _offsetOpt(rx * 0.01, o);
ry += _offsetOpt(ry * 0.01, o);
let strt = start;
let stp = stop;
while (strt < 0) {
strt += Math.PI * 2;
stp += Math.PI * 2;
}
if ((stp - strt) > (Math.PI * 2)) {
strt = 0;
stp = Math.PI * 2;
}
const increment = (stp - strt) / o.curveStepCount;
const points: Point[] = [];
for (let angle = strt; angle <= stp; angle = angle + increment) {
points.push([cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)]);
}
points.push([cx + rx * Math.cos(stp), cy + ry * Math.sin(stp)]);
points.push([cx, cy]);
return patternFillPolygons([points], o);
}
export function randOffset(x: number, o: ResolvedOptions): number {
return _offsetOpt(x, o);
}
export function randOffsetWithRange(min: number, max: number, o: ResolvedOptions): number {
return _offset(min, max, o);
}
export function doubleLineFillOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[] {
return _doubleLine(x1, y1, x2, y2, o, true);
}
// Private helpers
function cloneOptionsAlterSeed(ops: ResolvedOptions): ResolvedOptions {
const result: ResolvedOptions = { ...ops };
result.randomizer = undefined;
if (ops.seed) {
result.seed = ops.seed + 1;
}
return result;
}
function random(ops: ResolvedOptions): number {
if (!ops.randomizer) {
ops.randomizer = new Random(ops.seed || 0);
}
return ops.randomizer.next();
}
function _offset(min: number, max: number, ops: ResolvedOptions, roughnessGain = 1): number {
return ops.roughness * roughnessGain * ((random(ops) * (max - min)) + min);
}
function _offsetOpt(x: number, ops: ResolvedOptions, roughnessGain = 1): number {
return _offset(-x, x, ops, roughnessGain);
}
function _doubleLine(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions, filling = false): Op[] {
const singleStroke = filling ? o.disableMultiStrokeFill : o.disableMultiStroke;
const o1 = _line(x1, y1, x2, y2, o, true, false);
if (singleStroke) {
return o1;
}
const o2 = _line(x1, y1, x2, y2, o, true, true);
return o1.concat(o2);
}
function _line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions, move: boolean, overlay: boolean): Op[] {
const lengthSq = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
const length = Math.sqrt(lengthSq);
let roughnessGain = 1;
if (length < 200) {
roughnessGain = 1;
} else if (length > 500) {
roughnessGain = 0.4;
} else {
roughnessGain = (-0.0016668) * length + 1.233334;
}
let offset = o.maxRandomnessOffset || 0;
if ((offset * offset * 100) > lengthSq) {
offset = length / 10;
}
const halfOffset = offset / 2;
const divergePoint = 0.2 + random(o) * 0.2;
let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
let midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200;
midDispX = _offsetOpt(midDispX, o, roughnessGain);
midDispY = _offsetOpt(midDispY, o, roughnessGain);
const ops: Op[] = [];
const randomHalf = () => _offsetOpt(halfOffset, o, roughnessGain);
const randomFull = () => _offsetOpt(offset, o, roughnessGain);
const preserveVertices = o.preserveVertices;
if (move) {
if (overlay) {
ops.push({
op: 'move', data: [
x1 + (preserveVertices ? 0 : randomHalf()),
y1 + (preserveVertices ? 0 : randomHalf()),
],
});
} else {
ops.push({
op: 'move', data: [
x1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
y1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
],
});
}
}
if (overlay) {
ops.push({
op: 'bcurveTo',
data: [
midDispX + x1 + (x2 - x1) * divergePoint + randomHalf(),
midDispY + y1 + (y2 - y1) * divergePoint + randomHalf(),
midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomHalf(),
midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomHalf(),
x2 + (preserveVertices ? 0 : randomHalf()),
y2 + (preserveVertices ? 0 : randomHalf()),
],
});
} else {
ops.push({
op: 'bcurveTo',
data: [
midDispX + x1 + (x2 - x1) * divergePoint + randomFull(),
midDispY + y1 + (y2 - y1) * divergePoint + randomFull(),
midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomFull(),
midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomFull(),
x2 + (preserveVertices ? 0 : randomFull()),
y2 + (preserveVertices ? 0 : randomFull()),
],
});
}
return ops;
}
function _curveWithOffset(points: Point[], offset: number, o: ResolvedOptions): Op[] {
if (!points.length) {
return [];
}
const ps: Point[] = [];
ps.push([
points[0][0] + _offsetOpt(offset, o),
points[0][1] + _offsetOpt(offset, o),
]);
ps.push([
points[0][0] + _offsetOpt(offset, o),
points[0][1] + _offsetOpt(offset, o),
]);
for (let i = 1; i < points.length; i++) {
ps.push([
points[i][0] + _offsetOpt(offset, o),
points[i][1] + _offsetOpt(offset, o),
]);
if (i === (points.length - 1)) {
ps.push([
points[i][0] + _offsetOpt(offset, o),
points[i][1] + _offsetOpt(offset, o),
]);
}
}
return _curve(ps, null, o);
}
function _curve(points: Point[], closePoint: Point | null, o: ResolvedOptions): Op[] {
const len = points.length;
const ops: Op[] = [];
if (len > 3) {
const b = [];
const s = 1 - o.curveTightness;
ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
for (let i = 1; (i + 2) < len; i++) {
const cachedVertArray = points[i];
b[0] = [cachedVertArray[0], cachedVertArray[1]];
b[1] = [cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6, cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6];
b[2] = [points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6, points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6];
b[3] = [points[i + 1][0], points[i + 1][1]];
ops.push({ op: 'bcurveTo', data: [b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]] });
}
if (closePoint && closePoint.length === 2) {
const ro = o.maxRandomnessOffset;
ops.push({ op: 'lineTo', data: [closePoint[0] + _offsetOpt(ro, o), closePoint[1] + _offsetOpt(ro, o)] });
}
} else if (len === 3) {
ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
ops.push({
op: 'bcurveTo',
data: [
points[1][0], points[1][1],
points[2][0], points[2][1],
points[2][0], points[2][1],
],
});
} else if (len === 2) {
ops.push(..._line(points[0][0], points[0][1], points[1][0], points[1][1], o, true, true));
}
return ops;
}
function _computeEllipsePoints(increment: number, cx: number, cy: number, rx: number, ry: number, offset: number, overlap: number, o: ResolvedOptions): Point[][] {
const coreOnly = o.roughness === 0;
const corePoints: Point[] = [];
const allPoints: Point[] = [];
if (coreOnly) {
increment = increment / 4;
allPoints.push([
cx + rx * Math.cos(-increment),
cy + ry * Math.sin(-increment),
]);
for (let angle = 0; angle <= Math.PI * 2; angle = angle + increment) {
const p: Point = [
cx + rx * Math.cos(angle),
cy + ry * Math.sin(angle),
];
corePoints.push(p);
allPoints.push(p);
}
allPoints.push([
cx + rx * Math.cos(0),
cy + ry * Math.sin(0),
]);
allPoints.push([
cx + rx * Math.cos(increment),
cy + ry * Math.sin(increment),
]);
} else {
const radOffset = _offsetOpt(0.5, o) - (Math.PI / 2);
allPoints.push([
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
]);
const endAngle = Math.PI * 2 + radOffset - 0.01;
for (let angle = radOffset; angle < endAngle; angle = angle + increment) {
const p: Point = [
_offsetOpt(offset, o) + cx + rx * Math.cos(angle),
_offsetOpt(offset, o) + cy + ry * Math.sin(angle),
];
corePoints.push(p);
allPoints.push(p);
}
allPoints.push([
_offsetOpt(offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
_offsetOpt(offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5),
]);
allPoints.push([
_offsetOpt(offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
_offsetOpt(offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap),
]);
allPoints.push([
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5),
]);
}
return [allPoints, corePoints];
}
function _arc(increment: number, cx: number, cy: number, rx: number, ry: number, strt: number, stp: number, offset: number, o: ResolvedOptions) {
const radOffset = strt + _offsetOpt(0.1, o);
const points: Point[] = [];
points.push([
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
]);
for (let angle = radOffset; angle <= stp; angle = angle + increment) {
points.push([
_offsetOpt(offset, o) + cx + rx * Math.cos(angle),
_offsetOpt(offset, o) + cy + ry * Math.sin(angle),
]);
}
points.push([
cx + rx * Math.cos(stp),
cy + ry * Math.sin(stp),
]);
points.push([
cx + rx * Math.cos(stp),
cy + ry * Math.sin(stp),
]);
return _curve(points, null, o);
}
function _bezierTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number, current: Point, o: ResolvedOptions): Op[] {
const ops: Op[] = [];
const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.3];
let f: Point = [0, 0];
const iterations = o.disableMultiStroke ? 1 : 2;
const preserveVertices = o.preserveVertices;
for (let i = 0; i < iterations; i++) {
if (i === 0) {
ops.push({ op: 'move', data: [current[0], current[1]] });
} else {
ops.push({ op: 'move', data: [current[0] + (preserveVertices ? 0 : _offsetOpt(ros[0], o)), current[1] + (preserveVertices ? 0 : _offsetOpt(ros[0], o))] });
}
f = preserveVertices ? [x, y] : [x + _offsetOpt(ros[i], o), y + _offsetOpt(ros[i], o)];
ops.push({
op: 'bcurveTo',
data: [
x1 + _offsetOpt(ros[i], o), y1 + _offsetOpt(ros[i], o),
x2 + _offsetOpt(ros[i], o), y2 + _offsetOpt(ros[i], o),
f[0], f[1],
],
});
}
return ops;
}
================================================
FILE: src/rough.ts
================================================
import { Config } from './core';
import { RoughCanvas } from './canvas';
import { RoughGenerator } from './generator';
import { RoughSVG } from './svg';
export default {
canvas(canvas: HTMLCanvasElement, config?: Config): RoughCanvas {
return new RoughCanvas(canvas, config);
},
svg(svg: SVGSVGElement, config?: Config): RoughSVG {
return new RoughSVG(svg, config);
},
generator(config?: Config): RoughGenerator {
return new RoughGenerator(config);
},
newSeed(): number {
return RoughGenerator.newSeed();
},
};
================================================
FILE: src/svg.ts
================================================
import { Config, Options, OpSet, ResolvedOptions, Drawable, SVGNS } from './core';
import { RoughGenerator } from './generator';
import { Point } from './geometry';
export class RoughSVG {
private gen: RoughGenerator;
private svg: SVGSVGElement;
constructor(svg: SVGSVGElement, config?: Config) {
this.svg = svg;
this.gen = new RoughGenerator(config);
}
draw(drawable: Drawable): SVGGElement {
const sets = drawable.sets || [];
const o = drawable.options || this.getDefaultOptions();
const doc = this.svg.ownerDocument || window.document;
const g = doc.createElementNS(SVGNS, 'g');
const precision = drawable.options.fixedDecimalPlaceDigits;
for (const drawing of sets) {
let path = null;
switch (drawing.type) {
case 'path': {
path = doc.createElementNS(SVGNS, 'path');
path.setAttribute('d', this.opsToPath(drawing, precision));
path.setAttribute('stroke', o.stroke);
path.setAttribute('stroke-width', o.strokeWidth + '');
path.setAttribute('fill', 'none');
if (o.strokeLineDash) {
path.setAttribute('stroke-dasharray', o.strokeLineDash.join(' ').trim());
}
if (o.strokeLineDashOffset) {
path.setAttribute('stroke-dashoffset', `${o.strokeLineDashOffset}`);
}
break;
}
case 'fillPath': {
path = doc.createElementNS(SVGNS, 'path');
path.setAttribute('d', this.opsToPath(drawing, precision));
path.setAttribute('stroke', 'none');
path.setAttribute('stroke-width', '0');
path.setAttribute('fill', o.fill || '');
if (drawable.shape === 'curve' || drawable.shape === 'polygon') {
path.setAttribute('fill-rule', 'evenodd');
}
break;
}
case 'fillSketch': {
path = this.fillSketch(doc, drawing, o);
break;
}
}
if (path) {
g.appendChild(path);
}
}
return g;
}
private fillSketch(doc: Document, drawing: OpSet, o: ResolvedOptions): SVGPathElement {
let fweight = o.fillWeight;
if (fweight < 0) {
fweight = o.strokeWidth / 2;
}
const path = doc.createElementNS(SVGNS, 'path');
path.setAttribute('d', this.opsToPath(drawing, o.fixedDecimalPlaceDigits));
path.setAttribute('stroke', o.fill || '');
path.setAttribute('stroke-width', fweight + '');
path.setAttribute('fill', 'none');
if (o.fillLineDash) {
path.setAttribute('stroke-dasharray', o.fillLineDash.join(' ').trim());
}
if (o.fillLineDashOffset) {
path.setAttribute('stroke-dashoffset', `${o.fillLineDashOffset}`);
}
return path;
}
get generator(): RoughGenerator {
return this.gen;
}
getDefaultOptions(): ResolvedOptions {
return this.gen.defaultOptions;
}
opsToPath(drawing: OpSet, fixedDecimalPlaceDigits?: number): string {
return this.gen.opsToPath(drawing, fixedDecimalPlaceDigits);
}
line(x1: number, y1: number, x2: number, y2: number, options?: Options): SVGGElement {
const d = this.gen.line(x1, y1, x2, y2, options);
return this.draw(d);
}
rectangle(x: number, y: number, width: number, height: number, options?: Options): SVGGElement {
const d = this.gen.rectangle(x, y, width, height, options);
return this.draw(d);
}
ellipse(x: number, y: number, width: number, height: number, options?: Options): SVGGElement {
const d = this.gen.ellipse(x, y, width, height, options);
return this.draw(d);
}
circle(x: number, y: number, diameter: number, options?: Options): SVGGElement {
const d = this.gen.circle(x, y, diameter, options);
return this.draw(d);
}
linearPath(points: Point[], options?: Options): SVGGElement {
const d = this.gen.linearPath(points, options);
return this.draw(d);
}
polygon(points: Point[], options?: Options): SVGGElement {
const d = this.gen.polygon(points, options);
return this.draw(d);
}
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean = false, options?: Options): SVGGElement {
const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
return this.draw(d);
}
curve(points: Point[] | Point[][], options?: Options): SVGGElement {
const d = this.gen.curve(points, options);
return this.draw(d);
}
path(d: string, options?: Options): SVGGElement {
const drawing = this.gen.path(d, options);
return this.draw(drawing);
}
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "es2017",
"module": "es2015",
"moduleResolution": "node",
"lib": [
"es2017",
"dom"
],
"declaration": true,
"outDir": "./bin",
"baseUrl": ".",
"strict": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src/**/*.ts"
]
}
================================================
FILE: visual-tests/canvas/arc.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Arc</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.arc(350, 200, 200, 180, Math.PI, Math.PI * 1.6);
rc.arc(350, 300, 200, 180, Math.PI, Math.PI * 1.6, true);
rc.arc(350, 300, 200, 180, 0, Math.PI / 2, true, {
stroke: 'red', strokeWidth: 4,
fill: 'rgba(255,255,0,0.4)', fillStyle: 'solid'
});
rc.arc(350, 300, 200, 180, Math.PI / 2, Math.PI, true, {
stroke: 'blue', strokeWidth: 2,
fill: 'rgba(255,0,255,0.4)'
});
canvas.getContext('2d').translate(-210, 0);
rc.arc(350, 300, 200, 180, Math.PI, Math.PI * 1.6, true, {
fill: 'red', fillStyle: 'zigzag',
hachureGap: 10
});
rc.arc(350, 300, 200, 180, 0, Math.PI / 2, true, {
stroke: 'red', strokeWidth: 4,
fill: 'orange', fillStyle: 'dots'
});
rc.arc(350, 300, 200, 180, Math.PI / 2, Math.PI, true, {
stroke: 'blue', strokeWidth: 2,
fill: 'red', fillStyle: 'cross-hatch'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/arc2.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Arc</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.arc(350, 300, 200, 180, 0, Math.PI * 1.6, true, {
stroke: "#1a1f26",
strokeWidth: 1,
roughness: 1,
fill: 'rgba(186, 186, 188, 0.5)',
fillStyle: "solid"
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/curve-seed.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const seed = 232; // 1748179884
const roughness = 1.5;
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { roughness, seed });
canvas.getContext('2d').translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100]
], { roughness, seed });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/curve.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureAngle: 90 });
canvas.getContext('2d').translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid' });
canvas.getContext('2d').translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'dots', hachureGap: 16, fillWeight: 2 });
canvas.getContext('2d').translate(300, 0);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'cross-hatch', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/curve2.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const ctx = canvas.getContext('2d');
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureGap: 10 });
ctx.translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid', roughness: 3 });
ctx.translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'cross-hatch', hachureGap: 8 });
ctx.translate(300, 0);
ctx.translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid', stroke: 'none', roughness: 3 });
ctx.translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureGap: 10, stroke: 'none' });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/curve3.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const ctx = canvas.getContext('2d');
const ops = { fill: 'red', fillStyle: 'solid', roughness: 1 };
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], ops);
ctx.translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], ops);
ctx.translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], ops);
ctx.translate(300, 0);
ctx.translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], ops);
ctx.translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], ops);
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/curve4.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const ctx = canvas.getContext('2d');
const ops = { roughness: 1, fill: 'red', };
// preserveVertices
ctx.translate(20, 20);
// rc.curve([
// [
// [0, 0],
// [107.03890991210938, 324.7185974121094],
// [297.3592224121094, 186.47903442382812],
// [356.3987731933594, -5.518890380859375],
// [490.5584411621094, 216.71908569335938],
// [645.1175231933594, 47.040374755859375],
// ]
// ], ops);
rc.curve([
[
[0, 0],
[107.03890991210938, 324.7185974121094],
[297.3592224121094, 186.47903442382812],
[356.3987731933594, -5.518890380859375],
[490.5584411621094, 216.71908569335938],
// [645.1175231933594, 47.040374755859375]
],
[
[490.5584411621094, 216.71908569335938],
[645.1175231933594, 47.040374755859375]
],
], ops);
ctx.translate(0, 210);
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/arc.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Arc</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
stroke: 'red',
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.arc(350, 200, 200, 180, Math.PI, Math.PI * 1.6);
rc.arc(350, 300, 200, 180, Math.PI, Math.PI * 1.6, true);
rc.arc(350, 300, 200, 180, 0, Math.PI / 2, true, {
stroke: 'red', strokeWidth: 4,
fill: 'rgba(255,255,0,0.4)', fillStyle: 'solid'
});
rc.arc(350, 300, 200, 180, Math.PI / 2, Math.PI, true, {
stroke: 'blue', strokeWidth: 2,
fill: 'rgba(255,0,255,0.4)',
fillLineDash: [15, 5], fillLineDashOffset: 10
});
canvas.getContext('2d').translate(-210, 0);
rc.arc(350, 300, 200, 180, Math.PI, Math.PI * 1.6, true, {
fill: 'red', fillStyle: 'zigzag',
hachureGap: 10
});
rc.arc(350, 300, 200, 180, 0, Math.PI / 2, true, {
stroke: 'red', strokeWidth: 4,
fill: 'orange', fillStyle: 'dots'
});
rc.arc(350, 300, 200, 180, Math.PI / 2, Math.PI, true, {
stroke: 'blue', strokeWidth: 2,
fill: 'red', fillStyle: 'cross-hatch'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/curve.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
stroke: 'red',
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureAngle: 90, fillLineDash: [15, 5], fillLineDashOffset: 10 });
canvas.getContext('2d').translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid' });
canvas.getContext('2d').translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'dots', hachureGap: 16, fillWeight: 2 });
canvas.getContext('2d').translate(300, 0);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'cross-hatch', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/ellipse.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Ellipse</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
stroke: 'red',
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.ellipse(10 + 40, 10 + 40, 80, 80);
rc.ellipse(110 + 40, 10 + 40, 80, 80, { fill: 'red' });
rc.ellipse(210 + 40, 10 + 40, 80, 80, { fill: 'pink', fillStyle: 'solid' });
rc.ellipse(310 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'cross-hatch' });
rc.ellipse(410 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
rc.ellipse(510 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'dots' });
rc.circle(10 + 40, 110 + 40, 80, { roughness: 2 });
rc.circle(110 + 40, 110 + 40, 80, { fill: 'red', stroke: 'blue', hachureAngle: 0, strokeWidth: 3 });
rc.circle(210 + 40, 110 + 40, 80, { fill: 'pink', fillWeight: 3, hachureGap: 8, hachureAngle: 45 });
rc.ellipse(300, 350, 480, 280, { fill: 'red', fillStyle: 'dots', hachureGap: 20, hachureAngle: 0, fillWeight: 2 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/line.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Line</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.line(10, 10, 100, 10);
rc.line(10, 210, 500, 210);
rc.line(10, 20, 10, 110, { stroke: 'red' });
rc.line(10, 10, 100, 10);
rc.line(50, 30, 200, 100, { stroke: 'blue', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/linearpath.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS linear path</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
stroke: 'red',
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.linearPath([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 4 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/path-with-transform.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
const ctx = rc.ctx;
ctx.translate(150, 150);
ctx.scale(1, -1);
rc.path('M-100, 0L0 100L100 100Z', { fill: 'red' });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/path.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
simplification: undefined,
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.path('M400 100 h 90 v 90 h -90z', {
stroke: 'red',
strokeWidth: '3',
fill: 'rgba(0,0,255,0.2)',
fillStyle: 'solid'
});
rc.path('M400 250 h 90 v 90 h -90z', {
fill: 'rgba(0,0,255,0.6)'
});
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
stroke: 'red',
strokeWidth: '1',
fill: 'blue'
});
rc.path('M80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 Z', { fill: 'green' });
rc.path('M230 80 A 45 45, 0, 1, 0, 275 125 L 275 80 Z', { fill: 'purple', hachureAngle: 60, hachureGap: 5, fillLineDash: [15, 5], fillLineDashOffset: 10 });
rc.path('M80 230 A 45 45, 0, 0, 1, 125 275 L 125 230 Z', { fill: 'red' });
rc.path('M230 230 A 45 45, 0, 1, 1, 275 275 L 275 230 Z', { fill: 'blue', });
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'dots'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'cross-hatch'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'solid'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'zigzag'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/polygon.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Polygon</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureAngle: 90 });
canvas.getContext('2d').translate(0, 210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid' });
canvas.getContext('2d').translate(0, 210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'dots', hachureGap: 16, fillWeight: 2 });
canvas.getContext('2d').translate(300, 0);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'cross-hatch', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/dashed/rectangle.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Rectangle</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
strokeLineDash: [15, 5],
strokeLineDashOffset: 10
}
});
rc.rectangle(10, 10, 80, 80);
rc.rectangle(110, 10, 80, 80, { fill: 'red' });
rc.rectangle(210, 10, 80, 80, { fill: 'pink', fillStyle: 'solid' });
rc.rectangle(310, 10, 80, 80, { fill: 'red', fillStyle: 'cross-hatch' });
rc.rectangle(410, 10, 80, 80, { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
rc.rectangle(510, 10, 80, 80, { fill: 'red', fillStyle: 'dots' });
rc.rectangle(10, 110, 80, 80, { roughness: 2 });
rc.rectangle(110, 110, 80, 80, { fill: 'red', stroke: 'blue', hachureAngle: 0, strokeWidth: 3, fillLineDash: [15, 5], fillLineDashOffset: 10 });
rc.rectangle(210, 110, 80, 80, { fill: 'pink', fillWeight: 5, hachureGap: 10, hachureAngle: 90, fillLineDash: [15, 5], fillLineDashOffset: 10 });
rc.rectangle(10, 210, 480, 280, { fill: 'red', fillStyle: 'dots', hachureGap: 20, fillWeight: 2 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/ellipse.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Ellipse</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.ellipse(10 + 40, 10 + 40, 80, 80);
rc.ellipse(110 + 40, 10 + 40, 80, 80, { fill: 'red' });
rc.ellipse(210 + 40, 10 + 40, 80, 80, { fill: 'pink', fillStyle: 'solid' });
rc.ellipse(310 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'cross-hatch' });
rc.ellipse(410 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
rc.ellipse(510 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'dots' });
rc.circle(10 + 40, 110 + 40, 80, { roughness: 2 });
rc.circle(110 + 40, 110 + 40, 80, { fill: 'red', stroke: 'blue', hachureAngle: 0, strokeWidth: 3 });
rc.circle(210 + 40, 110 + 40, 80, { fill: 'pink', fillWeight: 3, hachureGap: 8, hachureAngle: 45 });
rc.ellipse(300, 350, 480, 280, { fill: 'red', fillStyle: 'dots', hachureGap: 20, hachureAngle: 0, fillWeight: 2 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/ellipse2.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Ellipse</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.ellipse(300, 350, 380, 280, { roughness: 2 });
rc.ellipse(200, 150, 380, 280, { roughness: 1 });
rc.ellipse(400, 150, 380, 280, { roughness: 0, fill: 'red', hachureGap: 10 });
rc.ellipse(400, 150, 100.65800865800863, 17.70129870129859, { roughness: 0 });
rc.ellipse(200, 150, 100, 17, { roughness: 0, fill: 'red' });
rc.ellipse(200, 50, 100, 17, { roughness: 0, fill: 'pink', fillStyle: 'solid' });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/ellipse3.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Ellipse</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const ops = { fill: 'red', fillStyle: 'solid', roughness: 2, stroke: 'none' };
rc.ellipse(10 + 40, 10 + 40, 80, 80, ops);
rc.ellipse(110 + 40, 10 + 40, 80, 80, ops);
rc.ellipse(210 + 40, 10 + 40, 80, 80, ops);
rc.ellipse(310 + 40, 10 + 40, 80, 80, ops);
rc.ellipse(410 + 40, 10 + 40, 80, 80, ops);
rc.ellipse(510 + 40, 10 + 40, 80, 80, ops);
rc.circle(10 + 40, 110 + 40, 80, ops);
rc.circle(110 + 40, 110 + 40, 80, ops);
rc.circle(210 + 40, 110 + 40, 80, ops);
rc.ellipse(300, 350, 480, 280, ops);
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/line.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Line</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.line(10, 10, 100, 10);
rc.line(10, 210, 500, 210);
rc.line(10, 20, 10, 110, { stroke: 'red' });
rc.line(10, 10, 100, 10);
rc.line(50, 30, 200, 100, { stroke: 'blue', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/linearpath.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS linear path</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.linearPath([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 4 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/map.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Map</title>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
</head>
<body>
<canvas width="960" height="500"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
simplification: 0.1,
roughness: 0.65
}
});
const width = 960, height = 500;
const projection = d3.geo.albersUsa().scale(1070).translate([width / 2, height / 2]);
const path = d3.geo.path().projection(projection);
const randomColor = () => `rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)})`;
const randomAngle = () => (Math.random() > 0.5 ? -1 : 1) * (1 + Math.random() * 88);
const randomStyle = () => (Math.random() > 0.5 ? 'solid' : 'hachure');
d3.json("./us.json", (error, us) => {
if (error) throw error;
let topo = topojson.feature(us, us.objects.states).features;
for (let feature of topo) {
// if (feature.id !== 48) {
// continue;
// }
rc.path(path(feature), {
fill: randomColor(),
fillStyle: randomStyle(),
hachureAngle: randomAngle()
});
}
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path-with-transform.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const ctx = rc.ctx;
ctx.translate(150, 150);
ctx.scale(1, -1);
rc.path('M-100, 0L0 100L100 100Z', { fill: 'red' });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
simplification: undefined
}
});
rc.path('M400 100 h 90 v 90 h -90z', {
stroke: 'red',
strokeWidth: '3',
fill: 'rgba(0,0,255,0.2)',
fillStyle: 'solid'
});
rc.path('M400 250 h 90 v 90 h -90z', {
fill: 'rgba(0,0,255,0.6)'
});
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
stroke: 'red',
strokeWidth: '1',
fill: 'blue'
});
rc.path('M80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 Z', { fill: 'green' });
rc.path('M230 80 A 45 45, 0, 1, 0, 275 125 L 275 80 Z', { fill: 'purple', hachureAngle: 60, hachureGap: 5 });
rc.path('M80 230 A 45 45, 0, 0, 1, 125 275 L 125 230 Z', { fill: 'red' });
rc.path('M230 230 A 45 45, 0, 1, 1, 275 275 L 275 230 Z', { fill: 'blue' });
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'dots'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'cross-hatch'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'solid'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'zigzag'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path2.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
simplification: undefined,
simplification: 0.1,
fillStyle: 'zigzag'
}
});
rc.path('M4.5000 150.1500L4.5000 150.1500Q4.5000 144.7500 6 138.1500Q7.5000 131.5500 10.8000 123.6000L10.8000 123.6000L21.6000 127.8000Q19.2000 134.1000 18 139.3500Q16.8000 144.6000 16.8000 149.1000L16.8000 149.1000Q16.8000 156.7500 21.1500 162Q25.5000 167.2500 32.9250 169.8750Q40.3500 172.5000 49.6500 172.5000L49.6500 172.5000Q60.4500 172.5000 68.5500 170.1750Q76.6500 167.8500 82.0500 164.2500Q87.4500 160.6500 90.0750 156.7500Q92.7000 152.8500 92.7000 149.8500L92.7000 149.8500Q92.7000 147.3000 89.1000 145.4250Q85.5000 143.5500 80.2500 141.6750Q75 139.8000 69.7500 137.3250Q64.5000 134.8500 60.9000 131.1750Q57.3000 127.5000 57.3000 121.8000L57.3000 121.8000Q57.3000 115.8000 60.3750 109.6500Q63.4500 103.5000 68.7750 98.4000Q74.1000 93.3000 80.7000 90.2250Q87.3000 87.1500 94.3500 87.1500L94.3500 87.1500Q100.6500 87.1500 105.0750 88.8750Q109.5000 90.6000 111.6000 92.1000L111.6000 92.1000L106.6500 102.7500Q104.1000 101.5500 100.9500 100.5000Q97.8000 99.4500 94.3500 99.4500L94.3500 99.4500Q90 99.4500 85.5750 101.2500Q81.1500 103.0500 77.4750 106.0500Q73.8000 109.0500 71.5500 112.6500Q69.3000 116.2500 69.3000 119.8500L69.3000 119.8500Q69.3000 122.4000 71.9250 124.5000Q74.5500 126.6000 78.7500 128.4000Q82.9500 130.2000 87.6750 132.0750Q92.4000 133.9500 96.6000 136.1250Q100.8000 138.3000 103.4250 141.1500Q106.0500 144 106.0500 147.9000L106.0500 147.9000Q106.0500 153.6000 102.3000 159.9750Q98.5500 166.3500 91.2000 171.9750Q83.8500 177.6000 73.2750 181.2000Q62.7000 184.8000 48.9000 184.8000L48.9000 184.8000Q36.3000 184.8000 26.2500 181.0500Q16.2000 177.3000 10.3500 169.6500Q4.5000 162 4.5000 150.1500Z', {
fill: 'red',
});
canvas.getContext('2d').translate(300, 0);
rc.path('M0.6000 184.5000L0.6000 184.5000L-3.7500 173.1000Q11.4000 168.3000 19.5750 161.7000Q27.7500 155.1000 30.8250 147.8250Q33.9000 140.5500 33.9000 133.9500L33.9000 133.9500Q33.9000 129.7500 33 125.8500Q32.1000 121.9500 29.8500 117Q27.6000 112.0500 23.5500 104.7000L23.5500 104.7000L34.5000 99.1500Q40.2000 108.9000 42.6750 117.6000Q45.1500 126.3000 45.1500 132.9000L45.1500 132.9000Q45.1500 142.3500 42.0750 150Q39 157.6500 33.9750 163.5750Q28.9500 169.5000 23.0250 173.7000Q17.1000 177.9000 11.1750 180.6000Q5.2500 183.3000 0.6000 184.5000Z', {
fill: 'blue'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path3.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const roughness = 2;
const shape = rc.path('M 37.1484375 0 L 112.11328125 0 Q 149.26171875 0, 149.26171875 37.1484375 L 149.26171875 111.4453125 Q 149.26171875 148.59375, 112.11328125 148.59375 L 37.1484375 148.59375 Q 0 148.59375, 0 111.4453125 L 0 37.1484375 Q 0 0, 37.1484375 0', {
disableMultiStroke: false,
fill: undefined,
fillStyle: "hachure",
fillWeight: 0.5,
hachureGap: 4,
roughness: roughness,
seed: 2142156371,
stroke: "#000000",
strokeLineDash: undefined,
strokeWidth: 1
});
console.log(shape);
const ctx = rc.ctx;
ctx.translate(250, 0);
rc.path('M 37.1484375 0 L 112.11328125 0 Q 149.26171875 0, 149.26171875 37.1484375 L 149.26171875 111.4453125 Q 149.26171875 148.59375, 112.11328125 148.59375 L 37.1484375 148.59375 Q 0 148.59375, 0 111.4453125 L 0 37.1484375 Q 0 0, 37.1484375 0', {
disableMultiStroke: false,
fill: undefined,
fillStyle: "hachure",
fillWeight: 0.5,
hachureGap: 4,
roughness: roughness,
seed: 2142156371,
stroke: "#000000",
strokeLineDash: undefined,
strokeWidth: 1,
preserveVertices: true
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path4.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="1000" height="1000"></canvas>
<br>
<input type="range" style="width: 100%; display: block; box-sizing: border-box;" min="0" max="800">
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const ctx = rc.ctx;
const input = document.querySelector('input');
const getRings = () => {
const xPos = +input.value;
return [
[
[100, 300],
[800, 300],
[800, 0],
[100, 0]
],
[
[200, 250],
[200, 50],
[600, 50],
[600, 250]
],
[
[400, 200],
[550, 200],
[550, 100],
[400, 100]
],
[
[xPos + 25, 175],
[xPos + 25, 125],
[xPos + 75, 125],
[xPos + 75, 175]
]
]
};
const draw = () => {
ctx.clearRect(0, 0, 1000, 1000);
const path = getRings().map((d) => `M${d.join("L")}Z`).join(' ') + ' M 650, 150 a 50,50 0 1,0 100,0 a 50,50 0 1,0 -100,0';
rc.path(path, {
seed: 2142156371,
fill: 'orange',
fillWeight: 2
});
}
draw();
input.addEventListener('input', () => draw());
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path5.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Path</title>
</head>
<body>
<demo-canvas></demo-canvas>
<script type="module">
import rough from '../../bin/rough.js';
const samples = [
'M400 100 h 90 v 90 h -90z',
'M230 230 A 45 45, 0, 1, 1, 275 275 L 275 230 Z',
'M50 0 L 21 90 L 98 35 L 2 35 L 79 90 Z',
'M210, 100 h190 v190 h -190 z M250, 120 v50 h80 v -50 z',
'M100,300L800,300L800,0L100,0ZM200,250L200,50L600,50L600,250ZM400,200L550,200L550,100L400,100ZM425,175L425,125L475,125L475,175Z',
'M100,300L800,300L800,0L100,0ZM200,250L200,50L600,50L600,250ZM400,200L550,200L550,100L400,100ZM267,175L267,125L317,125L317,175Z',
'M100,300L800,300L800,0L100,0ZM200,250L200,50L600,50L600,250ZM400,200L550,200L550,100L400,100ZM175,175L175,125L225,125L225,175Z',
'M100,300L800,300L800,0L100,0ZM200,250L200,50L600,50L600,250ZM400,200L550,200L550,100L400,100ZM123,175L123,125L173,125L173,175Z',
'M100,300L800,300L800,0L100,0ZM200,250L200,50L600,50L600,250ZM400,200L550,200L550,100L400,100ZM70,175L70,125L120,125L120,175Z',
'M100,300L800,300L800,0L100,0ZM200,250L200,50L600,50L600,250ZM400,200L550,200L550,100L400,100ZM25,175L25,125L75,125L75,175Z',
'M100,300L800,300L800,0L100,0ZM200,250L200,50L600,50L600,250ZM400,200L550,200L550,100L400,100ZM825,175L825,125L875,125L875,175Z',
'M0,0C171.49986081160606,0,342.9997216232121,0,461,0C461,74.77984827272596,461,149.5596965454519,461,279C285.96970007382333,279,110.93940014764667,279,0,279C0,207.8501017022878,0,136.70020340457557,0,0M52,105.5C52,105.5,52,105.5,52,105.5C52,138.463,78.314,165,111,165C143.686,165,170,138.463,170,105.5C170,72.537,143.686,46,111,46C78.314,46,52,72.537,52,105.5M261,74C261,74,261,74,261,74C261,90.066,273.265,103,288.5,103C303.735,103,316,90.066,316,74C316,57.934,303.735,45,288.5,45C273.265,45,261,57.934,261,74'
];
class DemoCanvas extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({ mode: 'open' });
this.root.innerHTML = `
<style>
:host {
display: block;
position: relative;
}
canvas {
display: block;
box-sizing: border-box;
border: 1px solid #000;
}
#panel {
padding: 16px 0;
}
input {
width: 500px;
padding: 10px;
font-family: monospace;
font-size: 12px;
}
</style>
<div id="panel">
<input>
<button id="submit">submit</button>
<br>
${samples.map((d, i) => `<button class="sampleButton" data-path="${d}">${i + 1}</button>`)}
</div>
<canvas id="c1" width="1000" height="1000"></canvas>
`;
}
connectedCallback() {
const input = this.root.querySelector('input');
const draw = () => {
this.draw(input.value.trim());
};
this.root.querySelector('#submit').addEventListener('click', draw);
this.root.querySelectorAll('.sampleButton').forEach((btn) => {
btn.addEventListener('click', () => {
input.value = btn.dataset.path || '';
draw();
});
});
}
draw(path) {
const canvas = this.root.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const rc = rough.canvas(canvas);
rc.path(path, {
fill: 'orange',
fillWeight: 5,
hachureAngle: -45,
hachureGap: 20,
roughness: 0.1,
fillStyle: 'zigzag'
});
}
}
customElements.define('demo-canvas', DemoCanvas);
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path6.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const roughness = 2;
const path = `M 32 0 L 153.96380615234375 0 Q 185.96380615234375 0, 185.96380615234375 32 L 185.96380615234375 157.74319458007812 Q 185.96380615234375 189.74319458007812, 153.96380615234375 189.74319458007812 L 32 189.74319458007812 Q 0 189.74319458007812, 0 157.74319458007812 L 0 32 Q 0 0, 32 0`;
const ctx = rc.ctx;
ctx.translate(0, 50);
let shape = rc.path(path, {
roughness: roughness,
seed: 2142156371,
preserveVertices: true
});
ctx.translate(200, 0);
shape = rc.path(path, {
roughness: roughness,
seed: 2142156371,
preserveVertices: true,
fill: 'red',
hachureGap: 15
});
ctx.translate(200, 0);
shape = rc.path(path, {
roughness: roughness,
seed: 2142156371,
preserveVertices: true,
fill: 'red',
fillStyle: 'solid',
});
ctx.translate(200, 0);
shape = rc.path(path, {
roughness: roughness,
seed: 2142156371,
preserveVertices: true,
fill: 'red',
fillStyle: 'solid',
stroke: 'none'
});
ctx.resetTransform();
ctx.translate(0, 250);
rc.ellipse(110, 150, 180, 180, {
roughness: roughness,
seed: 2142156371,
});
ctx.translate(200, 0);
rc.ellipse(110, 150, 180, 180, {
roughness: roughness,
seed: 2142156371,
fill: 'red',
hachureGap: 15
});
ctx.translate(200, 0);
rc.ellipse(110, 150, 180, 180, {
roughness: roughness,
seed: 2142156371,
fill: 'red',
fillStyle: 'solid'
});
ctx.translate(200, 0);
rc.ellipse(110, 150, 180, 180, {
roughness: roughness,
seed: 2142156371,
fill: 'red',
fillStyle: 'solid',
stroke: 'none'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/path7.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const roughness = 1;
const path = `M 32 0 L 153.96380615234375 0 Q 185.96380615234375 0, 185.96380615234375 32 L 185.96380615234375 157.74319458007812 Q 185.96380615234375 189.74319458007812, 153.96380615234375 189.74319458007812 L 32 189.74319458007812 Q 0 189.74319458007812, 0 157.74319458007812 L 0 32 Q 0 0, 32 0`;
const ops = {
preserveVertices: true,
fill: 'red',
fillStyle: 'hachure',
hachureGap: 6,
roughness: roughness
}
const ctx = rc.ctx;
ctx.translate(0, 50);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.resetTransform();
ctx.translate(0, 250);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.resetTransform();
ctx.translate(0, 450);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
ctx.translate(200, 0);
rc.path(path, ops);
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/poly-seed.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
const seed = 232; // 1748179884
const roughness = 1.5;
rc.linearPath([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { roughness, seed });
canvas.getContext('2d').translate(0, 210);
rc.linearPath([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100]
], { roughness, seed });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/polygon.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Polygon</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureAngle: 90 });
canvas.getContext('2d').translate(0, 210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid' });
canvas.getContext('2d').translate(0, 210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'dots', hachureGap: 16, fillWeight: 2 });
canvas.getContext('2d').translate(300, 0);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'cross-hatch', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/polygon2.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Polygon</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
rc.polygon([
[10, 300],
[150, 200],
[310, 300],
[200, 50],
[100, 50]
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 20, hachureAngle: 85 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/rectangle.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Rectangle</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas);
// const ctx = canvas.getContext('2d');
// ctx.scale(20, 20);
// rc.rectangle(5, 5, 5, 5, {
// fill: 'red', roughness: 0.1,
// strokeWidth: 0.1,
// fillWeight: 0.05,
// hachureGap: 0.5,
// fillStyle: 'dots',
// curveFitting: 1,
// curveTightness: 0.7
// });
rc.rectangle(10, 10, 80, 80);
rc.rectangle(110, 10, 80, 80, { fill: 'red' });
rc.rectangle(210, 10, 80, 80, { fill: 'pink', fillStyle: 'solid' });
rc.rectangle(310, 10, 80, 80, { fill: 'red', fillStyle: 'cross-hatch' });
rc.rectangle(410, 10, 80, 80, { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
rc.rectangle(510, 10, 80, 80, { fill: 'red', fillStyle: 'dots' });
rc.rectangle(10, 110, 80, 80, { roughness: 0 });
rc.rectangle(110, 110, 80, 80, { fill: 'red', stroke: 'blue', hachureAngle: 0, strokeWidth: 3 });
rc.rectangle(210, 110, 80, 80, { fill: 'pink', fillWeight: 5, hachureGap: 10, hachureAngle: 90 });
rc.rectangle(10, 210, 480, 280, { fill: 'red', fillStyle: 'dots', hachureGap: 20, fillWeight: 2 });
rc.rectangle(10, 210, 480, 280, { fill: 'orange', fillStyle: 'zigzag', hachureGap: 20, fillWeight: 2 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/singlestroke/arc.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Arc</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
stroke: 'red',
disableMultiStroke: true,
disableMultiStrokeFill: true
}
});
rc.arc(350, 200, 200, 180, Math.PI, Math.PI * 1.6);
rc.arc(350, 300, 200, 180, Math.PI, Math.PI * 1.6, true);
rc.arc(350, 300, 200, 180, 0, Math.PI / 2, true, {
stroke: 'red', strokeWidth: 4,
fill: 'rgba(255,255,0,0.4)', fillStyle: 'solid'
});
rc.arc(350, 300, 200, 180, Math.PI / 2, Math.PI, true, {
stroke: 'blue', strokeWidth: 2,
fill: 'rgba(255,0,255,0.4)',
fillLineDash: [15, 5], fillLineDashOffset: 10
});
canvas.getContext('2d').translate(-210, 0);
rc.arc(350, 300, 200, 180, Math.PI, Math.PI * 1.6, true, {
fill: 'red', fillStyle: 'zigzag',
hachureGap: 10
});
rc.arc(350, 300, 200, 180, 0, Math.PI / 2, true, {
stroke: 'red', strokeWidth: 4,
fill: 'orange', fillStyle: 'dots'
});
rc.arc(350, 300, 200, 180, Math.PI / 2, Math.PI, true, {
stroke: 'blue', strokeWidth: 2,
fill: 'red', fillStyle: 'cross-hatch'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/singlestroke/curve.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
stroke: 'red',
disableMultiStroke: true,
disableMultiStrokeFill: true
}
});
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureAngle: 90, fillLineDash: [15, 5], fillLineDashOffset: 10 });
canvas.getContext('2d').translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid' });
canvas.getContext('2d').translate(0, 210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'dots', hachureGap: 16, fillWeight: 2 });
canvas.getContext('2d').translate(300, 0);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'cross-hatch', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.curve([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/singlestroke/ellipse.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Ellipse</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
stroke: 'red',
disableMultiStroke: true,
disableMultiStrokeFill: true
}
});
rc.ellipse(10 + 40, 10 + 40, 80, 80);
rc.ellipse(110 + 40, 10 + 40, 80, 80, { fill: 'red' });
rc.ellipse(210 + 40, 10 + 40, 80, 80, { fill: 'pink', fillStyle: 'solid' });
rc.ellipse(310 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'cross-hatch' });
rc.ellipse(410 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
rc.ellipse(510 + 40, 10 + 40, 80, 80, { fill: 'red', fillStyle: 'dots' });
rc.circle(10 + 40, 110 + 40, 80, { roughness: 2 });
rc.circle(110 + 40, 110 + 40, 80, { fill: 'red', stroke: 'blue', hachureAngle: 0, strokeWidth: 3 });
rc.circle(210 + 40, 110 + 40, 80, { fill: 'pink', fillWeight: 3, hachureGap: 8, hachureAngle: 45 });
rc.ellipse(300, 350, 480, 280, { fill: 'red', fillStyle: 'dots', hachureGap: 20, hachureAngle: 0, fillWeight: 2 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/singlestroke/line.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Line</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
disableMultiStroke: true
}
});
rc.line(10, 10, 100, 10);
rc.line(10, 210, 500, 210);
rc.line(10, 20, 10, 110, { stroke: 'red' });
rc.line(50, 30, 200, 100, { stroke: 'blue', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/singlestroke/path.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Curve</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
simplification: undefined,
disableMultiStroke: true,
disableMultiStrokeFill: true
}
});
rc.path('M400 100 h 90 v 90 h -90z', {
stroke: 'red',
strokeWidth: '3',
fill: 'rgba(0,0,255,0.2)',
fillStyle: 'solid'
});
rc.path('M400 250 h 90 v 90 h -90z', {
fill: 'rgba(0,0,255,0.6)'
});
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
stroke: 'red',
strokeWidth: '1',
fill: 'blue'
});
rc.path('M80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 Z', { fill: 'green' });
rc.path('M230 80 A 45 45, 0, 1, 0, 275 125 L 275 80 Z', { fill: 'purple', hachureAngle: 60, hachureGap: 5, fillLineDash: [15, 5], fillLineDashOffset: 10 });
rc.path('M80 230 A 45 45, 0, 0, 1, 125 275 L 125 230 Z', { fill: 'red' });
rc.path('M230 230 A 45 45, 0, 1, 1, 275 275 L 275 230 Z', { fill: 'blue', });
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'dots'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'cross-hatch'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'solid'
});
canvas.getContext('2d').translate(0, 70);
rc.path('M37,17v15H14V17z M50,5H5v50h45z', {
fill: 'blue',
fillStyle: 'zigzag'
});
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/singlestroke/polygon.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Polygon</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
disableMultiStroke: true,
disableMultiStrokeFill: true
}
});
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'black', strokeWidth: 2, fill: 'red', hachureAngle: 90 });
canvas.getContext('2d').translate(0, 210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'solid' });
canvas.getContext('2d').translate(0, 210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'dots', hachureGap: 16, fillWeight: 2 });
canvas.getContext('2d').translate(300, 0);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'cross-hatch', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
canvas.getContext('2d').translate(0, -210);
rc.polygon([
[10, 10],
[200, 10],
[100, 100],
[100, 50],
[300, 100],
[60, 200]
], { stroke: 'orange', strokeWidth: 5 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/singlestroke/rectangle.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>RoughJS Rectangle</title>
</head>
<body>
<canvas width="800" height="800"></canvas>
<script type="module">
import rough from '../../../bin/rough.js';
const canvas = document.querySelector('canvas');
const rc = rough.canvas(canvas, {
options: {
disableMultiStroke: true,
disableMultiStrokeFill: true
}
});
rc.rectangle(10, 10, 80, 80);
rc.rectangle(110, 10, 80, 80, { fill: 'red' });
rc.rectangle(210, 10, 80, 80, { fill: 'pink', fillStyle: 'solid' });
rc.rectangle(310, 10, 80, 80, { fill: 'red', fillStyle: 'cross-hatch' });
rc.rectangle(410, 10, 80, 80, { fill: 'red', fillStyle: 'zigzag', hachureGap: 8 });
rc.rectangle(510, 10, 80, 80, { fill: 'red', fillStyle: 'dots' });
rc.rectangle(10, 110, 80, 80, { roughness: 2 });
rc.rectangle(110, 110, 80, 80, { fill: 'red', stroke: 'blue', hachureAngle: 0, strokeWidth: 3 });
rc.rectangle(210, 110, 80, 80, { fill: 'pink', fillWeight: 5, hachureGap: 10, hachureAngle: 90 });
rc.rectangle(10, 210, 480, 280, { fill: 'red', fillStyle: 'dots', hachureGap: 20, fillWeight: 2 });
</script>
</body>
</html>
================================================
FILE: visual-tests/canvas/us.json
================================================
{"type":"Topology","objects":{"counties":{"type":"GeometryCollection","bbox":[-179.1473399999999,17.67439566600018,179.7784800000003,71.38921046500008],"geometries":[{"type":"MultiPolygon","id":53073,"arcs":[[[0,1,2]]]},{"type":"Polygon","id":30105,"arcs":[[3,4,5,6,7,8]]},{"type":"Polygon","id":30029,"arcs":[[9,10,11,12,13,14,15,16,17,18]]},{"type":"Polygon","id":16021,"arcs":[[19,20,21,22]]},{"type":"Polygon","id":30071,"arcs":[[-8,23,24,25,26,27]]},{"type":"Polygon","id":38079,"arcs":[[28,29,30,31]]},{"type":"Polygon","id":30053,"arcs":[[-18,32,33,-20,34]]},{"type":"Polygon","id":38009,"arcs":[[-30,35,36,37,38]]},{"type":"Polygon","id":30035,"arcs":[[39,40,-10,41]]},{"type":"Polygon","id":30041,"arcs":[[42,43,44,45]]},{"type":"Polygon","id":30005,"arcs":[[-27,46,47,-46,48]]},{"type":"Polygon","id":30019,"arcs":[[49,50,-4,51]]},{"type":"Polygon","id":38067,"arcs":[[52,53,54,55]]},{"type":"Polygon","id":27069,"arcs":[[56,57,-53,58]]},{"type":"Polygon","id":38095,"arcs":[[59,60,61,-32,62,63]]},{"type":"Polygon","id":38019,"arcs":[[-55,64,65,-64,66]]},{"type":"Polygon","id":53047,"arcs":[[67,68,69,70,71,72,-1,73]]},{"type":"Polygon","id":53065,"arcs":[[74,75,76,77,78]]},{"type":"Polygon","id":53051,"arcs":[[-22,79,80,-75,81]]},{"type":"Polygon","id":53019,"arcs":[[-78,82,-68,83]]},{"type":"Polygon","id":30051,"arcs":[[84,85,86,-44,87]]},{"type":"Polygon","id":38023,"arcs":[[88,89,90,91]]},{"type":"Polygon","id":38013,"arcs":[[92,93,94,95,-89,96]]},{"type":"Polygon","id":30101,"arcs":[[97,-86,98,-40]]},{"type":"Polygon","id":38075,"arcs":[[99,100,-93,101,-38]]},{"type":"Polygon","id":27135,"arcs":[[102,103,-57,104,105]]},{"type":"Polygon","id":30091,"arcs":[[-91,106,107,-50,108]]},{"type":"Polygon","id":16017,"arcs":[[-34,109,110,111,112,-80,-21]]},{"type":"Polygon","id":38101,"arcs":[[-101,113,114,115,-94]]},{"type":"MultiPolygon","id":53055,"arcs":[[[116]],[[117]],[[118]]]},{"type":"Polygon","id":27071,"arcs":[[119,120,121,122,123]]},{"type":"MultiPolygon","id":53057,"arcs":[[[124,-2,-73,125,126,127]]]},{"type":"Polygon","id":38105,"arcs":[[-96,128,129,130,-107,-90]]},{"type":"Polygon","id":38049,"arcs":[[131,132,133,-114,-100,-37]]},{"type":"Polygon","id":27137,"arcs":[[134,135,136,137,138,139,-120,140]]},{"type":"Polygon","id":30085,"arcs":[[-108,-131,141,142,-5,-51]]},{"type":"Polygon","id":53007,"arcs":[[-72,143,144,145,146,-126]]},{"type":"Polygon","id":38061,"arcs":[[147,148,149,-129,-95,-116]]},{"type":"Polygon","id":27089,"arcs":[[150,151,152,153,154,-58,-104]]},{"type":"Polygon","id":38069,"arcs":[[-62,155,156,157,-132,-36,-29]]},{"type":"MultiPolygon","id":38071,"arcs":[[[158]],[[159,160,161,-60,-66]]]},{"type":"Polygon","id":38099,"arcs":[[-54,-155,162,163,-160,-65]]},{"type":"Polygon","id":27007,"arcs":[[-122,164,165,166,167,168,-151,-103,169]]},{"type":"Polygon","id":30073,"arcs":[[-99,-85,170,171,-11,-41]]},{"type":"MultiPolygon","id":53029,"arcs":[[[172,173]],[[174]]]},{"type":"MultiPolygon","id":53009,"arcs":[[[175,176]]]},{"type":"Polygon","id":38005,"arcs":[[-61,-162,177,178,179,-156],[-159]]},{"type":"Polygon","id":30015,"arcs":[[-48,180,181,182,183,-171,-88,-43]]},{"type":"MultiPolygon","id":53061,"arcs":[[[-147,184,185,-173,186,-127]]]},{"type":"Polygon","id":30089,"arcs":[[-17,187,188,189,190,-110,-33]]},{"type":"Polygon","id":27075,"arcs":[[191,192,-135,193]]},{"type":"Polygon","id":38063,"arcs":[[194,195,196,197,-178,-161,-164]]},{"type":"Polygon","id":38035,"arcs":[[-154,198,199,200,-195,-163]]},{"type":"Polygon","id":27119,"arcs":[[201,202,203,204,205,206,207,-199,-153]]},{"type":"Polygon","id":27113,"arcs":[[-169,208,-204,209,-202,-152]]},{"type":"Polygon","id":30083,"arcs":[[210,211,212,213,-142]]},{"type":"Polygon","id":53017,"arcs":[[214,215,-144,-71]]},{"type":"Polygon","id":38053,"arcs":[[-150,216,217,218,219,-211,-130]]},{"type":"MultiPolygon","id":53031,"arcs":[[[220,221,222,-176,223]]]},{"type":"Polygon","id":30099,"arcs":[[-184,224,225,-12,-172]]},{"type":"Polygon","id":30055,"arcs":[[-214,226,227,228,-6,-143]]},{"type":"Polygon","id":16079,"arcs":[[-191,229,230,231,232,233,-111]]},{"type":"Polygon","id":30047,"arcs":[[234,-188,-16]]},{"type":"Polygon","id":53063,"arcs":[[-81,-113,235,236,237,238,-76]]},{"type":"Polygon","id":27029,"arcs":[[239,240,241,-205,-209,-168]]},{"type":"Polygon","id":16055,"arcs":[[-234,242,-236,-112]]},{"type":"Polygon","id":30033,"arcs":[[-229,243,244,245,246,-24,-7]]},{"type":"Polygon","id":27125,"arcs":[[-203,-210]]},{"type":"Polygon","id":53025,"arcs":[[-70,247,248,249,250,251,252,-215]]},{"type":"Polygon","id":53043,"arcs":[[-83,-77,-239,253,254,-248,-69]]},{"type":"Polygon","id":30049,"arcs":[[255,256,257,258,259,-13,-226]]},{"type":"MultiPolygon","id":53035,"arcs":[[[260]],[[261,262,263,264,265]]]},{"type":"Polygon","id":27061,"arcs":[[-140,266,267,-165,-121]]},{"type":"Polygon","id":38055,"arcs":[[268,269,270,271,272,-148,-115,-134]]},{"type":"Polygon","id":38027,"arcs":[[-198,273,274,275,-179]]},{"type":"Polygon","id":38103,"arcs":[[-180,-276,276,277,278,279,-157]]},{"type":"Polygon","id":38083,"arcs":[[-158,-280,280,281,-269,-133]]},{"type":"Polygon","id":38025,"arcs":[[-273,282,283,284,-217,-149]]},{"type":"Polygon","id":30027,"arcs":[[-26,285,286,287,288,289,-181,-47]]},{"type":"Polygon","id":30021,"arcs":[[-213,290,291,-227]]},{"type":"MultiPolygon","id":53033,"arcs":[[[292]],[[-146,293,294,295,-185]]]},{"type":"Polygon","id":30013,"arcs":[[296,297,-256,-225,-183]]},{"type":"Polygon","id":38091,"arcs":[[-201,298,299,300,301,-196]]},{"type":"Polygon","id":38039,"arcs":[[-302,302,303,304,-274,-197]]},{"type":"Polygon","id":38097,"arcs":[[305,306,-299,-200,-208]]},{"type":"MultiPolygon","id":53045,"arcs":[[[307,-265,308,309,310,311,-221]]]},{"type":"Polygon","id":30063,"arcs":[[-15,312,313,314,315,316,317,-189,-235]]},{"type":"Polygon","id":30077,"arcs":[[-260,318,319,320,-313,-14]]},{"type":"Polygon","id":30069,"arcs":[[-247,321,322,-286,-25]]},{"type":"Polygon","id":53037,"arcs":[[-216,-253,323,-294,-145]]},{"type":"Polygon","id":38031,"arcs":[[-305,324,-277,-275]]},{"type":"Polygon","id":38057,"arcs":[[325,326,327,-283,-272]]},{"type":"MultiPolygon","id":53027,"arcs":[[[-312,328,329,330,331,-222]]]},{"type":"Polygon","id":27087,"arcs":[[332,333,-206,-242]]},{"type":"Polygon","id":27107,"arcs":[[-207,-334,334,335,336,-306]]},{"type":"Polygon","id":30061,"arcs":[[-318,337,-230,-190]]},{"type":"Polygon","id":27021,"arcs":[[338,339,340,341,342,343,-166,-268]]},{"type":"Polygon","id":23003,"arcs":[[344,345,346,347,348]]},{"type":"Polygon","id":30045,"arcs":[[-290,349,350,-297,-182]]},{"type":"Polygon","id":16009,"arcs":[[-233,351,352,-237,-243]]},{"type":"Polygon","id":27057,"arcs":[[-344,353,354,-240,-167]]},{"type":"MultiPolygon","id":53053,"arcs":[[[-295,355,356,357,358]],[[-262,359]],[[-309,-264,360]]]},{"type":"Polygon","id":30109,"arcs":[[-220,361,362,363,-291,-212]]},{"type":"Polygon","id":38007,"arcs":[[-285,364,365,366,-218]]},{"type":"Polygon","id":38033,"arcs":[[-367,367,368,-362,-219]]},{"type":"Polygon","id":38043,"arcs":[[369,370,371,372,-281,-279]]},{"type":"Polygon","id":38093,"arcs":[[-304,373,374,375,-370,-278,-325]]},{"type":"Polygon","id":38015,"arcs":[[-373,376,377,378,-270,-282]]},{"type":"Polygon","id":38065,"arcs":[[-379,379,-326,-271]]},{"type":"Polygon","id":53001,"arcs":[[380,381,-249,-255]]},{"type":"Polygon","id":53075,"arcs":[[-238,-353,382,383,384,385,386,387,-381,-254]]},{"type":"Polygon","id":38003,"arcs":[[-301,388,389,390,-374,-303]]},{"type":"Polygon","id":38017,"arcs":[[-307,-337,391,392,393,-389,-300]]},{"type":"Polygon","id":53067,"arcs":[[-358,394,-329,-311,395]]},{"type":"Polygon","id":30079,"arcs":[[-364,396,397,-244,-228,-292]]},{"type":"Polygon","id":27005,"arcs":[[-355,398,399,400,-335,-333,-241]]},{"type":"Polygon","id":27027,"arcs":[[-401,401,402,403,-392,-336]]},{"type":"Polygon","id":16057,"arcs":[[-232,404,405,-383,-352]]},{"type":"Polygon","id":53077,"arcs":[[-252,406,407,408,409,-356,-324]]},{"type":"Polygon","id":30059,"arcs":[[-351,410,411,412,413,414,-257,-298]]},{"type":"Polygon","id":27001,"arcs":[[-139,415,416,417,418,419,-339,-267]]},{"type":"Polygon","id":26131,"arcs":[[420,421,422,423]]},{"type":"Polygon","id":38089,"arcs":[[-328,424,425,426,427,-365,-284]]},{"type":"Polygon","id":38059,"arcs":[[-378,428,429,430,-425,-327,-380]]},{"type":"Polygon","id":26013,"arcs":[[431,432,433,434]]},{"type":"Polygon","id":16035,"arcs":[[-338,-317,435,436,437,-405,-231]]},{"type":"Polygon","id":30017,"arcs":[[438,439,440,441,-245,-398]]},{"type":"Polygon","id":30087,"arcs":[[-442,442,443,444,445,446,-322,-246]]},{"type":"Polygon","id":30039,"arcs":[[447,448,-314,-321]]},{"type":"Polygon","id":27159,"arcs":[[-343,449,450,-399,-354]]},{"type":"Polygon","id":27035,"arcs":[[-420,451,452,-340]]},{"type":"MultiPolygon","id":53049,"arcs":[[[453,454,455,-331]]]},{"type":"Polygon","id":53041,"arcs":[[-395,-357,-410,456,457,458,-454,-330]]},{"type":"Polygon","id":30007,"arcs":[[-415,459,460,-258]]},{"type":"Polygon","id":27017,"arcs":[[461,462,-416,-138]]},{"type":"Polygon","id":26053,"arcs":[[463,464,465,466,-422]]},{"type":"Polygon","id":30065,"arcs":[[-447,467,468,-287,-323]]},{"type":"Polygon","id":26095,"arcs":[[469,470,471,472,473]]},{"type":"Polygon","id":30037,"arcs":[[-469,474,475,476,477,-288]]},{"type":"Polygon","id":30107,"arcs":[[-289,-478,478,-411,-350]]},{"type":"Polygon","id":53021,"arcs":[[479,480,481,-250,-382,-388]]},{"type":"Polygon","id":53005,"arcs":[[482,483,484,485,-407,-251,-482]]},{"type":"Polygon","id":27111,"arcs":[[-451,486,487,488,489,-402,-400]]},{"type":"Polygon","id":38037,"arcs":[[490,491,492,-426,-431]]},{"type":"Polygon","id":53023,"arcs":[[493,494,495,-386]]},{"type":"Polygon","id":30025,"arcs":[[-369,496,497,498,499,-439,-397,-363]]},{"type":"Polygon","id":16049,"arcs":[[500,501,502,503,504,505,506,-436,-316]]},{"type":"Polygon","id":30081,"arcs":[[-449,507,508,509,-501,-315]]},{"type":"Polygon","id":38029,"arcs":[[-372,510,511,512,513,-429,-377]]},{"type":"Polygon","id":38047,"arcs":[[-376,514,515,-511,-371]]},{"type":"Polygon","id":16069,"arcs":[[-438,516,-506,517,518,-384,-406]]},{"type":"Polygon","id":38087,"arcs":[[519,520,521,-497,-368,-366,-428]]},{"type":"Polygon","id":38045,"arcs":[[522,523,524,-515,-375,-391]]},{"type":"Polygon","id":38041,"arcs":[[-493,525,-520,-427]]},{"type":"Polygon","id":27167,"arcs":[[-490,526,527,528,-403]]},{"type":"Polygon","id":38073,"arcs":[[-394,529,530,531,-523,-390]]},{"type":"Polygon","id":38077,"arcs":[[-529,532,533,534,-530,-393,-404]]},{"type":"Polygon","id":53013,"arcs":[[535,536,537,-480,-387,-496]]},{"type":"Polygon","id":53071,"arcs":[[-538,538,-483,-481]]},{"type":"Polygon","id":55051,"arcs":[[-466,539,540,541,542]]},{"type":"Polygon","id":23025,"arcs":[[543,544,545,546,547,548,-348]]},{"type":"Polygon","id":23021,"arcs":[[549,-544,-347]]},{"type":"Polygon","id":30043,"arcs":[[-461,550,551,552,553,-319,-259]]},{"type":"Polygon","id":26153,"arcs":[[-472,554,555,556,557]]},{"type":"Polygon","id":30111,"arcs":[[558,559,560,561,-475,-468,-446]]},{"type":"Polygon","id":30103,"arcs":[[562,-559,-445]]},{"type":"Polygon","id":16061,"arcs":[[-437,-507,-517]]},{"type":"Polygon","id":53003,"arcs":[[-519,563,-494,-385]]},{"type":"Polygon","id":38085,"arcs":[[-514,564,565,-491,-430]]},{"type":"Polygon","id":26071,"arcs":[[-433,566,567,568,569,570,-464,-421,571]]},{"type":"Polygon","id":27115,"arcs":[[572,573,574,575,-417,-463]]},{"type":"Polygon","id":23019,"arcs":[[576,577,578,579,-545,-550,-346]]},{"type":"Polygon","id":53059,"arcs":[[-409,580,581,582,583,584,-457]]},{"type":"Polygon","id":53015,"arcs":[[-585,585,586,587,588,-458]]},{"type":"MultiPolygon","id":53069,"arcs":[[[-459,-589,589,-455]]]},{"type":"Polygon","id":27153,"arcs":[[-342,590,591,592,-487,-450]]},{"type":"Polygon","id":27097,"arcs":[[-453,593,594,595,-591,-341]]},{"type":"Polygon","id":55125,"arcs":[[596,597,-540,-465,-571,598]]},{"type":"MultiPolygon","id":41007,"arcs":[[[599,600,601]]]},{"type":"Polygon","id":38001,"arcs":[[-492,-566,602,603,604,605,-521,-526]]},{"type":"Polygon","id":38081,"arcs":[[-535,606,607,608,-531]]},{"type":"Polygon","id":38051,"arcs":[[-525,609,610,611,-512,-516]]},{"type":"Polygon","id":38021,"arcs":[[-532,-609,612,613,-610,-524]]},{"type":"Polygon","id":38011,"arcs":[[-606,614,-498,-522]]},{"type":"Polygon","id":30023,"arcs":[[-554,615,616,-508,-448,-320]]},{"type":"Polygon","id":26043,"arcs":[[617,618,619,-568,620]]},{"type":"Polygon","id":27095,"arcs":[[621,622,623,624,-594,-452,-419]]},{"type":"Polygon","id":30097,"arcs":[[-477,625,626,-412,-479]]},{"type":"Polygon","id":30031,"arcs":[[-414,627,628,629,630,631,-551,-460]]},{"type":"Polygon","id":30067,"arcs":[[-627,632,633,634,-628,-413]]},{"type":"Polygon","id":30093,"arcs":[[635,636,-616,-553]]},{"type":"MultiPolygon","id":41009,"arcs":[[[637,638,639,-600,640,-587]]]},{"type":"Polygon","id":27065,"arcs":[[-576,641,-622,-418]]},{"type":"Polygon","id":55013,"arcs":[[642,643,644,645,-574,646]]},{"type":"Polygon","id":55113,"arcs":[[647,648,649,650,651]]},{"type":"Polygon","id":55129,"arcs":[[-651,652,-643,653]]},{"type":"Polygon","id":30011,"arcs":[[654,655,656,657,-440,-500]]},{"type":"Polygon","id":30095,"arcs":[[-562,658,-633,-626,-476]]},{"type":"Polygon","id":27051,"arcs":[[-489,659,660,661,-527]]},{"type":"Polygon","id":27041,"arcs":[[-593,662,663,-660,-488]]},{"type":"Polygon","id":55041,"arcs":[[664,665,666,667,668,-599,-570]]},{"type":"Polygon","id":53011,"arcs":[[-584,669,-638,-586]]},{"type":"Polygon","id":53039,"arcs":[[-486,670,671,672,673,674,-581,-408]]},{"type":"Polygon","id":30003,"arcs":[[-444,675,676,677,678,-560,-563]]},{"type":"Polygon","id":27155,"arcs":[[-662,679,680,681,-533,-528]]},{"type":"Polygon","id":55037,"arcs":[[-620,682,-665,-569]]},{"type":"Polygon","id":41059,"arcs":[[-537,683,684,685,686,-484,-539]]},{"type":"Polygon","id":41063,"arcs":[[-495,-564,-518,-505,687,688,689,-684,-536]]},{"type":"Polygon","id":26109,"arcs":[[690,691,692,-618,693]]},{"type":"Polygon","id":55099,"arcs":[[-598,694,695,696,697,-649,698,-541]]},{"type":"Polygon","id":46105,"arcs":[[699,700,701,702,703,-604]]},{"type":"Polygon","id":46031,"arcs":[[704,705,706,707,-700,-603,-565]]},{"type":"Polygon","id":46063,"arcs":[[-605,-704,708,-655,-499,-615]]},{"type":"Polygon","id":46021,"arcs":[[-612,709,710,-705,-513]]},{"type":"Polygon","id":30001,"arcs":[[-637,711,712,713,714,-509,-617]]},{"type":"Polygon","id":46089,"arcs":[[-614,715,716,717,-710,-611]]},{"type":"Polygon","id":46013,"arcs":[[-608,718,719,720,721,722,-716,-613]]},{"type":"Polygon","id":46109,"arcs":[[-682,723,724,725,726,-534]]},{"type":"Polygon","id":46091,"arcs":[[-727,727,-719,-607]]},{"type":"Polygon","id":41049,"arcs":[[-687,728,729,730,-671,-485]]},{"type":"Polygon","id":55085,"arcs":[[-669,731,732,-695,-597]]},{"type":"Polygon","id":41061,"arcs":[[733,734,-685,-690]]},{"type":"Polygon","id":30057,"arcs":[[-632,735,-712,-636,-552]]},{"type":"Polygon","id":27009,"arcs":[[-625,736,737,-595]]},{"type":"Polygon","id":41021,"arcs":[[738,739,740,-672,-731]]},{"type":"Polygon","id":30075,"arcs":[[-658,741,742,743,-676,-443,-441]]},{"type":"Polygon","id":26031,"arcs":[[744,745,746,747,748,749]]},{"type":"MultiPolygon","id":41057,"arcs":[[[750,751,752,753,754,-601]]]},{"type":"Polygon","id":41067,"arcs":[[755,756,757,-751,-640]]},{"type":"Polygon","id":27145,"arcs":[[-738,758,759,760,761,762,-663,-592,-596]]},{"type":"Polygon","id":27149,"arcs":[[763,764,765,-680,-661]]},{"type":"Polygon","id":27121,"arcs":[[-763,766,767,-764,-664]]},{"type":"Polygon","id":41055,"arcs":[[768,-673,-741]]},{"type":"Polygon","id":27059,"arcs":[[769,770,771,-623,-642]]},{"type":"Polygon","id":27025,"arcs":[[-646,772,773,774,-770,-575]]},{"type":"Polygon","id":55095,"arcs":[[775,776,777,-773,-645]]},{"type":"Polygon","id":41051,"arcs":[[-670,-583,778,779,-756,-639]]},{"type":"Polygon","id":41027,"arcs":[[780,781,-779,-582,-675]]},{"type":"Polygon","id":41065,"arcs":[[-769,-740,782,783,784,785,-781,-674]]},{"type":"Polygon","id":16059,"arcs":[[-715,786,787,788,789,-502,-510]]},{"type":"MultiPolygon","id":23029,"arcs":[[[790,-577,-345,791]]]},{"type":"Polygon","id":23007,"arcs":[[792,793,794,795,-548]]},{"type":"Polygon","id":26141,"arcs":[[796,797,-745,798]]},{"type":"Polygon","id":55005,"arcs":[[-653,799,800,801,-776,-644]]},{"type":"Polygon","id":55107,"arcs":[[-698,802,803,-800,-650]]},{"type":"Polygon","id":30009,"arcs":[[-679,804,805,-634,-659,-561]]},{"type":"Polygon","id":46129,"arcs":[[-718,806,807,808,-706,-711]]},{"type":"Polygon","id":46045,"arcs":[[-723,809,810,-807,-717]]},{"type":"Polygon","id":46037,"arcs":[[-728,-726,811,812,813,814,-720]]},{"type":"Polygon","id":27011,"arcs":[[-681,-766,815,816,817,-724]]},{"type":"Polygon","id":27141,"arcs":[[-624,-772,818,819,820,-759,-737]]},{"type":"Polygon","id":55069,"arcs":[[821,822,823,-696,-733]]},{"type":"Polygon","id":46041,"arcs":[[-809,824,825,826,827,-707]]},{"type":"Polygon","id":46137,"arcs":[[-828,828,829,-701,-708]]},{"type":"Polygon","id":55067,"arcs":[[-668,830,831,832,833,-822,-732]]},{"type":"Polygon","id":41005,"arcs":[[-782,-786,834,835,-757,-780]]},{"type":"Polygon","id":41071,"arcs":[[-836,836,837,-752,-758]]},{"type":"Polygon","id":27171,"arcs":[[-821,838,839,840,841,-760]]},{"type":"Polygon","id":27003,"arcs":[[-775,842,843,844,-819,-771]]},{"type":"Polygon","id":27067,"arcs":[[845,846,847,848,-767,-762]]},{"type":"Polygon","id":27151,"arcs":[[-849,849,850,-816,-765,-768]]},{"type":"Polygon","id":55119,"arcs":[[-824,851,852,853,-803,-697]]},{"type":"Polygon","id":55083,"arcs":[[854,855,856,857,858,859,860,-831,-667]]},{"type":"Polygon","id":23017,"arcs":[[-795,861,862,863,864,865,866]]},{"type":"Polygon","id":46051,"arcs":[[-818,867,868,869,-812,-725]]},{"type":"Polygon","id":27093,"arcs":[[-842,870,871,-846,-761]]},{"type":"Polygon","id":33007,"arcs":[[872,873,874,875,-866]]},{"type":"Polygon","id":27163,"arcs":[[-778,876,877,878,879,-843,-774]]},{"type":"Polygon","id":55017,"arcs":[[-854,880,881,882,-801,-804]]},{"type":"Polygon","id":41047,"arcs":[[-835,-785,883,884,885,-837]]},{"type":"Polygon","id":16003,"arcs":[[886,887,888,889,-688,-504]]},{"type":"Polygon","id":27073,"arcs":[[-851,890,891,892,-868,-817]]},{"type":"MultiPolygon","id":23009,"arcs":[[[893]],[[894]],[[895,-578,-791]]]},{"type":"Polygon","id":46107,"arcs":[[-811,896,897,898,-825,-808]]},{"type":"Polygon","id":46049,"arcs":[[-722,899,900,901,-897,-810]]},{"type":"Polygon","id":27053,"arcs":[[-820,-845,902,903,904,905,-839]]},{"type":"Polygon","id":46115,"arcs":[[-815,906,907,908,-900,-721]]},{"type":"Polygon","id":16085,"arcs":[[-790,909,910,911,-887,-503]]},{"type":"Polygon","id":46019,"arcs":[[-703,912,913,914,-656,-709]]},{"type":"Polygon","id":55109,"arcs":[[915,916,-877,-777]]},{"type":"Polygon","id":55033,"arcs":[[-883,917,918,919,-916,-802]]},{"type":"Polygon","id":26009,"arcs":[[920,921,922,923,924]]},{"type":"Polygon","id":26137,"arcs":[[925,926,-921,927,-747]]},{"type":"Polygon","id":26119,"arcs":[[928,929,-926,-746,-798]]},{"type":"Polygon","id":46025,"arcs":[[930,931,932,933,-907,-814]]},{"type":"Polygon","id":46029,"arcs":[[-870,934,935,-931,-813]]},{"type":"Polygon","id":27023,"arcs":[[-848,936,937,-891,-850]]},{"type":"Polygon","id":27123,"arcs":[[-880,938,-903,-844]]},{"type":"Polygon","id":55073,"arcs":[[-823,-834,939,940,941,942,-852]]},{"type":"Polygon","id":55078,"arcs":[[-861,943,-832]]},{"type":"Polygon","id":41001,"arcs":[[-890,944,945,946,-734,-689]]},{"type":"Polygon","id":41053,"arcs":[[-886,947,948,949,-753,-838]]},{"type":"Polygon","id":41069,"arcs":[[-730,950,951,952,-783,-739]]},{"type":"Polygon","id":41041,"arcs":[[-950,953,954,955,-754]]},{"type":"Polygon","id":46093,"arcs":[[-830,956,957,958,-913,-702]]},{"type":"Polygon","id":55019,"arcs":[[-943,959,960,961,-881,-853]]},{"type":"Polygon","id":55115,"arcs":[[-944,-860,962,963,964,-940,-833]]},{"type":"Polygon","id":50011,"arcs":[[965,966,967,968,969]]},{"type":"Polygon","id":50009,"arcs":[[970,971,972,973,-875]]},{"type":"Polygon","id":50013,"arcs":[[974,975,976,-969]]},{"type":"Polygon","id":36019,"arcs":[[977,978,979,980,-976]]},{"type":"Polygon","id":50019,"arcs":[[-973,981,982,-966,983]]},{"type":"Polygon","id":56029,"arcs":[[984,985,986,987,988,-629,-635,-806]]},{"type":"Polygon","id":36089,"arcs":[[989,990,991,992,993,994]]},{"type":"Polygon","id":56003,"arcs":[[995,996,997,-985,-805,-678]]},{"type":"Polygon","id":56005,"arcs":[[998,999,1000,1001,1002,-743]]},{"type":"Polygon","id":56033,"arcs":[[-744,-1003,1003,-996,-677]]},{"type":"Polygon","id":36033,"arcs":[[-980,1004,1005,-990,1006]]},{"type":"Polygon","id":56011,"arcs":[[-657,-915,1007,1008,-999,-742]]},{"type":"Polygon","id":41023,"arcs":[[-735,-947,1009,1010,1011,-951,-729,-686]]},{"type":"Polygon","id":27085,"arcs":[[-841,1012,1013,1014,-871]]},{"type":"Polygon","id":27019,"arcs":[[-906,1015,1016,-1013,-840]]},{"type":"Polygon","id":46039,"arcs":[[-893,1017,1018,1019,1020,-935,-869]]},{"type":"Polygon","id":27173,"arcs":[[1021,1022,1023,1024,-1018,-892,-938]]},{"type":"Polygon","id":27037,"arcs":[[-879,1025,1026,1027,1028,-904,-939]]},{"type":"Polygon","id":46119,"arcs":[[1029,1030,1031,-826,-899]]},{"type":"Polygon","id":46069,"arcs":[[-902,1032,1033,1034,1035,-1030,-898]]},{"type":"Polygon","id":46059,"arcs":[[-909,1036,1037,1038,-1033,-901]]},{"type":"Polygon","id":27129,"arcs":[[-872,-1015,1039,1040,1041,1042,-1022,-937,-847]]},{"type":"Polygon","id":16037,"arcs":[[1043,1044,1045,1046,-910,-789]]},{"type":"Polygon","id":55093,"arcs":[[-920,1047,1048,-1026,-878,-917]]},{"type":"Polygon","id":26001,"arcs":[[1049,1050,1051,1052]]},{"type":"Polygon","id":26079,"arcs":[[1053,1054,1055,-922]]},{"type":"Polygon","id":26039,"arcs":[[1056,1057,-1054,-927]]},{"type":"Polygon","id":55035,"arcs":[[-962,1058,1059,1060,1061,-918,-882]]},{"type":"Polygon","id":26135,"arcs":[[-1052,1062,-1057,-930]]},{"type":"Polygon","id":16087,"arcs":[[1063,1064,1065,-945,-889]]},{"type":"Polygon","id":41031,"arcs":[[-953,1066,1067,1068,-884,-784]]},{"type":"Polygon","id":27139,"arcs":[[-1029,1069,1070,1071,-1016,-905]]},{"type":"Polygon","id":46057,"arcs":[[-1021,1072,1073,-932,-936]]},{"type":"Polygon","id":50015,"arcs":[[1074,1075,1076,-967,-983]]},{"type":"Polygon","id":41043,"arcs":[[-1069,1077,1078,1079,-948,-885]]},{"type":"Polygon","id":46117,"arcs":[[-1032,1080,1081,1082,1083,-827]]},{"type":"Polygon","id":26019,"arcs":[[1084,1085,1086,1087]]},{"type":"Polygon","id":50005,"arcs":[[1088,1089,1090,-1075,-982,-972]]},{"type":"MultiPolygon","id":23027,"arcs":[[[-580,1091,1092,1093,1094,-546]]]},{"type":"Polygon","id":16043,"arcs":[[1095,1096,1097,1098,1099,-713,-736,-631]]},{"type":"Polygon","id":46055,"arcs":[[1100,1101,1102,-957,-829,-1084]]},{"type":"Polygon","id":50007,"arcs":[[-1077,1103,1104,1105,-978,-975,-968]]},{"type":"Polygon","id":41003,"arcs":[[-1080,1106,-954,-949]]},{"type":"Polygon","id":23011,"arcs":[[1107,1108,1109,1110,-793,-547,-1095]]},{"type":"Polygon","id":27143,"arcs":[[-1017,-1072,1111,1112,-1040,-1014]]},{"type":"Polygon","id":27049,"arcs":[[1113,1114,1115,1116,1117,-1027,-1049]]},{"type":"Polygon","id":27127,"arcs":[[1118,1119,1120,1121,-1023,-1043]]},{"type":"Polygon","id":55097,"arcs":[[1122,1123,1124,1125,-941]]},{"type":"Polygon","id":55141,"arcs":[[-1126,1126,1127,1128,-960,-942]]},{"type":"MultiPolygon","id":55009,"arcs":[[[-857,1129]],[[-859,1130,1131,1132,1133,1134,-963]]]},{"type":"Polygon","id":55091,"arcs":[[-1062,1135,1136,-1114,-1048,-919]]},{"type":"Polygon","id":55135,"arcs":[[1137,1138,1139,-1123,-965]]},{"type":"Polygon","id":55061,"arcs":[[1140,1141,-1132,1142,1143]]},{"type":"Polygon","id":56039,"arcs":[[1144,1145,1146,1147,1148,-1096,-630,-989]]},{"type":"Polygon","id":46005,"arcs":[[-934,1149,1150,1151,-1037,-908]]},{"type":"Polygon","id":27081,"arcs":[[1152,1153,1154,-1019,-1025]]},{"type":"Polygon","id":27083,"arcs":[[-1122,1155,1156,-1153,-1024]]},{"type":"Polygon","id":46081,"arcs":[[-959,1157,1158,-1008,-914]]},{"type":"Polygon","id":55011,"arcs":[[-1061,1159,1160,1161,-1136]]},{"type":"Polygon","id":55121,"arcs":[[1162,1163,1164,-1160,-1060]]},{"type":"Polygon","id":55053,"arcs":[[-961,-1129,1165,1166,1167,-1163,-1059]]},{"type":"Polygon","id":55087,"arcs":[[-964,-1135,1168,1169,-1138]]},{"type":"Polygon","id":16033,"arcs":[[-1100,1170,1171,-787,-714]]},{"type":"Polygon","id":56019,"arcs":[[-1002,1172,1173,1174,-997,-1004]]},{"type":"Polygon","id":41013,"arcs":[[-952,-1012,1175,1176,-1067]]},{"type":"Polygon","id":46065,"arcs":[[-1036,1177,-1081,-1031]]},{"type":"Polygon","id":36031,"arcs":[[1178,1179,1180,1181,-1005,-979,-1106]]},{"type":"Polygon","id":27079,"arcs":[[1182,1183,1184,1185,-1112,-1071]]},{"type":"Polygon","id":27131,"arcs":[[-1028,-1118,1186,1187,1188,-1183,-1070]]},{"type":"Polygon","id":46077,"arcs":[[-1074,1189,1190,1191,1192,-1150,-933]]},{"type":"Polygon","id":46011,"arcs":[[-1155,1193,1194,1195,-1190,-1073,-1020]]},{"type":"Polygon","id":26101,"arcs":[[1196,1197,1198,1199,-1086]]},{"type":"Polygon","id":26165,"arcs":[[1200,1201,1202,-1197,1203]]},{"type":"Polygon","id":26143,"arcs":[[1204,1205,1206,1207,-1058]]},{"type":"Polygon","id":26113,"arcs":[[-1208,1208,1209,-1201,-1055]]},{"type":"Polygon","id":26069,"arcs":[[1210,1211,1212,-1051]]},{"type":"Polygon","id":26129,"arcs":[[-1213,1213,1214,-1205,-1063]]},{"type":"Polygon","id":16045,"arcs":[[-912,1215,1216,1217,1218,-1064,-888]]},{"type":"Polygon","id":46103,"arcs":[[-1103,1219,1220,1221,1222,-1158,-958]]},{"type":"Polygon","id":50023,"arcs":[[1223,1224,-1104,-1076,-1091]]},{"type":"Polygon","id":27015,"arcs":[[1225,1226,1227,1228,-1119,-1042]]},{"type":"Polygon","id":23001,"arcs":[[1229,1230,-862,-794,-1111]]},{"type":"Polygon","id":27103,"arcs":[[1231,-1226,-1041,-1113,-1186]]},{"type":"Polygon","id":27157,"arcs":[[-1137,-1162,1232,1233,-1115]]},{"type":"Polygon","id":41045,"arcs":[[-1066,1234,1235,1236,1237,1238,-1010,-946]]},{"type":"Polygon","id":33009,"arcs":[[1239,1240,1241,1242,1243,1244,-1089,-971,-874]]},{"type":"Polygon","id":41017,"arcs":[[-1177,1245,1246,1247,1248,-1078,-1068]]},{"type":"MultiPolygon","id":36045,"arcs":[[[1249,-994,1250,1251,1252,1253,1254]]]},{"type":"MultiPolygon","id":23013,"arcs":[[[1255,1256,-1093]]]},{"type":"Polygon","id":16015,"arcs":[[-1047,1257,1258,-1216,-911]]},{"type":"MultiPolygon","id":23015,"arcs":[[[-1094,-1257,1259,1260,1261,-1108]]]},{"type":"Polygon","id":55071,"arcs":[[-1142,1262,1263,1264,-1133]]},{"type":"Polygon","id":50001,"arcs":[[-1225,1265,1266,1267,1268,-1179,-1105]]},{"type":"MultiPolygon","id":41039,"arcs":[[[-1249,1269,1270,1271,-955,-1107,-1079]]]},{"type":"Polygon","id":33003,"arcs":[[-865,1272,1273,1274,-1240,-873]]},{"type":"Polygon","id":27013,"arcs":[[-1185,1275,1276,1277,1278,-1227,-1232]]},{"type":"Polygon","id":55057,"arcs":[[1279,1280,1281,1282,-1166,-1128]]},{"type":"Polygon","id":55001,"arcs":[[-1125,1283,1284,1285,-1280,-1127]]},{"type":"Polygon","id":55137,"arcs":[[-1140,1286,1287,1288,-1284,-1124]]},{"type":"Polygon","id":55139,"arcs":[[1289,1290,1291,-1287,-1139,-1170]]},{"type":"Polygon","id":55015,"arcs":[[-1134,-1265,1292,1293,-1290,-1169]]},{"type":"Polygon","id":16023,"arcs":[[-1172,1294,1295,1296,-1044,-788]]},{"type":"Polygon","id":50017,"arcs":[[-1245,1297,-1266,-1224,-1090]]},{"type":"Polygon","id":36049,"arcs":[[1298,1299,-1251,-993,1300]]},{"type":"Polygon","id":46085,"arcs":[[1301,1302,1303,1304,1305,-1082,-1178,-1035,1306]]},{"type":"Polygon","id":27117,"arcs":[[-1157,1307,1308,1309,-1194,-1154]]},{"type":"Polygon","id":27101,"arcs":[[-1121,1310,1311,1312,-1308,-1156]]},{"type":"Polygon","id":46073,"arcs":[[-1152,1313,1314,1315,1316,-1038]]},{"type":"Polygon","id":27147,"arcs":[[1317,1318,1319,1320,-1188]]},{"type":"Polygon","id":27039,"arcs":[[1321,1322,-1318,-1187,-1117]]},{"type":"Polygon","id":46101,"arcs":[[-1310,1323,1324,-1195]]},{"type":"Polygon","id":27161,"arcs":[[-1321,1325,1326,-1276,-1184,-1189]]},{"type":"Polygon","id":46017,"arcs":[[-1317,1327,-1307,-1034,-1039]]},{"type":"Polygon","id":46111,"arcs":[[-1193,1328,1329,1330,1331,-1314,-1151]]},{"type":"Polygon","id":27109,"arcs":[[-1234,1332,1333,1334,-1322,-1116]]},{"type":"Polygon","id":27033,"arcs":[[-1229,1335,1336,1337,-1311,-1120]]},{"type":"Polygon","id":46079,"arcs":[[-1196,-1325,1338,1339,1340,-1191]]},{"type":"Polygon","id":46097,"arcs":[[-1341,1341,1342,-1329,-1192]]},{"type":"Polygon","id":27169,"arcs":[[-1161,-1165,1343,1344,1345,-1333,-1233]]},{"type":"Polygon","id":56045,"arcs":[[-1159,-1223,1346,1347,1348,-1000,-1009]]},{"type":"Polygon","id":26105,"arcs":[[1349,1350,1351,-1199]]},{"type":"MultiPolygon","id":23005,"arcs":[[[-1231,1352,1353,1354,1355,1356,-863]]]},{"type":"Polygon","id":46075,"arcs":[[-1306,1357,1358,-1101,-1083]]},{"type":"Polygon","id":56043,"arcs":[[-1175,1359,1360,1361,-986,-998]]},{"type":"MultiPolygon","id":23023,"arcs":[[[-1355,1362]],[[1363,1364,-1261,1365]],[[1366,-1353,-1230,-1110]]]},{"type":"Polygon","id":26085,"arcs":[[-1198,-1203,1367,1368,-1350]]},{"type":"Polygon","id":26133,"arcs":[[-1210,1369,1370,-1368,-1202]]},{"type":"Polygon","id":26035,"arcs":[[-1207,1371,1372,-1370,-1209]]},{"type":"Polygon","id":26051,"arcs":[[1373,1374,1375,-1372,-1206,-1215]]},{"type":"Polygon","id":55081,"arcs":[[-1283,1376,1377,-1167]]},{"type":"Polygon","id":16075,"arcs":[[-1219,1378,-1235,-1065]]},{"type":"Polygon","id":36041,"arcs":[[1379,1380,1381,1382,-991,-1006,-1182]]},{"type":"Polygon","id":27165,"arcs":[[-1279,1383,1384,-1336,-1228]]},{"type":"Polygon","id":16039,"arcs":[[-1046,1385,1386,1387,1388,1389,1390,-1258]]},{"type":"Polygon","id":36043,"arcs":[[-1383,1391,1392,1393,1394,-1301,-992]]},{"type":"Polygon","id":55063,"arcs":[[-1168,-1378,1395,1396,-1344,-1164]]},{"type":"Polygon","id":56017,"arcs":[[-1362,1397,-987]]},{"type":"Polygon","id":16051,"arcs":[[-1099,1398,1399,1400,-1295,-1171]]},{"type":"Polygon","id":41025,"arcs":[[-1239,1401,1402,1403,-1246,-1176,-1011]]},{"type":"Polygon","id":56013,"arcs":[[-1398,-1361,1404,1405,1406,1407,-1145,-988]]},{"type":"Polygon","id":26017,"arcs":[[1408,1409,1410,1411,-1375,1412]]},{"type":"Polygon","id":46071,"arcs":[[-1359,1413,1414,1415,-1220,-1102]]},{"type":"Polygon","id":16013,"arcs":[[-1297,1416,1417,1418,1419,1420,1421,-1386,-1045]]},{"type":"Polygon","id":16081,"arcs":[[-1149,1422,1423,-1097]]},{"type":"Polygon","id":55047,"arcs":[[-1292,1424,1425,1426,1427,-1288]]},{"type":"Polygon","id":55077,"arcs":[[-1428,1428,-1285,-1289]]},{"type":"Polygon","id":50027,"arcs":[[-1244,1429,1430,1431,1432,-1267,-1298]]},{"type":"MultiPolygon","id":41019,"arcs":[[[1433,1434,1435,1436,1437,1438,-1271]]]},{"type":"Polygon","id":55039,"arcs":[[1439,1440,1441,-1425,-1291,-1294]]},{"type":"Polygon","id":46003,"arcs":[[-1332,1442,1443,1444,1445,-1315]]},{"type":"Polygon","id":46015,"arcs":[[-1446,1446,-1302,-1328,-1316]]},{"type":"Polygon","id":16065,"arcs":[[-1424,1447,-1399,-1098]]},{"type":"Polygon","id":55117,"arcs":[[1448,1449,1450,-1440,-1293,-1264]]},{"type":"Polygon","id":16027,"arcs":[[-1218,1451,1452,-1236,-1379]]},{"type":"Polygon","id":46095,"arcs":[[-1305,1453,1454,-1414,-1358]]},{"type":"Polygon","id":16025,"arcs":[[1455,1456,-1387,-1422]]},{"type":"Polygon","id":50021,"arcs":[[1457,1458,-1268,-1433]]},{"type":"Polygon","id":46033,"arcs":[[1459,1460,1461,-1347,-1222]]},{"type":"Polygon","id":27133,"arcs":[[-1313,1462,1463,1464,-1309]]},{"type":"Polygon","id":27105,"arcs":[[-1338,1465,1466,1467,-1463,-1312]]},{"type":"Polygon","id":27047,"arcs":[[1468,1469,1470,1471,-1326,-1320]]},{"type":"Polygon","id":27099,"arcs":[[-1335,1472,1473,1474,1475,-1469,-1319,-1323]]},{"type":"Polygon","id":27055,"arcs":[[-1397,1476,1477,1478,1479,-1345]]},{"type":"Polygon","id":46035,"arcs":[[1480,1481,1482,-1443,-1331]]},{"type":"Polygon","id":46061,"arcs":[[-1343,1483,1484,-1481,-1330]]},{"type":"Polygon","id":27045,"arcs":[[-1346,-1480,1485,1486,-1473,-1334]]},{"type":"Polygon","id":27063,"arcs":[[-1385,1487,1488,1489,1490,-1466,-1337]]},{"type":"Polygon","id":27043,"arcs":[[-1327,-1472,1491,1492,1493,-1277]]},{"type":"Polygon","id":46099,"arcs":[[1494,1495,1496,1497,-1339,-1324,-1465]]},{"type":"Polygon","id":27091,"arcs":[[-1278,-1494,1498,1499,-1488,-1384]]},{"type":"Polygon","id":46087,"arcs":[[-1340,-1498,1500,1501,-1484,-1342]]},{"type":"Polygon","id":26111,"arcs":[[-1412,1502,1503,1504,-1376]]},{"type":"Polygon","id":26127,"arcs":[[1505,1506,1507,-1351]]},{"type":"Polygon","id":26073,"arcs":[[-1505,1508,1509,1510,-1373]]},{"type":"MultiPolygon","id":23031,"arcs":[[[1511,1512,-1273,-864,-1357]]]},{"type":"Polygon","id":26107,"arcs":[[-1511,1513,1514,-1371]]},{"type":"Polygon","id":26123,"arcs":[[-1515,1515,1516,1517,-1506,-1369]]},{"type":"Polygon","id":36115,"arcs":[[-1459,1518,1519,1520,1521,-1180,-1269]]},{"type":"Polygon","id":16001,"arcs":[[-1259,-1391,1522,-1452,-1217]]},{"type":"Polygon","id":36113,"arcs":[[1523,-1380,-1181,-1522]]},{"type":"Polygon","id":46123,"arcs":[[1524,1525,1526,-1454,-1304]]},{"type":"Polygon","id":33001,"arcs":[[1527,1528,-1241,-1275]]},{"type":"Polygon","id":26157,"arcs":[[1529,1530,1531,1532,1533,-1410,1534]]},{"type":"Polygon","id":55123,"arcs":[[1535,1536,1537,1538,-1477,-1396,-1377,-1282]]},{"type":"MultiPolygon","id":36075,"arcs":[[[-1254,1539]],[[-1300,1540,1541,1542,1543,1544,-1252]]]},{"type":"Polygon","id":46113,"arcs":[[1545,1546,1547,1548,-1460,-1221,-1416]]},{"type":"Polygon","id":26151,"arcs":[[1549,1550,-1531,1551,1552]]},{"type":"Polygon","id":16073,"arcs":[[-1523,-1390,1553,1554,1555,-1237,-1453]]},{"type":"Polygon","id":55021,"arcs":[[1556,1557,1558,-1286,-1429,-1427]]},{"type":"Polygon","id":55111,"arcs":[[-1559,1559,1560,1561,-1536,-1281]]},{"type":"Polygon","id":55027,"arcs":[[1562,1563,1564,1565,-1557,-1426,-1442]]},{"type":"Polygon","id":16019,"arcs":[[-1448,-1423,-1148,1566,1567,1568,-1400]]},{"type":"Polygon","id":16011,"arcs":[[1569,1570,1571,-1417,-1296,-1401,-1569]]},{"type":"Polygon","id":41035,"arcs":[[1572,1573,1574,1575,-1434,-1270,-1248]]},{"type":"Polygon","id":41037,"arcs":[[1576,1577,-1573,-1247,-1404]]},{"type":"Polygon","id":36065,"arcs":[[1578,1579,-1541,-1299,-1395]]},{"type":"Polygon","id":33013,"arcs":[[-1529,1580,1581,1582,1583,-1242]]},{"type":"MultiPolygon","id":41011,"arcs":[[[1584,1585,-1438]]]},{"type":"Polygon","id":33019,"arcs":[[-1584,1586,1587,1588,-1430,-1243]]},{"type":"Polygon","id":33017,"arcs":[[-1513,1589,1590,-1581,-1528,-1274]]},{"type":"Polygon","id":26145,"arcs":[[-1534,1591,1592,1593,-1503,-1411]]},{"type":"Polygon","id":55103,"arcs":[[1594,1595,1596,-1537,-1562]]},{"type":"Polygon","id":55089,"arcs":[[1597,1598,1599,-1450]]},{"type":"Polygon","id":55131,"arcs":[[-1451,-1600,1600,-1563,-1441]]},{"type":"Polygon","id":19189,"arcs":[[1601,1602,1603,-1492,-1471]]},{"type":"Polygon","id":19109,"arcs":[[1604,1605,1606,-1499,-1493,-1604,1607]]},{"type":"Polygon","id":19059,"arcs":[[1608,1609,-1490,1610]]},{"type":"Polygon","id":19063,"arcs":[[1611,-1611,-1489,-1500,-1607]]},{"type":"Polygon","id":19195,"arcs":[[1612,-1602,-1470,-1476,1613]]},{"type":"Polygon","id":19143,"arcs":[[1614,1615,-1467,-1491,-1610]]},{"type":"Polygon","id":56027,"arcs":[[-1462,1616,1617,1618,1619,1620,-1348]]},{"type":"Polygon","id":19131,"arcs":[[1621,1622,-1614,-1475,1623]]},{"type":"Polygon","id":19119,"arcs":[[1624,1625,-1495,-1464,-1468,-1616]]},{"type":"Polygon","id":19089,"arcs":[[1626,1627,-1624,-1474,-1487]]},{"type":"Polygon","id":19005,"arcs":[[-1539,1628,1629,1630,-1478]]},{"type":"Polygon","id":19191,"arcs":[[1631,1632,-1627,-1486,-1479,-1631]]},{"type":"Polygon","id":46083,"arcs":[[-1626,1633,1634,1635,1636,-1496]]},{"type":"Polygon","id":56009,"arcs":[[-1621,1637,1638,1639,1640,-1173,-1001,-1349]]},{"type":"Polygon","id":46023,"arcs":[[-1445,1641,1642,1643,1644,1645,1646,-1447]]},{"type":"Polygon","id":46125,"arcs":[[-1497,-1637,1647,1648,1649,-1501]]},{"type":"Polygon","id":46067,"arcs":[[-1502,-1650,1650,1651,-1643,1652,-1482,-1485]]},{"type":"Polygon","id":56025,"arcs":[[-1174,-1641,1653,-1405,-1360]]},{"type":"Polygon","id":46043,"arcs":[[-1483,-1653,-1642,-1444]]},{"type":"Polygon","id":46053,"arcs":[[-1647,1654,1655,-1525,-1303]]},{"type":"Polygon","id":46047,"arcs":[[-1549,1656,1657,-1617,-1461]]},{"type":"Polygon","id":26121,"arcs":[[-1518,1658,1659,1660,-1507]]},{"type":"Polygon","id":26117,"arcs":[[-1510,1661,1662,1663,-1516,-1514]]},{"type":"Polygon","id":56035,"arcs":[[1664,1665,-1146,-1408]]},{"type":"Polygon","id":26057,"arcs":[[-1504,-1594,1666,1667,-1662,-1509]]},{"type":"Polygon","id":55023,"arcs":[[-1597,1668,1669,-1629,-1538]]},{"type":"Polygon","id":36011,"arcs":[[1670,1671,1672,1673,1674,1675,-1544]]},{"type":"Polygon","id":36091,"arcs":[[-1521,1676,1677,1678,1679,1680,-1381,-1524]]},{"type":"Polygon","id":46007,"arcs":[[1681,1682,-1546,-1415]]},{"type":"Polygon","id":46121,"arcs":[[-1527,1683,-1682,-1455]]},{"type":"Polygon","id":36073,"arcs":[[1684,1685,1686,1687]]},{"type":"Polygon","id":36063,"arcs":[[-1687,1688,1689,1690]]},{"type":"Polygon","id":36055,"arcs":[[1691,1692,1693,1694,-1685,1695]]},{"type":"MultiPolygon","id":36117,"arcs":[[[1696,1697,-1692,1698,-1675]]]},{"type":"Polygon","id":26087,"arcs":[[1699,1700,1701,1702,-1532,-1551]]},{"type":"Polygon","id":56023,"arcs":[[-1666,1703,1704,1705,1706,1707,-1567,-1147]]},{"type":"Polygon","id":50003,"arcs":[[-1432,1708,1709,1710,1711,-1519,-1458]]},{"type":"Polygon","id":55025,"arcs":[[-1566,1712,1713,1714,1715,-1560,-1558]]},{"type":"Polygon","id":26081,"arcs":[[1716,1717,1718,1719,-1659,-1517,-1664]]},{"type":"Polygon","id":36035,"arcs":[[-1681,1720,-1392,-1382]]},{"type":"MultiPolygon","id":33015,"arcs":[[[1721,1722,1723,-1582,-1591]]]},{"type":"Polygon","id":36067,"arcs":[[1724,1725,-1671,-1543]]},{"type":"Polygon","id":50025,"arcs":[[-1589,1726,1727,-1709,-1431]]},{"type":"Polygon","id":19167,"arcs":[[1728,1729,1730,-1634,-1625]]},{"type":"Polygon","id":19141,"arcs":[[1731,1732,-1729,-1615]]},{"type":"Polygon","id":19033,"arcs":[[-1623,1733,1734,1735,-1613]]},{"type":"Polygon","id":19081,"arcs":[[-1736,1736,-1608,-1603]]},{"type":"Polygon","id":19147,"arcs":[[-1606,1737,1738,-1612]]},{"type":"Polygon","id":19041,"arcs":[[-1739,1739,-1732,-1609]]},{"type":"Polygon","id":26049,"arcs":[[-1703,1740,1741,1742,-1592,-1533]]},{"type":"Polygon","id":19037,"arcs":[[-1633,1743,1744,1745,-1628]]},{"type":"Polygon","id":19067,"arcs":[[-1746,1746,-1734,-1622]]},{"type":"Polygon","id":55049,"arcs":[[-1561,-1716,1747,1748,1749,-1595]]},{"type":"Polygon","id":55043,"arcs":[[-1750,1750,1751,1752,1753,-1669,-1596]]},{"type":"Polygon","id":33011,"arcs":[[-1724,1754,1755,1756,1757,-1587,-1583]]},{"type":"Polygon","id":26139,"arcs":[[-1720,1758,1759,-1660]]},{"type":"Polygon","id":16063,"arcs":[[1760,1761,1762,-1456,-1421]]},{"type":"Polygon","id":16047,"arcs":[[-1763,1763,1764,-1388,-1457]]},{"type":"Polygon","id":55055,"arcs":[[1765,1766,1767,-1713,-1565]]},{"type":"Polygon","id":16067,"arcs":[[1768,1769,-1761,-1420]]},{"type":"Polygon","id":55133,"arcs":[[-1601,1770,1771,1772,-1766,-1564]]},{"type":"Polygon","id":55079,"arcs":[[1773,1774,-1771,-1599]]},{"type":"Polygon","id":36053,"arcs":[[-1580,1775,1776,1777,-1725,-1542]]},{"type":"Polygon","id":33005,"arcs":[[-1758,1778,1779,-1727,-1588]]},{"type":"Polygon","id":46135,"arcs":[[1780,1781,1782,1783,-1651,-1649]]},{"type":"Polygon","id":46009,"arcs":[[-1784,1784,-1644,-1652]]},{"type":"Polygon","id":26155,"arcs":[[-1743,1785,1786,1787,-1667,-1593]]},{"type":"Polygon","id":36037,"arcs":[[1788,1789,1790,-1689,-1686,-1695]]},{"type":"Polygon","id":26067,"arcs":[[1791,1792,1793,-1717,-1663]]},{"type":"Polygon","id":26037,"arcs":[[-1788,1794,1795,-1792,-1668]]},{"type":"Polygon","id":16077,"arcs":[[-1572,1796,1797,1798,-1418]]},{"type":"MultiPolygon","id":36029,"arcs":[[[1799]],[[-1791,1800,1801,1802,1803,-1690]]]},{"type":"Polygon","id":46127,"arcs":[[-1731,1804,1805,1806,1807,1808,-1635]]},{"type":"Polygon","id":46027,"arcs":[[-1809,1809,1810,-1781,-1648,-1636]]},{"type":"Polygon","id":19065,"arcs":[[1811,1812,1813,-1744,-1632]]},{"type":"Polygon","id":19043,"arcs":[[-1670,-1754,1814,1815,-1812,-1630]]},{"type":"Polygon","id":36057,"arcs":[[-1680,1816,1817,1818,-1393,-1721]]},{"type":"Polygon","id":36069,"arcs":[[1819,1820,1821,1822,-1693,-1698]]},{"type":"Polygon","id":16005,"arcs":[[1823,1824,1825,-1797,-1571]]},{"type":"Polygon","id":16029,"arcs":[[-1568,-1708,1826,1827,-1824,-1570]]},{"type":"Polygon","id":36099,"arcs":[[1828,1829,1830,-1820,-1697,-1674]]},{"type":"Polygon","id":31165,"arcs":[[1831,1832,1833,-1618,-1658,1834]]},{"type":"Polygon","id":31161,"arcs":[[1835,1836,1837,1838,1839,-1547,1840]]},{"type":"Polygon","id":31045,"arcs":[[1841,-1835,-1657,-1548,-1840]]},{"type":"Polygon","id":31015,"arcs":[[1842,1843,1844,-1655,-1646]]},{"type":"Polygon","id":31103,"arcs":[[-1845,1845,1846,1847,-1526,-1656]]},{"type":"Polygon","id":31031,"arcs":[[-1684,-1848,1848,1849,1850,1851,1852,-1841,-1683]]},{"type":"Polygon","id":41029,"arcs":[[-1576,1853,1854,-1435]]},{"type":"Polygon","id":36051,"arcs":[[-1823,1855,1856,1857,-1789,-1694]]},{"type":"Polygon","id":36083,"arcs":[[-1712,1858,1859,1860,-1677,-1520]]},{"type":"Polygon","id":36093,"arcs":[[1861,1862,-1817,-1679]]},{"type":"Polygon","id":41015,"arcs":[[-1437,1863,1864,1865,-1585]]},{"type":"Polygon","id":16083,"arcs":[[1866,1867,1868,-1554,-1389,-1765]]},{"type":"Polygon","id":19149,"arcs":[[1869,1870,-1805,-1730]]},{"type":"Polygon","id":19021,"arcs":[[1871,1872,1873,-1740]]},{"type":"Polygon","id":19035,"arcs":[[-1874,1874,1875,-1870,-1733]]},{"type":"Polygon","id":19151,"arcs":[[1876,1877,1878,-1872,-1738]]},{"type":"Polygon","id":19197,"arcs":[[1879,1880,1881,1882,-1737]]},{"type":"Polygon","id":19091,"arcs":[[-1883,1883,-1877,-1605]]},{"type":"Polygon","id":19069,"arcs":[[1884,1885,-1880,-1735]]},{"type":"Polygon","id":19023,"arcs":[[1886,1887,1888,-1885,-1747]]},{"type":"Polygon","id":19017,"arcs":[[-1814,1889,-1887,-1745]]},{"type":"Polygon","id":36077,"arcs":[[-1819,1890,1891,1892,-1776,-1579,-1394]]},{"type":"Polygon","id":31089,"arcs":[[1893,1894,1895,1896,1897,1898,-1844]]},{"type":"Polygon","id":26099,"arcs":[[1899,1900,1901,-1701,1902]]},{"type":"Polygon","id":26125,"arcs":[[1903,1904,1905,-1741,-1702,-1902]]},{"type":"MultiPolygon","id":25009,"arcs":[[[1906,1907,1908,-1755,-1723]]]},{"type":"Polygon","id":31107,"arcs":[[-1785,-1783,1909,1910,1911,-1894,-1843,-1645]]},{"type":"Polygon","id":31027,"arcs":[[-1811,1912,1913,1914,-1910,-1782]]},{"type":"Polygon","id":36121,"arcs":[[-1858,1915,1916,-1801,-1790]]},{"type":"Polygon","id":55045,"arcs":[[1917,1918,1919,1920,-1748,-1715]]},{"type":"Polygon","id":16053,"arcs":[[-1770,1921,-1867,-1764,-1762]]},{"type":"Polygon","id":55105,"arcs":[[-1768,1922,1923,1924,-1918,-1714]]},{"type":"Polygon","id":31017,"arcs":[[1925,1926,1927,-1849,-1847]]},{"type":"Polygon","id":55127,"arcs":[[-1773,1928,1929,1930,1931,-1923,-1767]]},{"type":"Polygon","id":55101,"arcs":[[1932,1933,-1929,-1772,-1775]]},{"type":"Polygon","id":36095,"arcs":[[-1863,1934,1935,1936,-1891,-1818]]},{"type":"Polygon","id":36001,"arcs":[[-1861,1937,-1935,-1862,-1678]]},{"type":"Polygon","id":55065,"arcs":[[-1921,1938,1939,-1751,-1749]]},{"type":"Polygon","id":31149,"arcs":[[1940,-1926,-1846,-1899]]},{"type":"Polygon","id":36023,"arcs":[[1941,1942,-1672,-1726,-1778,1943,1944]]},{"type":"Polygon","id":41033,"arcs":[[-1436,-1855,1945,1946,-1864]]},{"type":"Polygon","id":26093,"arcs":[[-1906,1947,1948,1949,-1786,-1742]]},{"type":"Polygon","id":26065,"arcs":[[-1950,1950,1951,-1795,-1787]]},{"type":"Polygon","id":26045,"arcs":[[-1796,-1952,1952,1953,1954,-1793]]},{"type":"Polygon","id":26015,"arcs":[[-1955,1955,1956,1957,-1718,-1794]]},{"type":"Polygon","id":26005,"arcs":[[-1719,-1958,1958,1959,1960,-1759]]},{"type":"Polygon","id":36123,"arcs":[[1961,1962,-1821,-1831]]},{"type":"Polygon","id":31051,"arcs":[[-1808,1963,1964,1965,-1913,-1810]]},{"type":"Polygon","id":25003,"arcs":[[1966,1967,1968,1969,1970,1971,-1859,-1711]]},{"type":"Polygon","id":36017,"arcs":[[-1893,1972,1973,-1944,-1777]]},{"type":"Polygon","id":25011,"arcs":[[1974,-1967,-1710,-1728,-1780,1975]]},{"type":"Polygon","id":25017,"arcs":[[1976,1977,1978,1979,1980,1981,1982,1983,1984,-1756,-1909]]},{"type":"Polygon","id":25027,"arcs":[[-1757,-1985,1985,1986,1987,1988,1989,1990,-1976,-1779]]},{"type":"Polygon","id":16031,"arcs":[[-1799,1991,1992,1993,-1868,-1922,-1769,-1419]]},{"type":"Polygon","id":19061,"arcs":[[1994,1995,1996,1997,-1815,-1753]]},{"type":"Polygon","id":55059,"arcs":[[1998,1999,2000,-1930,-1934]]},{"type":"Polygon","id":19055,"arcs":[[-1998,2001,2002,2003,-1816]]},{"type":"Polygon","id":19187,"arcs":[[-1882,2004,2005,2006,2007,-1878,-1884]]},{"type":"Polygon","id":19019,"arcs":[[-2004,2008,2009,2010,-1813]]},{"type":"Polygon","id":19013,"arcs":[[-2011,2011,2012,2013,-1888,-1890]]},{"type":"Polygon","id":36109,"arcs":[[-1943,2014,2015,2016,-1829,-1673]]},{"type":"Polygon","id":56015,"arcs":[[-1834,2017,2018,2019,2020,-1619]]},{"type":"Polygon","id":56031,"arcs":[[-2021,2021,2022,-1638,-1620]]},{"type":"Polygon","id":16007,"arcs":[[-1707,2023,2024,-1827]]},{"type":"Polygon","id":36101,"arcs":[[-1822,-1963,2025,2026,2027,2028,2029,-1856]]},{"type":"Polygon","id":36013,"arcs":[[2030,2031,2032,2033,-1803]]},{"type":"Polygon","id":19193,"arcs":[[-1876,2034,2035,2036,2037,-1806,-1871]]},{"type":"Polygon","id":19161,"arcs":[[2038,2039,2040,2041,-1873]]},{"type":"Polygon","id":19093,"arcs":[[-2042,2042,-2035,-1875]]},{"type":"Polygon","id":19025,"arcs":[[-2008,2043,2044,-2039,-1879]]},{"type":"Polygon","id":19079,"arcs":[[2045,2046,2047,-2005,-1881]]},{"type":"Polygon","id":19083,"arcs":[[-1886,2048,2049,2050,-2046]]},{"type":"Polygon","id":19075,"arcs":[[-2014,2051,2052,-2049,-1889]]},{"type":"Polygon","id":25015,"arcs":[[-1991,2053,-1968,-1975]]},{"type":"Polygon","id":36097,"arcs":[[-2017,2054,-2026,-1962,-1830]]},{"type":"Polygon","id":36009,"arcs":[[-1917,2055,2056,2057,-2031,-1802]]},{"type":"Polygon","id":31043,"arcs":[[-1807,-2038,2058,-1964]]},{"type":"Polygon","id":36003,"arcs":[[-1857,-2030,2059,2060,-2056,-1916]]},{"type":"Polygon","id":36025,"arcs":[[-1937,2061,2062,2063,2064,2065,-1973,-1892]]},{"type":"Polygon","id":17085,"arcs":[[-1940,2066,2067,2068,-1995,-1752]]},{"type":"Polygon","id":36021,"arcs":[[2069,2070,2071,-1860,-1972]]},{"type":"Polygon","id":17177,"arcs":[[2072,-2067,-1939,-1920,2073,2074]]},{"type":"Polygon","id":17201,"arcs":[[2075,2076,-2074,-1919,-1925]]},{"type":"Polygon","id":16071,"arcs":[[-1826,2077,2078,2079,-1992,-1798]]},{"type":"Polygon","id":17111,"arcs":[[2080,2081,2082,2083,2084,-1931,-2001]]},{"type":"Polygon","id":17007,"arcs":[[-1932,-2085,2085,-2076,-1924]]},{"type":"Polygon","id":17097,"arcs":[[2086,-2081,-2000,2087]]},{"type":"Polygon","id":36039,"arcs":[[2088,-2062,-1936,-1938,-2072]]},{"type":"MultiPolygon","id":25025,"arcs":[[[2089,2090,-1983,2091,-1981]],[[-1979,2092]],[[2093,-1977,-1908]]]},{"type":"Polygon","id":31139,"arcs":[[2094,2095,2096,-1911,-1915]]},{"type":"Polygon","id":31013,"arcs":[[2097,2098,-1832,-1842,-1839]]},{"type":"Polygon","id":31003,"arcs":[[-2097,2099,2100,2101,-1895,-1912]]},{"type":"Polygon","id":26161,"arcs":[[2102,2103,2104,-1948,-1905,2105]]},{"type":"Polygon","id":56007,"arcs":[[-1640,2106,2107,2108,2109,2110,-1406,-1654]]},{"type":"Polygon","id":56001,"arcs":[[-2023,2111,2112,2113,-2107,-1639]]},{"type":"Polygon","id":16041,"arcs":[[-2025,2114,-2078,-1825,-1828]]},{"type":"Polygon","id":26075,"arcs":[[-1949,-2105,2115,2116,2117,-1953,-1951]]},{"type":"Polygon","id":26025,"arcs":[[-2118,2118,2119,2120,-1956,-1954]]},{"type":"Polygon","id":26159,"arcs":[[2121,2122,2123,2124,-1960]]},{"type":"Polygon","id":26077,"arcs":[[-2121,2125,-2122,-1959,-1957]]},{"type":"Polygon","id":36007,"arcs":[[-2066,2126,2127,2128,-1945,-1974]]},{"type":"Polygon","id":36107,"arcs":[[-2129,2129,2130,2131,-2015,-1942]]},{"type":"Polygon","id":19097,"arcs":[[-2069,2132,2133,2134,-1996]]},{"type":"Polygon","id":31179,"arcs":[[-1966,2135,2136,2137,-2095,-1914]]},{"type":"MultiPolygon","id":25021,"arcs":[[[2138,2139]],[[-2091,2140,2141,2142,2143,-1986,-1984]],[[-1982,-2092]]]},{"type":"Polygon","id":25013,"arcs":[[-1990,2144,2145,2146,-1969,-2054]]},{"type":"MultiPolygon","id":25023,"arcs":[[[-2140,2147,2148,2149,2150,-2142,2151]]]},{"type":"Polygon","id":19011,"arcs":[[2152,2153,2154,-2012,-2010]]},{"type":"Polygon","id":19113,"arcs":[[-2003,2155,2156,2157,-2153,-2009]]},{"type":"Polygon","id":19171,"arcs":[[-2155,2158,2159,-2052,-2013]]},{"type":"Polygon","id":19105,"arcs":[[-1997,-2135,2160,2161,-2156,-2002]]},{"type":"Polygon","id":36015,"arcs":[[-2016,-2132,2162,2163,-2027,-2055]]},{"type":"Polygon","id":31173,"arcs":[[-2037,2164,2165,2166,-2136,-1965,-2059]]},{"type":"Polygon","id":56037,"arcs":[[-1407,-2111,2167,2168,2169,2170,-1704,-1665]]},{"type":"Polygon","id":42049,"arcs":[[2171,2172,2173,2174,-2033]]},{"type":"Polygon","id":26021,"arcs":[[2175,2176,2177,2178,-2124]]},{"type":"Polygon","id":19133,"arcs":[[2179,2180,2181,-2165,-2036]]},{"type":"Polygon","id":19127,"arcs":[[-2160,2182,2183,-2050,-2053]]},{"type":"Polygon","id":19027,"arcs":[[-2045,2184,2185,2186,2187,-2040]]},{"type":"Polygon","id":19047,"arcs":[[-2043,-2041,-2188,2188,2189,-2180]]},{"type":"Polygon","id":19015,"arcs":[[-2048,2190,2191,2192,2193,-2006]]},{"type":"Polygon","id":19073,"arcs":[[-2194,2194,2195,-2185,-2044,-2007]]},{"type":"Polygon","id":19169,"arcs":[[-2051,-2184,2196,2197,-2191,-2047]]},{"type":"Polygon","id":17141,"arcs":[[2198,2199,2200,2201,-2075,-2077]]},{"type":"Polygon","id":17015,"arcs":[[-2202,2202,2203,-2133,-2068,-2073]]},{"type":"Polygon","id":36111,"arcs":[[-2071,2204,2205,2206,-2063,-2089]]},{"type":"Polygon","id":17031,"arcs":[[-2087,2207,2208,2209,2210,2211,-2082]]},{"type":"Polygon","id":17037,"arcs":[[2212,2213,2214,2215,-2199,-2086,-2084]]},{"type":"Polygon","id":17089,"arcs":[[-2212,2216,2217,-2213,-2083]]},{"type":"Polygon","id":31075,"arcs":[[2218,2219,2220,-1836,-1853]]},{"type":"MultiPolygon","id":25005,"arcs":[[[2221,2222,2223,2224,2225,-2143,-2151]]]},{"type":"Polygon","id":31091,"arcs":[[2226,2227,2228,-2219,-1852]]},{"type":"Polygon","id":31039,"arcs":[[-2167,2229,2230,2231,2232,-2137]]},{"type":"Polygon","id":31119,"arcs":[[2233,2234,2235,-2100,-2096]]},{"type":"Polygon","id":31167,"arcs":[[-2233,2236,2237,-2234,-2138]]},{"type":"Polygon","id":31171,"arcs":[[2238,2239,2240,-2227,-1851]]},{"type":"Polygon","id":31183,"arcs":[[2241,2242,2243,2244,-1896,-2102]]},{"type":"Polygon","id":31009,"arcs":[[-1850,-1928,2245,2246,2247,-2239]]},{"type":"Polygon","id":31115,"arcs":[[2248,2249,-2246,-1927,-1941,-1898]]},{"type":"Polygon","id":31071,"arcs":[[-2245,2250,2251,-2249,-1897]]},{"type":"Polygon","id":26091,"arcs":[[2252,2253,2254,2255,-2116,-2104]]},{"type":"Polygon","id":36027,"arcs":[[-1971,2256,2257,2258,2259,-2205,-2070]]},{"type":"MultiPolygon","id":25001,"arcs":[[[2260,-2149,2261]]]},{"type":"Polygon","id":26023,"arcs":[[2262,2263,2264,2265,-2120]]},{"type":"Polygon","id":26059,"arcs":[[-2256,2266,2267,2268,-2263,-2119,-2117]]},{"type":"Polygon","id":26149,"arcs":[[-2266,2269,2270,2271,-2126]]},{"type":"Polygon","id":26027,"arcs":[[-2272,2272,2273,-2176,-2123]]},{"type":"Polygon","id":9005,"arcs":[[-2147,2274,2275,2276,-2257,-1970]]},{"type":"Polygon","id":31021,"arcs":[[2277,2278,2279,-2230,-2166,-2182]]},{"type":"Polygon","id":9003,"arcs":[[2280,2281,2282,2283,-2275,-2146]]},{"type":"Polygon","id":9013,"arcs":[[-1989,2284,2285,-2281,-2145]]},{"type":"Polygon","id":19045,"arcs":[[-2204,2286,2287,2288,2289,-2161,-2134]]},{"type":"Polygon","id":9015,"arcs":[[2290,2291,2292,-2285,-1988]]},{"type":"Polygon","id":44007,"arcs":[[-2226,2293,2294,2295,-2291,-1987,-2144]]},{"type":"Polygon","id":36105,"arcs":[[2296,2297,2298,-2064,-2207]]},{"type":"Polygon","id":6093,"arcs":[[-1575,2299,2300,2301,2302,2303,-1946,-1854]]},{"type":"Polygon","id":31069,"arcs":[[-2221,2304,2305,2306,2307,2308,-1837]]},{"type":"Polygon","id":31123,"arcs":[[-2309,2309,2310,2311,-2098,-1838]]},{"type":"Polygon","id":49005,"arcs":[[2312,2313,2314,-2079,-2115]]},{"type":"Polygon","id":31157,"arcs":[[-2312,2315,-2018,-1833,-2099]]},{"type":"Polygon","id":49033,"arcs":[[-1706,2316,2317,2318,2319,-2313,-2024]]},{"type":"Polygon","id":42015,"arcs":[[-2163,-2131,2320,2321,2322,2323,2324]]},{"type":"Polygon","id":42117,"arcs":[[-2164,-2325,2325,2326,-2028]]},{"type":"Polygon","id":49003,"arcs":[[2327,2328,2329,-1993,-2080,-2315]]},{"type":"Polygon","id":32013,"arcs":[[2330,2331,2332,-1402,-1238,-1556,2333]]},{"type":"Polygon","id":32007,"arcs":[[-1994,-2330,2334,2335,2336,2337,-2334,-1555,-1869]]},{"type":"Polygon","id":42083,"arcs":[[-2061,2338,2339,2340,2341,-2057]]},{"type":"Polygon","id":42105,"arcs":[[-2029,-2327,2342,2343,2344,-2339,-2060]]},{"type":"Polygon","id":6015,"arcs":[[-2304,2345,2346,-1865,-1947]]},{"type":"Polygon","id":42127,"arcs":[[-2065,-2299,2347,2348,2349,2350,-2127]]},{"type":"Polygon","id":42115,"arcs":[[2351,2352,-2321,-2130,-2128,-2351]]},{"type":"Polygon","id":42123,"arcs":[[-2342,2353,2354,2355,-2172,-2032,-2058]]},{"type":"Polygon","id":6049,"arcs":[[-1578,2356,2357,2358,-2300,-1574]]},{"type":"Polygon","id":32031,"arcs":[[2359,2360,2361,2362,2363,2364,2365,2366,2367,-2357,-1577,-1403,-2333]]},{"type":"Polygon","id":17043,"arcs":[[2368,2369,-2217,-2211]]},{"type":"Polygon","id":39007,"arcs":[[2370,2371,2372,2373,2374,-2174]]},{"type":"Polygon","id":19031,"arcs":[[-2162,-2290,2375,2376,2377,-2157]]},{"type":"Polygon","id":17195,"arcs":[[-2201,2378,2379,2380,2381,-2287,-2203]]},{"type":"Polygon","id":31011,"arcs":[[-2236,2382,2383,2384,-2242,-2101]]},{"type":"Polygon","id":17103,"arcs":[[-2216,2385,2386,-2379,-2200]]},{"type":"Polygon","id":19085,"arcs":[[-2190,2387,2388,2389,-2278,-2181]]},{"type":"Polygon","id":19095,"arcs":[[2390,2391,2392,2393,-2154]]},{"type":"Polygon","id":19049,"arcs":[[2394,2395,2396,-2195,-2193]]},{"type":"Polygon","id":19165,"arcs":[[2397,2398,2399,-2388,-2189]]},{"type":"Polygon","id":19009,"arcs":[[2400,2401,-2398,-2187]]},{"type":"Polygon","id":19157,"arcs":[[-2394,2402,2403,2404,-2159]]},{"type":"Polygon","id":19153,"arcs":[[-2198,2405,2406,2407,-2395,-2192]]},{"type":"Polygon","id":19099,"arcs":[[-2183,-2405,2408,2409,-2406,-2197]]},{"type":"Polygon","id":19077,"arcs":[[-2397,2410,-2401,-2186,-2196]]},{"type":"Polygon","id":19103,"arcs":[[-2378,2411,2412,2413,-2391,-2158]]},{"type":"Polygon","id":39085,"arcs":[[2414,2415,2416,-2374]]},{"type":"Polygon","id":42039,"arcs":[[-2356,2417,2418,2419,-2371,-2173]]},{"type":"Polygon","id":17161,"arcs":[[2420,2421,2422,2423,2424,-2288,-2382]]},{"type":"Polygon","id":44001,"arcs":[[2425,-2294,-2225]]},{"type":"Polygon","id":19163,"arcs":[[-2425,2426,-2376,-2289]]},{"type":"Polygon","id":44003,"arcs":[[2427,2428,2429,-2292,-2296]]},{"type":"Polygon","id":18039,"arcs":[[2430,2431,2432,2433,2434,-2273,-2271]]},{"type":"Polygon","id":18141,"arcs":[[2435,2436,2437,-2177,-2274,-2435]]},{"type":"Polygon","id":18091,"arcs":[[2438,2439,2440,-2178,-2438]]},{"type":"Polygon","id":18151,"arcs":[[2441,2442,-2264,-2269,2443]]},{"type":"Polygon","id":18087,"arcs":[[2444,-2431,-2270,-2265,-2443]]},{"type":"Polygon","id":31037,"arcs":[[-2232,2445,2446,2447,-2237]]},{"type":"Polygon","id":31141,"arcs":[[-2448,2448,2449,2450,2451,-2383,-2235,-2238]]},{"type":"Polygon","id":31053,"arcs":[[-2280,2452,2453,2454,2455,-2446,-2231]]},{"type":"Polygon","id":31117,"arcs":[[-2241,2456,2457,2458,2459,-2228]]},{"type":"Polygon","id":31005,"arcs":[[-2460,2460,-2305,-2220,-2229]]},{"type":"Polygon","id":31077,"arcs":[[2461,2462,2463,2464,-2243,-2385]]},{"type":"Polygon","id":31041,"arcs":[[-2252,2465,2466,2467,2468,2469,2470,-2247,-2250]]},{"type":"Polygon","id":31113,"arcs":[[-2248,-2471,2471,-2457,-2240]]},{"type":"Polygon","id":31175,"arcs":[[-2244,-2465,2472,-2466,-2251]]},{"type":"MultiPolygon","id":39095,"arcs":[[[2473,2474,2475,2476,-2254,2477,2478,2479,2480,2481,2482]]]},{"type":"Polygon","id":17197,"arcs":[[-2210,2483,2484,2485,2486,-2369]]},{"type":"MultiPolygon","id":39123,"arcs":[[[2487,2488,2489,2490,2491,-2474]]]},{"type":"Polygon","id":17093,"arcs":[[-2487,2492,2493,-2214,-2218,-2370]]},{"type":"Polygon","id":39051,"arcs":[[2494,2495,-2267,-2255,-2477]]},{"type":"Polygon","id":39055,"arcs":[[2496,2497,2498,-2415,-2373]]},{"type":"MultiPolygon","id":9011,"arcs":[[[-2430,2499,2500,2501,-2282,-2286,-2293]]]},{"type":"Polygon","id":18089,"arcs":[[2502,2503,2504,2505,-2484,-2209,2506]]},{"type":"Polygon","id":18127,"arcs":[[2507,-2503,2508,-2440]]},{"type":"Polygon","id":39171,"arcs":[[2509,2510,2511,-2444,-2268,-2496]]},{"type":"Polygon","id":31007,"arcs":[[-2311,2512,2513,2514,-2019,-2316]]},{"type":"Polygon","id":31177,"arcs":[[-2390,2515,2516,-2453,-2279]]},{"type":"MultiPolygon","id":44005,"arcs":[[[2517]],[[2518,-2223]]]},{"type":"MultiPolygon","id":9001,"arcs":[[[2519,2520,2521,2522,-2258,-2277]]]},{"type":"Polygon","id":56021,"arcs":[[-2020,-2515,2523,2524,2525,-2112,-2022]]},{"type":"MultiPolygon","id":44009,"arcs":[[[-2500,-2429,2526]]]},{"type":"Polygon","id":42131,"arcs":[[-2353,2527,2528,2529,-2322]]},{"type":"MultiPolygon","id":9007,"arcs":[[[2530,2531]],[[-2502,2532,2533,-2283]]]},{"type":"Polygon","id":9009,"arcs":[[-2534,2534,-2531,2535,-2520,-2276,-2284]]},{"type":"Polygon","id":42069,"arcs":[[-2350,2536,2537,-2528,-2352]]},{"type":"Polygon","id":36071,"arcs":[[-2260,2538,2539,2540,2541,2542,-2297,-2206]]},{"type":"Polygon","id":17099,"arcs":[[-2494,2543,2544,2545,2546,2547,2548,-2386,-2215]]},{"type":"Polygon","id":39035,"arcs":[[-2499,2549,2550,2551,2552,2553,-2416]]},{"type":"Polygon","id":42047,"arcs":[[2554,2555,2556,2557,-2341]]},{"type":"Polygon","id":42053,"arcs":[[-2558,2558,2559,2560,-2354]]},{"type":"Polygon","id":42121,"arcs":[[2561,2562,2563,-2418,-2355,-2561]]},{"type":"MultiPolygon","id":39043,"arcs":[[[2564,2565,2566,2567]]]},{"type":"Polygon","id":39173,"arcs":[[-2492,2568,2569,2570,2571,-2475]]},{"type":"Polygon","id":42023,"arcs":[[-2345,2572,2573,-2555,-2340]]},{"type":"Polygon","id":42103,"arcs":[[-2543,2574,2575,-2348,-2298]]},{"type":"Polygon","id":19139,"arcs":[[-2377,-2427,-2424,2576,-2412]]},{"type":"Polygon","id":42081,"arcs":[[2577,2578,2579,2580,2581,2582,-2343,-2326,-2324]]},{"type":"Polygon","id":42113,"arcs":[[-2578,-2323,-2530,2583,2584]]},{"type":"Polygon","id":17011,"arcs":[[-2549,2585,2586,2587,2588,-2380,-2387]]},{"type":"Polygon","id":17073,"arcs":[[-2589,2589,2590,2591,-2421,-2381]]},{"type":"Polygon","id":56041,"arcs":[[-2171,2592,-2317,-1705]]},{"type":"Polygon","id":18033,"arcs":[[2593,2594,2595,-2442,-2512]]},{"type":"Polygon","id":18113,"arcs":[[-2596,2596,2597,2598,-2432,-2445]]},{"type":"Polygon","id":31125,"arcs":[[2599,2600,-2462,-2384,-2452]]},{"type":"Polygon","id":36079,"arcs":[[2601,2602,-2539,-2259,-2523,2603]]},{"type":"MultiPolygon","id":25007,"arcs":[[[2604]]]},{"type":"Polygon","id":39093,"arcs":[[-2553,2605,2606,2607,-2565,2608]]},{"type":"Polygon","id":19183,"arcs":[[2609,2610,2611,2612,-2392,-2414]]},{"type":"Polygon","id":19181,"arcs":[[2613,2614,2615,2616,-2408]]},{"type":"Polygon","id":19107,"arcs":[[-2613,2617,2618,2619,-2403,-2393]]},{"type":"Polygon","id":19121,"arcs":[[2620,2621,2622,-2396,-2617]]},{"type":"Polygon","id":19123,"arcs":[[-2620,2623,2624,2625,-2409,-2404]]},{"type":"Polygon","id":19125,"arcs":[[-2626,2626,2627,-2614,-2407,-2410]]},{"type":"Polygon","id":19155,"arcs":[[-2400,2628,2629,2630,2631,2632,-2516,-2389]]},{"type":"Polygon","id":19029,"arcs":[[-2402,2633,2634,2635,-2629,-2399]]},{"type":"Polygon","id":19001,"arcs":[[-2411,-2623,2636,2637,-2634]]},{"type":"Polygon","id":39155,"arcs":[[-2420,2638,2639,2640,-2497,-2372]]},{"type":"MultiPolygon","id":39143,"arcs":[[[-2489,2641]],[[2642,-2567,2643,2644,-2569,-2491]]]},{"type":"Polygon","id":42085,"arcs":[[-2564,2645,2646,2647,-2639,-2419]]},{"type":"Polygon","id":39069,"arcs":[[-2476,-2572,2648,2649,-2510,-2495]]},{"type":"Polygon","id":18099,"arcs":[[-2434,2650,2651,2652,-2436]]},{"type":"Polygon","id":42035,"arcs":[[-2583,2653,2654,2655,-2573,-2344]]},{"type":"MultiPolygon","id":6023,"arcs":[[[-2303,2656,2657,2658,-2346]]]},{"type":"Polygon","id":17063,"arcs":[[2659,2660,-2544,-2493,-2486]]},{"type":"Polygon","id":31023,"arcs":[[-2456,2661,2662,2663,2664,-2449,-2447]]},{"type":"Polygon","id":31155,"arcs":[[2665,2666,2667,2668,-2662,-2455]]},{"type":"Polygon","id":18085,"arcs":[[-2599,2669,2670,2671,-2651,-2433]]},{"type":"Polygon","id":31033,"arcs":[[-2308,2672,2673,2674,2675,-2513,-2310]]},{"type":"Polygon","id":42031,"arcs":[[2676,2677,2678,-2562,-2560]]},{"type":"Polygon","id":49057,"arcs":[[-2314,-2320,2679,2680,-2328]]},{"type":"Polygon","id":18149,"arcs":[[-2653,2681,2682,-2439,-2437]]},{"type":"Polygon","id":42079,"arcs":[[2683,2684,2685,2686,-2584,-2529,-2538]]},{"type":"Polygon","id":39039,"arcs":[[2687,2688,2689,-2594,-2511,-2650]]},{"type":"Polygon","id":19115,"arcs":[[-2577,-2423,2690,2691,2692,-2610,-2413]]},{"type":"Polygon","id":31101,"arcs":[[2693,2694,2695,-2306,-2461,-2459]]},{"type":"Polygon","id":31111,"arcs":[[-2472,-2470,2696,2697,2698,2699,-2694,-2458]]},{"type":"MultiPolygon","id":25019,"arcs":[[[2700]]]},{"type":"Polygon","id":31143,"arcs":[[2701,2702,2703,-2450,-2665]]},{"type":"Polygon","id":31105,"arcs":[[-2676,2704,2705,-2524,-2514]]},{"type":"Polygon","id":31121,"arcs":[[-2704,2706,2707,2708,-2600,-2451]]},{"type":"Polygon","id":31093,"arcs":[[-2601,-2709,2709,2710,2711,-2463]]},{"type":"Polygon","id":31163,"arcs":[[-2464,-2712,2712,-2467,-2473]]},{"type":"Polygon","id":31055,"arcs":[[-2517,-2633,2713,-2666,-2454]]},{"type":"Polygon","id":49029,"arcs":[[2714,2715,2716,-2680,-2319]]},{"type":"Polygon","id":42065,"arcs":[[2717,2718,2719,-2677,-2559,-2557]]},{"type":"Polygon","id":6105,"arcs":[[2720,2721,2722,-2657,-2302]]},{"type":"MultiPolygon","id":36119,"arcs":[[[2723,2724,2725,-2604,-2522,2726]]]},{"type":"Polygon","id":34037,"arcs":[[2727,2728,2729,2730,-2575,-2542]]},{"type":"Polygon","id":39153,"arcs":[[2731,2732,2733,2734,-2551]]},{"type":"Polygon","id":39133,"arcs":[[-2641,2735,2736,-2732,-2550,-2498]]},{"type":"Polygon","id":17131,"arcs":[[-2592,2737,2738,2739,2740,-2691,-2422]]},{"type":"Polygon","id":17155,"arcs":[[-2548,2741,-2586]]},{"type":"Polygon","id":36087,"arcs":[[2742,2743,-2540,-2603,2744]]},{"type":"Polygon","id":42037,"arcs":[[2745,2746,2747,-2579,-2585,-2687]]},{"type":"Polygon","id":17091,"arcs":[[-2506,2748,2749,2750,2751,-2660,-2485]]},{"type":"Polygon","id":18183,"arcs":[[2752,2753,2754,-2670,-2598]]},{"type":"MultiPolygon","id":36103,"arcs":[[[2755,2756]],[[2757,2758]]]},{"type":"Polygon","id":39077,"arcs":[[-2608,2759,2760,2761,2762,-2644,-2566]]},{"type":"Polygon","id":18073,"arcs":[[-2683,2763,2764,2765,2766,-2504,-2508]]},{"type":"Polygon","id":39103,"arcs":[[-2735,2767,2768,-2606,-2552]]},{"type":"Polygon","id":18003,"arcs":[[2769,2770,2771,2772,2773,-2753,-2597,-2595,-2690]]},{"type":"Polygon","id":39147,"arcs":[[-2763,2774,2775,2776,-2570,-2645]]},{"type":"Polygon","id":42033,"arcs":[[2777,2778,2779,2780,-2718,-2556,-2574,-2656]]},{"type":"Polygon","id":49043,"arcs":[[-2593,-2170,2781,2782,2783,2784,-2715,-2318]]},{"type":"Polygon","id":42027,"arcs":[[2785,2786,2787,2788,-2778,-2655]]},{"type":"Polygon","id":42089,"arcs":[[-2731,2789,2790,2791,-2684,-2537,-2349,-2576]]},{"type":"Polygon","id":39125,"arcs":[[2792,2793,-2770,-2689]]},{"type":"Polygon","id":17175,"arcs":[[-2588,2794,2795,2796,-2590]]},{"type":"Polygon","id":31049,"arcs":[[-2696,2797,2798,-2673,-2307]]},{"type":"Polygon","id":18111,"arcs":[[2799,2800,-2749,-2505,-2767]]},{"type":"Polygon","id":34031,"arcs":[[2801,2802,-2728,-2541,-2744,2803]]},{"type":"Polygon","id":31153,"arcs":[[-2632,2804,2805,-2667,-2714]]},{"type":"Polygon","id":6035,"arcs":[[-2368,2806,2807,2808,-2358]]},{"type":"Polygon","id":6089,"arcs":[[-2359,-2809,2809,2810,-2721,-2301]]},{"type":"Polygon","id":42097,"arcs":[[2811,-2747,2812,2813,2814,2815,2816,-2581]]},{"type":"Polygon","id":18049,"arcs":[[2817,2818,2819,2820,-2652,-2672]]},{"type":"Polygon","id":31081,"arcs":[[2821,2822,2823,2824,-2707,-2703]]},{"type":"Polygon","id":18131,"arcs":[[-2821,2825,2826,-2764,-2682]]},{"type":"Polygon","id":42093,"arcs":[[-2748,-2812,-2580]]},{"type":"Polygon","id":42019,"arcs":[[-2679,2827,2828,2829,2830,-2646,-2563]]},{"type":"Polygon","id":42005,"arcs":[[-2720,2831,2832,2833,-2828,-2678]]},{"type":"Polygon","id":39063,"arcs":[[-2777,2834,2835,2836,2837,-2571]]},{"type":"Polygon","id":39137,"arcs":[[-2838,2838,2839,-2793,-2688,-2649]]},{"type":"Polygon","id":19101,"arcs":[[-2612,2840,2841,2842,-2618]]},{"type":"Polygon","id":19087,"arcs":[[-2693,2843,2844,2845,-2841,-2611]]},{"type":"Polygon","id":19179,"arcs":[[-2843,2846,2847,-2624,-2619]]},{"type":"Polygon","id":19039,"arcs":[[2848,2849,2850,-2621,-2616]]},{"type":"Polygon","id":19117,"arcs":[[-2628,2851,2852,-2849,-2615]]},{"type":"Polygon","id":19135,"arcs":[[-2848,2853,-2852,-2627,-2625]]},{"type":"Polygon","id":19129,"arcs":[[2854,2855,2856,-2805,-2631]]},{"type":"Polygon","id":19137,"arcs":[[-2636,2857,2858,-2855,-2630]]},{"type":"Polygon","id":19003,"arcs":[[-2638,2859,2860,-2858,-2635]]},{"type":"Polygon","id":19175,"arcs":[[-2622,-2851,2861,-2860,-2637]]},{"type":"Polygon","id":49011,"arcs":[[-2717,2862,2863,-2681]]},{"type":"Polygon","id":17095,"arcs":[[-2797,2864,2865,2866,-2738,-2591]]},{"type":"Polygon","id":42119,"arcs":[[2867,2868,-2786,-2654,-2582,-2817]]},{"type":"Polygon","id":17123,"arcs":[[-2742,-2547,2869,2870,-2795,-2587]]},{"type":"Polygon","id":34003,"arcs":[[2871,2872,-2804,-2743,2873]]},{"type":"Polygon","id":39099,"arcs":[[-2648,2874,2875,2876,-2736,-2640]]},{"type":"Polygon","id":42025,"arcs":[[-2792,2877,2878,2879,-2685]]},{"type":"Polygon","id":42073,"arcs":[[-2831,2880,2881,-2875,-2647]]},{"type":"Polygon","id":17105,"arcs":[[-2752,2882,2883,2884,-2545,-2661]]},{"type":"Polygon","id":34041,"arcs":[[2885,2886,2887,2888,-2790,-2730]]},{"type":"Polygon","id":34027,"arcs":[[2889,2890,2891,2892,-2886,-2729,-2803]]},{"type":"Polygon","id":49045,"arcs":[[2893,2894,2895,2896,-2335,-2329,-2864]]},{"type":"Polygon","id":19057,"arcs":[[-2741,2897,2898,-2844,-2692]]},{"type":"Polygon","id":17071,"arcs":[[2899,2900,2901,2902,-2898,-2740]]},{"type":"Polygon","id":17187,"arcs":[[-2867,2903,2904,-2900,-2739]]},{"type":"Polygon","id":39005,"arcs":[[-2769,2905,2906,2907,2908,-2760,-2607]]},{"type":"Polygon","id":31025,"arcs":[[-2857,2909,2910,2911,-2668,-2806]]},{"type":"Polygon","id":31185,"arcs":[[2912,2913,2914,-2822,-2702]]},{"type":"Polygon","id":31079,"arcs":[[-2708,-2825,2915,2916,-2710]]},{"type":"Polygon","id":31047,"arcs":[[2917,2918,2919,2920,-2697,-2469]]},{"type":"Polygon","id":31019,"arcs":[[-2711,-2917,2921,2922,2923,-2918,-2468,-2713]]},{"type":"Polygon","id":31159,"arcs":[[2924,2925,-2913,-2664]]},{"type":"Polygon","id":31109,"arcs":[[-2912,2926,2927,2928,-2925,-2663,-2669]]},{"type":"Polygon","id":18169,"arcs":[[-2755,2929,2930,2931,-2818,-2671]]},{"type":"Polygon","id":17075,"arcs":[[-2801,2932,2933,2934,-2750]]},{"type":"Polygon","id":18069,"arcs":[[-2774,2935,2936,-2930,-2754]]},{"type":"MultiPolygon","id":8123,"arcs":[[[2937]],[[2938,2939,2940,2941,2942,-2525,-2706,2943]]]},{"type":"Polygon","id":31135,"arcs":[[-2700,2944,2945,2946,2947,-2798,-2695]]},{"type":"Polygon","id":8107,"arcs":[[2948,2949,2950,2951,2952,-2109,2953]]},{"type":"Polygon","id":8057,"arcs"
gitextract_5rbjmnml/
├── .eslintrc.json
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src/
│ ├── canvas.ts
│ ├── core.ts
│ ├── fillers/
│ │ ├── dashed-filler.ts
│ │ ├── dot-filler.ts
│ │ ├── filler-interface.ts
│ │ ├── filler.ts
│ │ ├── hachure-filler.ts
│ │ ├── hatch-filler.ts
│ │ ├── scan-line-hachure.ts
│ │ ├── zigzag-filler.ts
│ │ └── zigzag-line-filler.ts
│ ├── generator.ts
│ ├── geometry.ts
│ ├── math.ts
│ ├── renderer.ts
│ ├── rough.ts
│ └── svg.ts
├── tsconfig.json
└── visual-tests/
├── canvas/
│ ├── arc.html
│ ├── arc2.html
│ ├── curve-seed.html
│ ├── curve.html
│ ├── curve2.html
│ ├── curve3.html
│ ├── curve4.html
│ ├── dashed/
│ │ ├── arc.html
│ │ ├── curve.html
│ │ ├── ellipse.html
│ │ ├── line.html
│ │ ├── linearpath.html
│ │ ├── path-with-transform.html
│ │ ├── path.html
│ │ ├── polygon.html
│ │ └── rectangle.html
│ ├── ellipse.html
│ ├── ellipse2.html
│ ├── ellipse3.html
│ ├── line.html
│ ├── linearpath.html
│ ├── map.html
│ ├── path-with-transform.html
│ ├── path.html
│ ├── path2.html
│ ├── path3.html
│ ├── path4.html
│ ├── path5.html
│ ├── path6.html
│ ├── path7.html
│ ├── poly-seed.html
│ ├── polygon.html
│ ├── polygon2.html
│ ├── rectangle.html
│ ├── singlestroke/
│ │ ├── arc.html
│ │ ├── curve.html
│ │ ├── ellipse.html
│ │ ├── line.html
│ │ ├── path.html
│ │ ├── polygon.html
│ │ └── rectangle.html
│ └── us.json
└── svg/
├── dashed/
│ ├── ellipse.html
│ ├── line.html
│ ├── polygon.html
│ └── rectangle.html
├── ellipse.html
├── line.html
├── polygon.html
├── rectangle.html
└── rectangle2.html
SYMBOL INDEX (128 symbols across 17 files)
FILE: src/canvas.ts
class RoughCanvas (line 5) | class RoughCanvas {
method constructor (line 10) | constructor(canvas: HTMLCanvasElement, config?: Config) {
method draw (line 16) | draw(drawable: Drawable): void {
method fillSketch (line 51) | private fillSketch(ctx: CanvasRenderingContext2D, drawing: OpSet, o: R...
method _drawToContext (line 69) | private _drawToContext(ctx: CanvasRenderingContext2D, drawing: OpSet, ...
method generator (line 92) | get generator(): RoughGenerator {
method getDefaultOptions (line 96) | getDefaultOptions(): ResolvedOptions {
method line (line 100) | line(x1: number, y1: number, x2: number, y2: number, options?: Options...
method rectangle (line 106) | rectangle(x: number, y: number, width: number, height: number, options...
method ellipse (line 112) | ellipse(x: number, y: number, width: number, height: number, options?:...
method circle (line 118) | circle(x: number, y: number, diameter: number, options?: Options): Dra...
method linearPath (line 124) | linearPath(points: Point[], options?: Options): Drawable {
method polygon (line 130) | polygon(points: Point[], options?: Options): Drawable {
method arc (line 136) | arc(x: number, y: number, width: number, height: number, start: number...
method curve (line 142) | curve(points: Point[] | Point[][], options?: Options): Drawable {
method path (line 148) | path(d: string, options?: Options): Drawable {
FILE: src/core.ts
constant SVGNS (line 4) | const SVGNS = 'http://www.w3.org/2000/svg';
type Config (line 6) | interface Config {
type DrawingSurface (line 10) | interface DrawingSurface {
type Options (line 15) | interface Options {
type ResolvedOptions (line 45) | interface ResolvedOptions extends Options {
type OpType (line 69) | type OpType = 'move' | 'bcurveTo' | 'lineTo';
type OpSetType (line 70) | type OpSetType = 'path' | 'fillPath' | 'fillSketch';
type Op (line 72) | interface Op {
type OpSet (line 77) | interface OpSet {
type Drawable (line 84) | interface Drawable {
type PathInfo (line 90) | interface PathInfo {
FILE: src/fillers/dashed-filler.ts
class DashedFiller (line 6) | class DashedFiller implements PatternFiller {
method constructor (line 9) | constructor(helper: RenderHelper) {
method fillPolygons (line 13) | fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
method dashedLine (line 18) | private dashedLine(lines: Line[], o: ResolvedOptions): Op[] {
FILE: src/fillers/dot-filler.ts
class DotFiller (line 6) | class DotFiller implements PatternFiller {
method constructor (line 9) | constructor(helper: RenderHelper) {
method fillPolygons (line 13) | fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
method dotsOnLines (line 19) | private dotsOnLines(lines: Line[], o: ResolvedOptions): OpSet {
FILE: src/fillers/filler-interface.ts
type PatternFiller (line 4) | interface PatternFiller {
type RenderHelper (line 8) | interface RenderHelper {
FILE: src/fillers/filler.ts
function getFiller (line 12) | function getFiller(o: ResolvedOptions, helper: RenderHelper): PatternFil...
FILE: src/fillers/hachure-filler.ts
class HachureFiller (line 6) | class HachureFiller implements PatternFiller {
method constructor (line 9) | constructor(helper: RenderHelper) {
method fillPolygons (line 13) | fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
method _fillPolygons (line 17) | protected _fillPolygons(polygonList: Point[][], o: ResolvedOptions): O...
method renderLines (line 23) | protected renderLines(lines: Line[], o: ResolvedOptions): Op[] {
FILE: src/fillers/hatch-filler.ts
class HatchFiller (line 5) | class HatchFiller extends HachureFiller {
method fillPolygons (line 6) | fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
FILE: src/fillers/scan-line-hachure.ts
function polygonHachureLines (line 5) | function polygonHachureLines(polygonList: Point[][], o: ResolvedOptions)...
FILE: src/fillers/zigzag-filler.ts
class ZigZagFiller (line 6) | class ZigZagFiller extends HachureFiller {
method fillPolygons (line 7) | fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
FILE: src/fillers/zigzag-line-filler.ts
class ZigZagLineFiller (line 6) | class ZigZagLineFiller implements PatternFiller {
method constructor (line 9) | constructor(helper: RenderHelper) {
method fillPolygons (line 13) | fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet {
method zigzagLines (line 21) | private zigzagLines(lines: Line[], zo: number, o: ResolvedOptions): Op...
FILE: src/generator.ts
constant NOS (line 9) | const NOS = 'none';
class RoughGenerator (line 11) | class RoughGenerator {
method constructor (line 37) | constructor(config?: Config) {
method newSeed (line 44) | static newSeed(): number {
method _o (line 48) | private _o(options?: Options): ResolvedOptions {
method _d (line 52) | private _d(shape: string, sets: OpSet[], options: ResolvedOptions): Dr...
method line (line 56) | line(x1: number, y1: number, x2: number, y2: number, options?: Options...
method rectangle (line 61) | rectangle(x: number, y: number, width: number, height: number, options...
method ellipse (line 79) | ellipse(x: number, y: number, width: number, height: number, options?:...
method circle (line 99) | circle(x: number, y: number, diameter: number, options?: Options): Dra...
method linearPath (line 105) | linearPath(points: Point[], options?: Options): Drawable {
method arc (line 110) | arc(x: number, y: number, width: number, height: number, start: number...
method curve (line 131) | curve(points: Point[] | Point[][], options?: Options): Drawable {
method polygon (line 174) | polygon(points: Point[], options?: Options): Drawable {
method path (line 191) | path(d: string, options?: Options): Drawable {
method opsToPath (line 234) | opsToPath(drawing: OpSet, fixedDecimals?: number): string {
method toPaths (line 253) | toPaths(drawable: Drawable): PathInfo[] {
method fillSketch (line 287) | private fillSketch(drawing: OpSet, o: ResolvedOptions): PathInfo {
method _mergedShape (line 300) | private _mergedShape(input: Op[]): Op[] {
FILE: src/geometry.ts
type Point (line 1) | type Point = [number, number];
type Line (line 2) | type Line = [Point, Point];
type Rectangle (line 4) | interface Rectangle {
function lineLength (line 11) | function lineLength(line: Line): number {
FILE: src/math.ts
function randomSeed (line 1) | function randomSeed(): number {
class Random (line 5) | class Random {
method constructor (line 8) | constructor(seed: number) {
method next (line 12) | next(): number {
FILE: src/renderer.ts
type EllipseParams (line 8) | interface EllipseParams {
function line (line 21) | function line(x1: number, y1: number, x2: number, y2: number, o: Resolve...
function linearPath (line 25) | function linearPath(points: Point[], close: boolean, o: ResolvedOptions)...
function polygon (line 42) | function polygon(points: Point[], o: ResolvedOptions): OpSet {
function rectangle (line 46) | function rectangle(x: number, y: number, width: number, height: number, ...
function curve (line 56) | function curve(inputPoints: Point[] | Point[][], o: ResolvedOptions): Op...
type EllipseResult (line 87) | interface EllipseResult {
function ellipse (line 92) | function ellipse(x: number, y: number, width: number, height: number, o:...
function generateEllipseParams (line 97) | function generateEllipseParams(width: number, height: number, o: Resolve...
function ellipseWithParams (line 109) | function ellipseWithParams(x: number, y: number, o: ResolvedOptions, ell...
function arc (line 123) | function arc(x: number, y: number, width: number, height: number, start:...
function svgPath (line 163) | function svgPath(path: string, o: ResolvedOptions): OpSet {
function solidFillPolygon (line 196) | function solidFillPolygon(polygonList: Point[][], o: ResolvedOptions): O...
function patternFillPolygons (line 213) | function patternFillPolygons(polygonList: Point[][], o: ResolvedOptions)...
function patternFillArc (line 217) | function patternFillArc(x: number, y: number, width: number, height: num...
function randOffset (line 244) | function randOffset(x: number, o: ResolvedOptions): number {
function randOffsetWithRange (line 248) | function randOffsetWithRange(min: number, max: number, o: ResolvedOption...
function doubleLineFillOps (line 252) | function doubleLineFillOps(x1: number, y1: number, x2: number, y2: numbe...
function cloneOptionsAlterSeed (line 258) | function cloneOptionsAlterSeed(ops: ResolvedOptions): ResolvedOptions {
function random (line 267) | function random(ops: ResolvedOptions): number {
function _offset (line 274) | function _offset(min: number, max: number, ops: ResolvedOptions, roughne...
function _offsetOpt (line 278) | function _offsetOpt(x: number, ops: ResolvedOptions, roughnessGain = 1):...
function _doubleLine (line 282) | function _doubleLine(x1: number, y1: number, x2: number, y2: number, o: ...
function _line (line 292) | function _line(x1: number, y1: number, x2: number, y2: number, o: Resolv...
function _curveWithOffset (line 363) | function _curveWithOffset(points: Point[], offset: number, o: ResolvedOp...
function _curve (line 391) | function _curve(points: Point[], closePoint: Point | null, o: ResolvedOp...
function _computeEllipsePoints (line 426) | function _computeEllipsePoints(increment: number, cx: number, cy: number...
function _arc (line 486) | function _arc(increment: number, cx: number, cy: number, rx: number, ry:...
function _bezierTo (line 510) | function _bezierTo(x1: number, y1: number, x2: number, y2: number, x: nu...
FILE: src/rough.ts
method canvas (line 7) | canvas(canvas: HTMLCanvasElement, config?: Config): RoughCanvas {
method svg (line 11) | svg(svg: SVGSVGElement, config?: Config): RoughSVG {
method generator (line 15) | generator(config?: Config): RoughGenerator {
method newSeed (line 19) | newSeed(): number {
FILE: src/svg.ts
class RoughSVG (line 5) | class RoughSVG {
method constructor (line 9) | constructor(svg: SVGSVGElement, config?: Config) {
method draw (line 14) | draw(drawable: Drawable): SVGGElement {
method fillSketch (line 60) | private fillSketch(doc: Document, drawing: OpSet, o: ResolvedOptions):...
method generator (line 79) | get generator(): RoughGenerator {
method getDefaultOptions (line 83) | getDefaultOptions(): ResolvedOptions {
method opsToPath (line 87) | opsToPath(drawing: OpSet, fixedDecimalPlaceDigits?: number): string {
method line (line 91) | line(x1: number, y1: number, x2: number, y2: number, options?: Options...
method rectangle (line 96) | rectangle(x: number, y: number, width: number, height: number, options...
method ellipse (line 101) | ellipse(x: number, y: number, width: number, height: number, options?:...
method circle (line 106) | circle(x: number, y: number, diameter: number, options?: Options): SVG...
method linearPath (line 111) | linearPath(points: Point[], options?: Options): SVGGElement {
method polygon (line 116) | polygon(points: Point[], options?: Options): SVGGElement {
method arc (line 121) | arc(x: number, y: number, width: number, height: number, start: number...
method curve (line 126) | curve(points: Point[] | Point[][], options?: Options): SVGGElement {
method path (line 131) | path(d: string, options?: Options): SVGGElement {
Condensed preview — 78 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (826K chars).
[
{
"path": ".eslintrc.json",
"chars": 797,
"preview": "{\n \"extends\": [\n \"eslint:recommended\",\n \"plugin:@typescript-eslint/eslint-recommended\",\n \"plugin:@typescript-e"
},
{
"path": ".github/FUNDING.yml",
"chars": 38,
"preview": "github: pshihn\nopen_collective: rough\n"
},
{
"path": ".gitignore",
"chars": 57,
"preview": ".cache\n.DS_Store\nnode_modules\nz\nbin\ndist\nbundled\nbug.html"
},
{
"path": ".npmignore",
"chars": 88,
"preview": ".cache\n.DS_Store\nz\nnode_modules\nsrc\ntslint.json\nrollup.config.js\n.gitignore\nvisual-tests"
},
{
"path": "CHANGELOG.md",
"chars": 2268,
"preview": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\n# [4.5.0] - 2021-05-09\n* Better algo"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2019 Preet Shihn\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 6057,
"preview": "# Rough.js\n\n<b>Rough.js</b> is a small (\\<9 kB) graphics library that lets you draw in a _sketchy_, _hand-drawn-like_, s"
},
{
"path": "package.json",
"chars": 1291,
"preview": "{\n \"name\": \"roughjs\",\n \"version\": \"4.6.6\",\n \"description\": \"Create graphics using HTML Canvas or SVG with a hand-draw"
},
{
"path": "rollup.config.js",
"chars": 867,
"preview": "import { nodeResolve } from '@rollup/plugin-node-resolve';\nimport { terser } from \"rollup-plugin-terser\";\nimport typescr"
},
{
"path": "src/canvas.ts",
"chars": 4708,
"preview": "import { Config, Options, ResolvedOptions, Drawable, OpSet } from './core';\nimport { RoughGenerator } from './generator'"
},
{
"path": "src/core.ts",
"chars": 2056,
"preview": "import { Point } from './geometry';\nimport { Random } from './math';\n\nexport const SVGNS = 'http://www.w3.org/2000/svg';"
},
{
"path": "src/fillers/dashed-filler.ts",
"chars": 1857,
"preview": "import { PatternFiller, RenderHelper } from './filler-interface';\nimport { ResolvedOptions, OpSet, Op } from '../core';\n"
},
{
"path": "src/fillers/dot-filler.ts",
"chars": 1582,
"preview": "import { PatternFiller, RenderHelper } from './filler-interface';\nimport { ResolvedOptions, OpSet, Op } from '../core';\n"
},
{
"path": "src/fillers/filler-interface.ts",
"chars": 540,
"preview": "import { ResolvedOptions, OpSet, Op } from '../core';\nimport { Point } from '../geometry';\n\nexport interface PatternFill"
},
{
"path": "src/fillers/filler.ts",
"chars": 1581,
"preview": "import { ResolvedOptions } from '../core';\nimport { PatternFiller, RenderHelper } from './filler-interface';\nimport { Ha"
},
{
"path": "src/fillers/hachure-filler.ts",
"chars": 974,
"preview": "import { PatternFiller, RenderHelper } from './filler-interface';\nimport { ResolvedOptions, OpSet, Op } from '../core';\n"
},
{
"path": "src/fillers/hatch-filler.ts",
"chars": 498,
"preview": "import { HachureFiller } from './hachure-filler';\nimport { ResolvedOptions, OpSet } from '../core';\nimport { Point } fro"
},
{
"path": "src/fillers/scan-line-hachure.ts",
"chars": 580,
"preview": "import { hachureLines } from 'hachure-fill';\nimport { Point, Line } from '../geometry';\nimport { ResolvedOptions } from "
},
{
"path": "src/fillers/zigzag-filler.ts",
"chars": 1102,
"preview": "import { HachureFiller } from './hachure-filler';\nimport { polygonHachureLines } from './scan-line-hachure';\nimport { Re"
},
{
"path": "src/fillers/zigzag-line-filler.ts",
"chars": 1915,
"preview": "import { PatternFiller, RenderHelper } from './filler-interface';\nimport { ResolvedOptions, OpSet, Op } from '../core';\n"
},
{
"path": "src/generator.ts",
"chars": 9690,
"preview": "import { Config, Options, Drawable, OpSet, Op, ResolvedOptions, PathInfo } from './core.js';\nimport { Point } from './ge"
},
{
"path": "src/geometry.ts",
"chars": 338,
"preview": "export type Point = [number, number];\nexport type Line = [Point, Point];\n\nexport interface Rectangle {\n x: number;\n y:"
},
{
"path": "src/math.ts",
"chars": 368,
"preview": "export function randomSeed(): number {\n return Math.floor(Math.random() * 2 ** 31);\n}\n\nexport class Random {\n private "
},
{
"path": "src/renderer.ts",
"chars": 18207,
"preview": "import { ResolvedOptions, Op, OpSet } from './core.js';\nimport { Point } from './geometry.js';\nimport { getFiller } from"
},
{
"path": "src/rough.ts",
"chars": 547,
"preview": "import { Config } from './core';\nimport { RoughCanvas } from './canvas';\nimport { RoughGenerator } from './generator';\ni"
},
{
"path": "src/svg.ts",
"chars": 4566,
"preview": "import { Config, Options, OpSet, ResolvedOptions, Drawable, SVGNS } from './core';\nimport { RoughGenerator } from './gen"
},
{
"path": "tsconfig.json",
"chars": 471,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es2017\",\n \"module\": \"es2015\",\n \"moduleResolution\": \"node\",\n \"lib\": [\n "
},
{
"path": "visual-tests/canvas/arc.html",
"chars": 1338,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/arc2.html",
"chars": 650,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/curve-seed.html",
"chars": 843,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/curve.html",
"chars": 1768,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/curve2.html",
"chars": 1588,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/curve3.html",
"chars": 1357,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/curve4.html",
"chars": 1383,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/arc.html",
"chars": 1517,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/curve.html",
"chars": 1941,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/ellipse.html",
"chars": 1415,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/line.html",
"chars": 765,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/linearpath.html",
"chars": 755,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/path-with-transform.html",
"chars": 690,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/path.html",
"chars": 1954,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/polygon.html",
"chars": 1885,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/dashed/rectangle.html",
"chars": 1416,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/ellipse.html",
"chars": 1289,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/ellipse2.html",
"chars": 883,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/ellipse3.html",
"chars": 1002,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/line.html",
"chars": 662,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/linearpath.html",
"chars": 629,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/map.html",
"chars": 1550,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path-with-transform.html",
"chars": 587,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path.html",
"chars": 1836,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path2.html",
"chars": 2867,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path3.html",
"chars": 1645,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path4.html",
"chars": 1551,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path5.html",
"chars": 3912,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path6.html",
"chars": 2226,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/path7.html",
"chars": 1631,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/poly-seed.html",
"chars": 853,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/polygon.html",
"chars": 1782,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/polygon2.html",
"chars": 640,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/rectangle.html",
"chars": 1646,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/singlestroke/arc.html",
"chars": 1522,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/singlestroke/curve.html",
"chars": 1946,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/singlestroke/ellipse.html",
"chars": 1420,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/singlestroke/line.html",
"chars": 702,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/singlestroke/path.html",
"chars": 1959,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/singlestroke/polygon.html",
"chars": 1890,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/singlestroke/rectangle.html",
"chars": 1327,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/canvas/us.json",
"chars": 656702,
"preview": "\n{\"type\":\"Topology\",\"objects\":{\"counties\":{\"type\":\"GeometryCollection\",\"bbox\":[-179.1473399999999,17.67439566600018,179."
},
{
"path": "visual-tests/svg/dashed/ellipse.html",
"chars": 1638,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/dashed/line.html",
"chars": 832,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/dashed/polygon.html",
"chars": 802,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/dashed/rectangle.html",
"chars": 1568,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/ellipse.html",
"chars": 1441,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/line.html",
"chars": 729,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/polygon.html",
"chars": 672,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/rectangle.html",
"chars": 1371,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
},
{
"path": "visual-tests/svg/rectangle2.html",
"chars": 544,
"preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, m"
}
]
About this extraction
This page contains the full source code of the rough-stuff/rough GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 78 files (771.1 KB), approximately 332.3k tokens, and a symbol index with 128 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.