First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
{
"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"
}
}
+2
View File
@@ -0,0 +1,2 @@
github: pshihn
open_collective: rough
+46
View File
@@ -0,0 +1,46 @@
# 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
+21
View File
@@ -0,0 +1,21 @@
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.
+155
View File
@@ -0,0 +1,155 @@
# 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).
![Rough.js sample](https://roughjs.com/images/cap_demo.png)
[@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
![Rough.js rectangle](https://roughjs.com/images/m1.png)
```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
![Rough.js rectangle](https://roughjs.com/images/m2.png)
```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
![Rough.js rectangle](https://roughjs.com/images/m3.png)
```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**
![Rough.js fill examples](https://roughjs.com/images/m14.png)
### Sketching style
![Rough.js rectangle](https://roughjs.com/images/m4.png)
```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
![Rough.js paths](https://roughjs.com/images/m5.png)
```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:
![Rough.js texas map](https://roughjs.com/images/m9.png) ![Rough.js texas map](https://roughjs.com/images/m10.png)
## Examples
![Rough.js US map](https://roughjs.com/images/m6.png)
[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)
+23
View File
@@ -0,0 +1,23 @@
import { Config, Options, ResolvedOptions, Drawable } from './core';
import { RoughGenerator } from './generator';
import { Point } from './geometry';
export declare class RoughCanvas {
private gen;
private canvas;
private ctx;
constructor(canvas: HTMLCanvasElement, config?: Config);
draw(drawable: Drawable): void;
private fillSketch;
private _drawToContext;
get generator(): RoughGenerator;
getDefaultOptions(): ResolvedOptions;
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable;
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable;
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable;
circle(x: number, y: number, diameter: number, options?: Options): Drawable;
linearPath(points: Point[], options?: Options): Drawable;
polygon(points: Point[], options?: Options): Drawable;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): Drawable;
curve(points: Point[] | Point[][], options?: Options): Drawable;
path(d: string, options?: Options): Drawable;
}
+133
View File
@@ -0,0 +1,133 @@
import { RoughGenerator } from './generator';
export class RoughCanvas {
constructor(canvas, config) {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d');
this.gen = new RoughGenerator(config);
}
draw(drawable) {
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 = (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;
}
}
}
fillSketch(ctx, drawing, o) {
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();
}
_drawToContext(ctx, drawing, fixedDecimals, rule = '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() {
return this.gen;
}
getDefaultOptions() {
return this.gen.defaultOptions;
}
line(x1, y1, x2, y2, options) {
const d = this.gen.line(x1, y1, x2, y2, options);
this.draw(d);
return d;
}
rectangle(x, y, width, height, options) {
const d = this.gen.rectangle(x, y, width, height, options);
this.draw(d);
return d;
}
ellipse(x, y, width, height, options) {
const d = this.gen.ellipse(x, y, width, height, options);
this.draw(d);
return d;
}
circle(x, y, diameter, options) {
const d = this.gen.circle(x, y, diameter, options);
this.draw(d);
return d;
}
linearPath(points, options) {
const d = this.gen.linearPath(points, options);
this.draw(d);
return d;
}
polygon(points, options) {
const d = this.gen.polygon(points, options);
this.draw(d);
return d;
}
arc(x, y, width, height, start, stop, closed = false, options) {
const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
this.draw(d);
return d;
}
curve(points, options) {
const d = this.gen.curve(points, options);
this.draw(d);
return d;
}
path(d, options) {
const drawing = this.gen.path(d, options);
this.draw(drawing);
return drawing;
}
}
+85
View File
@@ -0,0 +1,85 @@
import { Point } from './geometry';
import { Random } from './math';
export declare 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;
}
+1
View File
@@ -0,0 +1 @@
export const SVGNS = 'http://www.w3.org/2000/svg';
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class DashedFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private dashedLine;
}
+36
View File
@@ -0,0 +1,36 @@
import { lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class DashedFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
const lines = polygonHachureLines(polygonList, o);
return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
}
dashedLine(lines, o) {
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 = [];
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 = [p1[0] + (lstart * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha) + (startOffset * Math.sin(alpha))];
const end = [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;
}
}
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class DotFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private dotsOnLines;
}
+41
View File
@@ -0,0 +1,41 @@
import { lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class DotFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
o = Object.assign({}, o, { hachureAngle: 0 });
const lines = polygonHachureLines(polygonList, o);
return this.dotsOnLines(lines, o);
}
dotsOnLines(lines, o) {
const ops = [];
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 };
}
}
+11
View File
@@ -0,0 +1,11 @@
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[];
}
+1
View File
@@ -0,0 +1 @@
export {};
+3
View File
@@ -0,0 +1,3 @@
import { ResolvedOptions } from '../core';
import { PatternFiller, RenderHelper } from './filler-interface';
export declare function getFiller(o: ResolvedOptions, helper: RenderHelper): PatternFiller;
+47
View File
@@ -0,0 +1,47 @@
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 = {};
export function getFiller(o, helper) {
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];
}
+10
View File
@@ -0,0 +1,10 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line } from '../geometry';
export declare class HachureFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
protected _fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
protected renderLines(lines: Line[], o: ResolvedOptions): Op[];
}
+21
View File
@@ -0,0 +1,21 @@
import { polygonHachureLines } from './scan-line-hachure';
export class HachureFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
return this._fillPolygons(polygonList, o);
}
_fillPolygons(polygonList, o) {
const lines = polygonHachureLines(polygonList, o);
const ops = this.renderLines(lines, o);
return { type: 'fillSketch', ops };
}
renderLines(lines, o) {
const ops = [];
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;
}
}
+6
View File
@@ -0,0 +1,6 @@
import { HachureFiller } from './hachure-filler';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class HatchFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
+10
View File
@@ -0,0 +1,10 @@
import { HachureFiller } from './hachure-filler';
export class HatchFiller extends HachureFiller {
fillPolygons(polygonList, o) {
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;
}
}
+3
View File
@@ -0,0 +1,3 @@
import { Point, Line } from '../geometry';
import { ResolvedOptions } from '../core';
export declare function polygonHachureLines(polygonList: Point[][], o: ResolvedOptions): Line[];
+17
View File
@@ -0,0 +1,17 @@
import { hachureLines } from 'hachure-fill';
export function polygonHachureLines(polygonList, o) {
var _a;
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 ((((_a = o.randomizer) === null || _a === void 0 ? void 0 : _a.next()) || Math.random()) > 0.7) {
skipOffset = gap;
}
}
return hachureLines(polygonList, gap, angle, skipOffset || 1);
}
+6
View File
@@ -0,0 +1,6 @@
import { HachureFiller } from './hachure-filler';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class ZigZagFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
+31
View File
@@ -0,0 +1,31 @@
import { HachureFiller } from './hachure-filler';
import { polygonHachureLines } from './scan-line-hachure';
import { lineLength } from '../geometry';
export class ZigZagFiller extends HachureFiller {
fillPolygons(polygonList, o) {
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 = [];
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 };
}
}
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class ZigZagLineFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private zigzagLines;
}
+38
View File
@@ -0,0 +1,38 @@
import { lineLength } from '../geometry';
import { polygonHachureLines } from './scan-line-hachure';
export class ZigZagLineFiller {
constructor(helper) {
this.helper = helper;
}
fillPolygons(polygonList, o) {
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) };
}
zigzagLines(lines, zo, o) {
const ops = [];
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 = [p1[0] + (lstart * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha)];
const end = [p1[0] + (lend * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha))];
const middle = [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;
}
}
+23
View File
@@ -0,0 +1,23 @@
import { Config, Options, Drawable, OpSet, ResolvedOptions, PathInfo } from './core.js';
import { Point } from './geometry.js';
export declare class RoughGenerator {
private config;
defaultOptions: ResolvedOptions;
constructor(config?: Config);
static newSeed(): number;
private _o;
private _d;
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable;
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable;
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable;
circle(x: number, y: number, diameter: number, options?: Options): Drawable;
linearPath(points: Point[], options?: Options): Drawable;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): Drawable;
curve(points: Point[] | Point[][], options?: Options): Drawable;
polygon(points: Point[], options?: Options): Drawable;
path(d: string, options?: Options): Drawable;
opsToPath(drawing: OpSet, fixedDecimals?: number): string;
toPaths(drawable: Drawable): PathInfo[];
private fillSketch;
private _mergedShape;
}
+295
View File
@@ -0,0 +1,295 @@
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 {
constructor(config) {
this.defaultOptions = {
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,
};
this.config = config || {};
if (this.config.options) {
this.defaultOptions = this._o(this.config.options);
}
}
static newSeed() {
return randomSeed();
}
_o(options) {
return options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
}
_d(shape, sets, options) {
return { shape, sets: sets || [], options: options || this.defaultOptions };
}
line(x1, y1, x2, y2, options) {
const o = this._o(options);
return this._d('line', [line(x1, y1, x2, y2, o)], o);
}
rectangle(x, y, width, height, options) {
const o = this._o(options);
const paths = [];
const outline = rectangle(x, y, width, height, o);
if (o.fill) {
const points = [[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, y, width, height, options) {
const o = this._o(options);
const paths = [];
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, y, diameter, options) {
const ret = this.ellipse(x, y, diameter, diameter, options);
ret.shape = 'circle';
return ret;
}
linearPath(points, options) {
const o = this._o(options);
return this._d('linearPath', [linearPath(points, false, o)], o);
}
arc(x, y, width, height, start, stop, closed = false, options) {
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 = Object.assign({}, 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, options) {
const o = this._o(options);
const paths = [];
const outline = curve(points, o);
if (o.fill && o.fill !== NOS) {
if (o.fillStyle === 'solid') {
const fillShape = curve(points, Object.assign(Object.assign({}, o), { disableMultiStroke: true, roughness: o.roughness ? (o.roughness + o.fillShapeRoughnessGain) : 0 }));
paths.push({
type: 'fillPath',
ops: this._mergedShape(fillShape.ops),
});
}
else {
const polyPoints = [];
const inputPoints = points;
if (inputPoints.length) {
const p1 = inputPoints[0];
const pointsList = (typeof p1[0] === 'number') ? [inputPoints] : inputPoints;
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, options) {
const o = this._o(options);
const paths = [];
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, options) {
const o = this._o(options);
const paths = [];
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, Object.assign(Object.assign({}, 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, fixedDecimals) {
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) {
const sets = drawable.sets || [];
const o = drawable.options || this.defaultOptions;
const paths = [];
for (const drawing of sets) {
let path = 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;
}
fillSketch(drawing, o) {
let fweight = o.fillWeight;
if (fweight < 0) {
fweight = o.strokeWidth / 2;
}
return {
d: this.opsToPath(drawing),
stroke: o.fill || NOS,
strokeWidth: fweight,
fill: NOS,
};
}
_mergedShape(input) {
return input.filter((d, i) => {
if (i === 0) {
return true;
}
if (d.op === 'move') {
return false;
}
return true;
});
}
}
+9
View File
@@ -0,0 +1,9 @@
export type Point = [number, number];
export type Line = [Point, Point];
export interface Rectangle {
x: number;
y: number;
width: number;
height: number;
}
export declare function lineLength(line: Line): number;
+5
View File
@@ -0,0 +1,5 @@
export function lineLength(line) {
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));
}
+6
View File
@@ -0,0 +1,6 @@
export declare function randomSeed(): number;
export declare class Random {
private seed;
constructor(seed: number);
next(): number;
}
+16
View File
@@ -0,0 +1,16 @@
export function randomSeed() {
return Math.floor(Math.random() * 2 ** 31);
}
export class Random {
constructor(seed) {
this.seed = seed;
}
next() {
if (this.seed) {
return ((2 ** 31 - 1) & (this.seed = Math.imul(48271, this.seed))) / 2 ** 31;
}
else {
return Math.random();
}
}
}
+28
View File
@@ -0,0 +1,28 @@
import { ResolvedOptions, Op, OpSet } from './core.js';
import { Point } from './geometry.js';
interface EllipseParams {
rx: number;
ry: number;
increment: number;
}
export declare function line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): OpSet;
export declare function linearPath(points: Point[], close: boolean, o: ResolvedOptions): OpSet;
export declare function polygon(points: Point[], o: ResolvedOptions): OpSet;
export declare function rectangle(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
export declare function curve(inputPoints: Point[] | Point[][], o: ResolvedOptions): OpSet;
export interface EllipseResult {
opset: OpSet;
estimatedPoints: Point[];
}
export declare function ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
export declare function generateEllipseParams(width: number, height: number, o: ResolvedOptions): EllipseParams;
export declare function ellipseWithParams(x: number, y: number, o: ResolvedOptions, ellipseParams: EllipseParams): EllipseResult;
export declare function arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean, roughClosure: boolean, o: ResolvedOptions): OpSet;
export declare function svgPath(path: string, o: ResolvedOptions): OpSet;
export declare function solidFillPolygon(polygonList: Point[][], o: ResolvedOptions): OpSet;
export declare function patternFillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
export declare function patternFillArc(x: number, y: number, width: number, height: number, start: number, stop: number, o: ResolvedOptions): OpSet;
export declare function randOffset(x: number, o: ResolvedOptions): number;
export declare function randOffsetWithRange(min: number, max: number, o: ResolvedOptions): number;
export declare function doubleLineFillOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
export {};
+486
View File
@@ -0,0 +1,486 @@
import { getFiller } from './fillers/filler.js';
import { Random } from './math.js';
import { parsePath, normalize, absolutize } from 'path-data-parser';
const helper = {
randOffset,
randOffsetWithRange,
ellipse,
doubleLineOps: doubleLineFillOps,
};
export function line(x1, y1, x2, y2, o) {
return { type: 'path', ops: _doubleLine(x1, y1, x2, y2, o) };
}
export function linearPath(points, close, o) {
const len = (points || []).length;
if (len > 2) {
const ops = [];
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, o) {
return linearPath(points, true, o);
}
export function rectangle(x, y, width, height, o) {
const points = [
[x, y],
[x + width, y],
[x + width, y + height],
[x, y + height],
];
return polygon(points, o);
}
export function curve(inputPoints, o) {
if (inputPoints.length) {
const p1 = inputPoints[0];
const pointsList = (typeof p1[0] === 'number') ? [inputPoints] : inputPoints;
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 function ellipse(x, y, width, height, o) {
const params = generateEllipseParams(width, height, o);
return ellipseWithParams(x, y, o, params).opset;
}
export function generateEllipseParams(width, height, o) {
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, y, o, ellipseParams) {
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, y, width, height, start, stop, closed, roughClosure, o) {
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, o) {
const segments = normalize(absolutize(parsePath(path)));
const ops = [];
let first = [0, 0];
let current = [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, o) {
const ops = [];
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, o) {
return getFiller(o, helper).fillPolygons(polygonList, o);
}
export function patternFillArc(x, y, width, height, start, stop, o) {
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 = [];
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, o) {
return _offsetOpt(x, o);
}
export function randOffsetWithRange(min, max, o) {
return _offset(min, max, o);
}
export function doubleLineFillOps(x1, y1, x2, y2, o) {
return _doubleLine(x1, y1, x2, y2, o, true);
}
// Private helpers
function cloneOptionsAlterSeed(ops) {
const result = Object.assign({}, ops);
result.randomizer = undefined;
if (ops.seed) {
result.seed = ops.seed + 1;
}
return result;
}
function random(ops) {
if (!ops.randomizer) {
ops.randomizer = new Random(ops.seed || 0);
}
return ops.randomizer.next();
}
function _offset(min, max, ops, roughnessGain = 1) {
return ops.roughness * roughnessGain * ((random(ops) * (max - min)) + min);
}
function _offsetOpt(x, ops, roughnessGain = 1) {
return _offset(-x, x, ops, roughnessGain);
}
function _doubleLine(x1, y1, x2, y2, o, filling = false) {
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, y1, x2, y2, o, move, overlay) {
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 = [];
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, offset, o) {
if (!points.length) {
return [];
}
const ps = [];
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, closePoint, o) {
const len = points.length;
const ops = [];
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, cx, cy, rx, ry, offset, overlap, o) {
const coreOnly = o.roughness === 0;
const corePoints = [];
const allPoints = [];
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 = [
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 = [
_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, cx, cy, rx, ry, strt, stp, offset, o) {
const radOffset = strt + _offsetOpt(0.1, o);
const points = [];
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, y1, x2, y2, x, y, current, o) {
const ops = [];
const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.3];
let f = [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;
}
+11
View File
@@ -0,0 +1,11 @@
import { Config } from './core';
import { RoughCanvas } from './canvas';
import { RoughGenerator } from './generator';
import { RoughSVG } from './svg';
declare const _default: {
canvas(canvas: HTMLCanvasElement, config?: Config): RoughCanvas;
svg(svg: SVGSVGElement, config?: Config): RoughSVG;
generator(config?: Config): RoughGenerator;
newSeed(): number;
};
export default _default;
+17
View File
@@ -0,0 +1,17 @@
import { RoughCanvas } from './canvas';
import { RoughGenerator } from './generator';
import { RoughSVG } from './svg';
export default {
canvas(canvas, config) {
return new RoughCanvas(canvas, config);
},
svg(svg, config) {
return new RoughSVG(svg, config);
},
generator(config) {
return new RoughGenerator(config);
},
newSeed() {
return RoughGenerator.newSeed();
},
};
+22
View File
@@ -0,0 +1,22 @@
import { Config, Options, OpSet, ResolvedOptions, Drawable } from './core';
import { RoughGenerator } from './generator';
import { Point } from './geometry';
export declare class RoughSVG {
private gen;
private svg;
constructor(svg: SVGSVGElement, config?: Config);
draw(drawable: Drawable): SVGGElement;
private fillSketch;
get generator(): RoughGenerator;
getDefaultOptions(): ResolvedOptions;
opsToPath(drawing: OpSet, fixedDecimalPlaceDigits?: number): string;
line(x1: number, y1: number, x2: number, y2: number, options?: Options): SVGGElement;
rectangle(x: number, y: number, width: number, height: number, options?: Options): SVGGElement;
ellipse(x: number, y: number, width: number, height: number, options?: Options): SVGGElement;
circle(x: number, y: number, diameter: number, options?: Options): SVGGElement;
linearPath(points: Point[], options?: Options): SVGGElement;
polygon(points: Point[], options?: Options): SVGGElement;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): SVGGElement;
curve(points: Point[] | Point[][], options?: Options): SVGGElement;
path(d: string, options?: Options): SVGGElement;
}
+116
View File
@@ -0,0 +1,116 @@
import { SVGNS } from './core';
import { RoughGenerator } from './generator';
export class RoughSVG {
constructor(svg, config) {
this.svg = svg;
this.gen = new RoughGenerator(config);
}
draw(drawable) {
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;
}
fillSketch(doc, drawing, o) {
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() {
return this.gen;
}
getDefaultOptions() {
return this.gen.defaultOptions;
}
opsToPath(drawing, fixedDecimalPlaceDigits) {
return this.gen.opsToPath(drawing, fixedDecimalPlaceDigits);
}
line(x1, y1, x2, y2, options) {
const d = this.gen.line(x1, y1, x2, y2, options);
return this.draw(d);
}
rectangle(x, y, width, height, options) {
const d = this.gen.rectangle(x, y, width, height, options);
return this.draw(d);
}
ellipse(x, y, width, height, options) {
const d = this.gen.ellipse(x, y, width, height, options);
return this.draw(d);
}
circle(x, y, diameter, options) {
const d = this.gen.circle(x, y, diameter, options);
return this.draw(d);
}
linearPath(points, options) {
const d = this.gen.linearPath(points, options);
return this.draw(d);
}
polygon(points, options) {
const d = this.gen.polygon(points, options);
return this.draw(d);
}
arc(x, y, width, height, start, stop, closed = false, options) {
const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
return this.draw(d);
}
curve(points, options) {
const d = this.gen.curve(points, options);
return this.draw(d);
}
path(d, options) {
const drawing = this.gen.path(d, options);
return this.draw(drawing);
}
}
+23
View File
@@ -0,0 +1,23 @@
import { Config, Options, ResolvedOptions, Drawable } from './core';
import { RoughGenerator } from './generator';
import { Point } from './geometry';
export declare class RoughCanvas {
private gen;
private canvas;
private ctx;
constructor(canvas: HTMLCanvasElement, config?: Config);
draw(drawable: Drawable): void;
private fillSketch;
private _drawToContext;
get generator(): RoughGenerator;
getDefaultOptions(): ResolvedOptions;
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable;
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable;
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable;
circle(x: number, y: number, diameter: number, options?: Options): Drawable;
linearPath(points: Point[], options?: Options): Drawable;
polygon(points: Point[], options?: Options): Drawable;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): Drawable;
curve(points: Point[] | Point[][], options?: Options): Drawable;
path(d: string, options?: Options): Drawable;
}
+85
View File
@@ -0,0 +1,85 @@
import { Point } from './geometry';
import { Random } from './math';
export declare 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;
}
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class DashedFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private dashedLine;
}
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class DotFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private dotsOnLines;
}
+11
View File
@@ -0,0 +1,11 @@
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[];
}
+3
View File
@@ -0,0 +1,3 @@
import { ResolvedOptions } from '../core';
import { PatternFiller, RenderHelper } from './filler-interface';
export declare function getFiller(o: ResolvedOptions, helper: RenderHelper): PatternFiller;
+10
View File
@@ -0,0 +1,10 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line } from '../geometry';
export declare class HachureFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
protected _fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
protected renderLines(lines: Line[], o: ResolvedOptions): Op[];
}
+6
View File
@@ -0,0 +1,6 @@
import { HachureFiller } from './hachure-filler';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class HatchFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
+3
View File
@@ -0,0 +1,3 @@
import { Point, Line } from '../geometry';
import { ResolvedOptions } from '../core';
export declare function polygonHachureLines(polygonList: Point[][], o: ResolvedOptions): Line[];
+6
View File
@@ -0,0 +1,6 @@
import { HachureFiller } from './hachure-filler';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class ZigZagFiller extends HachureFiller {
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
}
+9
View File
@@ -0,0 +1,9 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class ZigZagLineFiller implements PatternFiller {
private helper;
constructor(helper: RenderHelper);
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
private zigzagLines;
}
+23
View File
@@ -0,0 +1,23 @@
import { Config, Options, Drawable, OpSet, ResolvedOptions, PathInfo } from './core.js';
import { Point } from './geometry.js';
export declare class RoughGenerator {
private config;
defaultOptions: ResolvedOptions;
constructor(config?: Config);
static newSeed(): number;
private _o;
private _d;
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable;
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable;
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable;
circle(x: number, y: number, diameter: number, options?: Options): Drawable;
linearPath(points: Point[], options?: Options): Drawable;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): Drawable;
curve(points: Point[] | Point[][], options?: Options): Drawable;
polygon(points: Point[], options?: Options): Drawable;
path(d: string, options?: Options): Drawable;
opsToPath(drawing: OpSet, fixedDecimals?: number): string;
toPaths(drawable: Drawable): PathInfo[];
private fillSketch;
private _mergedShape;
}
+9
View File
@@ -0,0 +1,9 @@
export type Point = [number, number];
export type Line = [Point, Point];
export interface Rectangle {
x: number;
y: number;
width: number;
height: number;
}
export declare function lineLength(line: Line): number;
+6
View File
@@ -0,0 +1,6 @@
export declare function randomSeed(): number;
export declare class Random {
private seed;
constructor(seed: number);
next(): number;
}
+28
View File
@@ -0,0 +1,28 @@
import { ResolvedOptions, Op, OpSet } from './core.js';
import { Point } from './geometry.js';
interface EllipseParams {
rx: number;
ry: number;
increment: number;
}
export declare function line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): OpSet;
export declare function linearPath(points: Point[], close: boolean, o: ResolvedOptions): OpSet;
export declare function polygon(points: Point[], o: ResolvedOptions): OpSet;
export declare function rectangle(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
export declare function curve(inputPoints: Point[] | Point[][], o: ResolvedOptions): OpSet;
export interface EllipseResult {
opset: OpSet;
estimatedPoints: Point[];
}
export declare function ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
export declare function generateEllipseParams(width: number, height: number, o: ResolvedOptions): EllipseParams;
export declare function ellipseWithParams(x: number, y: number, o: ResolvedOptions, ellipseParams: EllipseParams): EllipseResult;
export declare function arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean, roughClosure: boolean, o: ResolvedOptions): OpSet;
export declare function svgPath(path: string, o: ResolvedOptions): OpSet;
export declare function solidFillPolygon(polygonList: Point[][], o: ResolvedOptions): OpSet;
export declare function patternFillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
export declare function patternFillArc(x: number, y: number, width: number, height: number, start: number, stop: number, o: ResolvedOptions): OpSet;
export declare function randOffset(x: number, o: ResolvedOptions): number;
export declare function randOffsetWithRange(min: number, max: number, o: ResolvedOptions): number;
export declare function doubleLineFillOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
export {};
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
import { Config } from './core';
import { RoughCanvas } from './canvas';
import { RoughGenerator } from './generator';
import { RoughSVG } from './svg';
declare const _default: {
canvas(canvas: HTMLCanvasElement, config?: Config): RoughCanvas;
svg(svg: SVGSVGElement, config?: Config): RoughSVG;
generator(config?: Config): RoughGenerator;
newSeed(): number;
};
export default _default;
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
import { Config, Options, OpSet, ResolvedOptions, Drawable } from './core';
import { RoughGenerator } from './generator';
import { Point } from './geometry';
export declare class RoughSVG {
private gen;
private svg;
constructor(svg: SVGSVGElement, config?: Config);
draw(drawable: Drawable): SVGGElement;
private fillSketch;
get generator(): RoughGenerator;
getDefaultOptions(): ResolvedOptions;
opsToPath(drawing: OpSet, fixedDecimalPlaceDigits?: number): string;
line(x1: number, y1: number, x2: number, y2: number, options?: Options): SVGGElement;
rectangle(x: number, y: number, width: number, height: number, options?: Options): SVGGElement;
ellipse(x: number, y: number, width: number, height: number, options?: Options): SVGGElement;
circle(x: number, y: number, diameter: number, options?: Options): SVGGElement;
linearPath(points: Point[], options?: Options): SVGGElement;
polygon(points: Point[], options?: Options): SVGGElement;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): SVGGElement;
curve(points: Point[] | Point[][], options?: Options): SVGGElement;
path(d: string, options?: Options): SVGGElement;
}
+48
View File
@@ -0,0 +1,48 @@
{
"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"
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"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"
]
}