refactor - wip

This commit is contained in:
Preet Shihn
2018-12-20 14:11:32 -08:00
parent 05fb7ab08c
commit 6dbf7685d8
48 changed files with 3245 additions and 2705 deletions

View File

@@ -1,10 +1,8 @@
import { ResolvedOptions, Drawable } from './core';
import { RoughRenderer } from './renderer';
export declare abstract class RoughCanvasBase {
protected canvas: HTMLCanvasElement;
protected ctx: CanvasRenderingContext2D;
constructor(canvas: HTMLCanvasElement);
static createRenderer(): RoughRenderer;
abstract getDefaultOptions(): ResolvedOptions;
draw(drawable: Drawable): void;
private computeBBox;

View File

@@ -1,13 +1,9 @@
import { RoughRenderer } from './renderer';
const hasDocument = typeof document !== 'undefined';
export class RoughCanvasBase {
constructor(canvas) {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d');
}
static createRenderer() {
return new RoughRenderer();
}
draw(drawable) {
const sets = drawable.sets || [];
const o = drawable.options || this.getDefaultOptions();

View File

@@ -1,9 +1,7 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { PatternFiller } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class DotFiller implements PatternFiller {
renderer: RenderHelper;
constructor(renderer: RenderHelper);
fillPolygon(points: Point[], o: ResolvedOptions): OpSet;
fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet;
private dotsOnLines;

View File

@@ -1,8 +1,6 @@
import { hachureLinesForPolygon, hachureLinesForEllipse, lineLength } from './filler-utils';
import { randOffsetWithRange, ellipse } from '../renderer';
export class DotFiller {
constructor(renderer) {
this.renderer = renderer;
}
fillPolygon(points, o) {
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0 });
const lines = hachureLinesForPolygon(points, o);
@@ -10,7 +8,7 @@ export class DotFiller {
}
fillEllipse(cx, cy, width, height, o) {
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0 });
const lines = hachureLinesForEllipse(cx, cy, width, height, o, this.renderer);
const lines = hachureLinesForEllipse(cx, cy, width, height, o);
return this.dotsOnLines(lines, o);
}
dotsOnLines(lines, o) {
@@ -34,10 +32,10 @@ export class DotFiller {
const dy = l * Math.sin(alpha);
const dx = l * Math.cos(alpha);
const c = [line[0][0] - dx, line[0][1] + dy];
const cx = this.renderer.getOffset(c[0] - gap / 4, c[0] + gap / 4, o);
const cy = this.renderer.getOffset(c[1] - gap / 4, c[1] + gap / 4, o);
const ellipse = this.renderer.ellipse(cx, cy, fweight, fweight, o);
ops = ops.concat(ellipse.ops);
const cx = randOffsetWithRange(c[0] - gap / 4, c[0] + gap / 4, o);
const cy = randOffsetWithRange(c[1] - gap / 4, c[1] + gap / 4, o);
const el = ellipse(cx, cy, fweight, fweight, o);
ops = ops.concat(el.ops);
}
}
return { type: 'fillSketch', ops };

View File

@@ -1,11 +1,6 @@
import { ResolvedOptions, OpSet, Op } from '../core';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export interface PatternFiller {
fillPolygon(points: Point[], o: ResolvedOptions): OpSet;
fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet;
}
export interface RenderHelper {
doubleLine(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
getOffset(min: number, max: number, ops: ResolvedOptions): number;
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
}

View File

@@ -1,8 +1,7 @@
import { Point, Line } from '../geometry';
import { ResolvedOptions } from '../core';
import { RenderHelper } from './filler-interface';
export declare function lineLength(line: Line): number;
export declare function getIntersectingLines(line: number[], points: Point[]): Point[];
export declare function affine(x: number, y: number, cx: number, cy: number, sinAnglePrime: number, cosAnglePrime: number, R: number): Point;
export declare function hachureLinesForPolygon(points: Point[], o: ResolvedOptions): Line[];
export declare function hachureLinesForEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions, renderer: RenderHelper): Line[];
export declare function hachureLinesForEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): Line[];

View File

@@ -1,5 +1,6 @@
import { Segment } from '../geometry';
import { HachureIterator } from '../utils/hachure';
import { randOffset } from '../renderer';
export function lineLength(line) {
const p1 = line[0];
const p2 = line[1];
@@ -67,12 +68,12 @@ export function hachureLinesForPolygon(points, o) {
}
return ret;
}
export function hachureLinesForEllipse(cx, cy, width, height, o, renderer) {
export function hachureLinesForEllipse(cx, cy, width, height, o) {
const ret = [];
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
rx += renderer.getOffset(-rx * 0.05, rx * 0.05, o);
ry += renderer.getOffset(-ry * 0.05, ry * 0.05, o);
rx += randOffset(rx * 0.05, o);
ry += randOffset(ry * 0.05, o);
const angle = o.hachureAngle;
let gap = o.hachureGap;
if (gap <= 0) {

View File

@@ -1,3 +1,3 @@
import { ResolvedOptions } from '../core';
import { PatternFiller, RenderHelper } from './filler-interface';
export declare function getFiller(renderer: RenderHelper, o: ResolvedOptions): PatternFiller;
import { PatternFiller } from './filler-interface';
export declare function getFiller(o: ResolvedOptions): PatternFiller;

View File

@@ -3,30 +3,30 @@ import { ZigZagFiller } from './zigzag-filler';
import { HatchFiller } from './hatch-filler';
import { DotFiller } from './dot-filler';
const fillers = {};
export function getFiller(renderer, o) {
export function getFiller(o) {
let fillerName = o.fillStyle || 'hachure';
if (!fillers[fillerName]) {
switch (fillerName) {
case 'zigzag':
if (!fillers[fillerName]) {
fillers[fillerName] = new ZigZagFiller(renderer);
fillers[fillerName] = new ZigZagFiller();
}
break;
case 'cross-hatch':
if (!fillers[fillerName]) {
fillers[fillerName] = new HatchFiller(renderer);
fillers[fillerName] = new HatchFiller();
}
break;
case 'dots':
if (!fillers[fillerName]) {
fillers[fillerName] = new DotFiller(renderer);
fillers[fillerName] = new DotFiller();
}
break;
case 'hachure':
default:
fillerName = 'hachure';
if (!fillers[fillerName]) {
fillers[fillerName] = new HachureFiller(renderer);
fillers[fillerName] = new HachureFiller();
}
break;
}

View File

@@ -1,9 +1,7 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { PatternFiller } from './filler-interface';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export declare class HachureFiller implements PatternFiller {
renderer: RenderHelper;
constructor(renderer: RenderHelper);
fillPolygon(points: Point[], o: ResolvedOptions): OpSet;
fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet;
protected _fillPolygon(points: Point[], o: ResolvedOptions, connectEnds?: boolean): OpSet;

View File

@@ -1,8 +1,6 @@
import { hachureLinesForPolygon, hachureLinesForEllipse } from './filler-utils';
import { doubleLineOps } from '../renderer';
export class HachureFiller {
constructor(renderer) {
this.renderer = renderer;
}
fillPolygon(points, o) {
return this._fillPolygon(points, o);
}
@@ -15,7 +13,7 @@ export class HachureFiller {
return { type: 'fillSketch', ops };
}
_fillEllipse(cx, cy, width, height, o, connectEnds = false) {
const lines = hachureLinesForEllipse(cx, cy, width, height, o, this.renderer);
const lines = hachureLinesForEllipse(cx, cy, width, height, o);
const ops = this.renderLines(lines, o, connectEnds);
return { type: 'fillSketch', ops };
}
@@ -23,9 +21,9 @@ export class HachureFiller {
let ops = [];
let prevPoint = null;
for (const line of lines) {
ops = ops.concat(this.renderer.doubleLine(line[0][0], line[0][1], line[1][0], line[1][1], o));
ops = ops.concat(doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
if (connectEnds && prevPoint) {
ops = ops.concat(this.renderer.doubleLine(prevPoint[0], prevPoint[1], line[0][0], line[0][1], o));
ops = ops.concat(doubleLineOps(prevPoint[0], prevPoint[1], line[0][0], line[0][1], o));
}
prevPoint = line[1];
}

View File

@@ -1,15 +1,12 @@
import { RoughRenderer } from './renderer.js';
import { Config, DrawingSurface, Options, ResolvedOptions, Drawable, OpSet, PathInfo } from './core';
import { Point } from './geometry.js';
export declare abstract class RoughGeneratorBase {
protected config: Config;
protected surface: DrawingSurface;
protected renderer: RoughRenderer;
defaultOptions: ResolvedOptions;
constructor(config: Config | null, surface: DrawingSurface);
protected _options(options?: Options): ResolvedOptions;
protected _drawable(shape: string, sets: OpSet[], options: ResolvedOptions): Drawable;
protected readonly lib: RoughRenderer;
private getCanvasSize;
protected computePolygonSize(points: Point[]): Point;
protected polygonPath(points: Point[]): string;

View File

@@ -1,4 +1,3 @@
import { createRenderer } from './renderer-factory.js';
const hasSelf = typeof self !== 'undefined';
export class RoughGeneratorBase {
constructor(config, surface) {
@@ -17,7 +16,6 @@ export class RoughGeneratorBase {
};
this.config = config || {};
this.surface = surface;
this.renderer = createRenderer(this.config);
if (this.config.options) {
this.defaultOptions = this._options(this.config.options);
}
@@ -28,9 +26,6 @@ export class RoughGeneratorBase {
_drawable(shape, sets, options) {
return { shape, sets: sets || [], options: options || this.defaultOptions };
}
get lib() {
return this.renderer;
}
getCanvasSize() {
const val = (w) => {
if (w && typeof w === 'object') {

2
bin/generator.d.ts vendored
View File

@@ -1,6 +1,6 @@
import { Config, DrawingSurface, Options, Drawable } from './core';
import { Point } from './geometry.js';
import { RoughGeneratorBase } from './generator-base';
import { RoughGeneratorBase } from './generator-base.js';
export declare class RoughGenerator extends RoughGeneratorBase {
constructor(config: Config | null, surface: DrawingSurface);
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable;

View File

@@ -1,11 +1,12 @@
import { RoughGeneratorBase } from './generator-base';
import { RoughGeneratorBase } from './generator-base.js';
import { line, solidFillPolygon, patternFillPolygon, rectangle, ellipse, patternFillEllipse, linearPath, arc, patternFillArc, curve, svgPath } from './renderer.js';
export class RoughGenerator extends RoughGeneratorBase {
constructor(config, surface) {
super(config, surface);
}
line(x1, y1, x2, y2, options) {
const o = this._options(options);
return this._drawable('line', [this.lib.line(x1, y1, x2, y2, o)], o);
return this._drawable('line', [line(x1, y1, x2, y2, o)], o);
}
rectangle(x, y, width, height, options) {
const o = this._options(options);
@@ -13,13 +14,13 @@ export class RoughGenerator extends RoughGeneratorBase {
if (o.fill) {
const points = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]];
if (o.fillStyle === 'solid') {
paths.push(this.lib.solidFillPolygon(points, o));
paths.push(solidFillPolygon(points, o));
}
else {
paths.push(this.lib.patternFillPolygon(points, o));
paths.push(patternFillPolygon(points, o));
}
}
paths.push(this.lib.rectangle(x, y, width, height, o));
paths.push(rectangle(x, y, width, height, o));
return this._drawable('rectangle', paths, o);
}
ellipse(x, y, width, height, options) {
@@ -27,15 +28,15 @@ export class RoughGenerator extends RoughGeneratorBase {
const paths = [];
if (o.fill) {
if (o.fillStyle === 'solid') {
const shape = this.lib.ellipse(x, y, width, height, o);
const shape = ellipse(x, y, width, height, o);
shape.type = 'fillPath';
paths.push(shape);
}
else {
paths.push(this.lib.patternFillEllipse(x, y, width, height, o));
paths.push(patternFillEllipse(x, y, width, height, o));
}
}
paths.push(this.lib.ellipse(x, y, width, height, o));
paths.push(ellipse(x, y, width, height, o));
return this._drawable('ellipse', paths, o);
}
circle(x, y, diameter, options) {
@@ -45,34 +46,34 @@ export class RoughGenerator extends RoughGeneratorBase {
}
linearPath(points, options) {
const o = this._options(options);
return this._drawable('linearPath', [this.lib.linearPath(points, false, o)], o);
return this._drawable('linearPath', [linearPath(points, false, o)], o);
}
arc(x, y, width, height, start, stop, closed = false, options) {
const o = this._options(options);
const paths = [];
if (closed && o.fill) {
if (o.fillStyle === 'solid') {
const shape = this.lib.arc(x, y, width, height, start, stop, true, false, o);
const shape = arc(x, y, width, height, start, stop, true, false, o);
shape.type = 'fillPath';
paths.push(shape);
}
else {
paths.push(this.lib.patternFillArc(x, y, width, height, start, stop, o));
paths.push(patternFillArc(x, y, width, height, start, stop, o));
}
}
paths.push(this.lib.arc(x, y, width, height, start, stop, closed, true, o));
paths.push(arc(x, y, width, height, start, stop, closed, true, o));
return this._drawable('arc', paths, o);
}
curve(points, options) {
const o = this._options(options);
return this._drawable('curve', [this.lib.curve(points, o)], o);
return this._drawable('curve', [curve(points, o)], o);
}
polygon(points, options) {
const o = this._options(options);
const paths = [];
if (o.fill) {
if (o.fillStyle === 'solid') {
paths.push(this.lib.solidFillPolygon(points, o));
paths.push(solidFillPolygon(points, o));
}
else {
const size = this.computePolygonSize(points);
@@ -82,14 +83,14 @@ export class RoughGenerator extends RoughGeneratorBase {
[size[0], size[1]],
[0, size[1]]
];
const shape = this.lib.patternFillPolygon(fillPoints, o);
const shape = patternFillPolygon(fillPoints, o);
shape.type = 'path2Dpattern';
shape.size = size;
shape.path = this.polygonPath(points);
paths.push(shape);
}
}
paths.push(this.lib.linearPath(points, true, o));
paths.push(linearPath(points, true, o));
return this._drawable('polygon', paths, o);
}
path(d, options) {
@@ -111,14 +112,14 @@ export class RoughGenerator extends RoughGeneratorBase {
[size[0], size[1]],
[0, size[1]]
];
const shape = this.lib.patternFillPolygon(points, o);
const shape = patternFillPolygon(points, o);
shape.type = 'path2Dpattern';
shape.size = size;
shape.path = d;
paths.push(shape);
}
}
paths.push(this.lib.svgPath(d, o));
paths.push(svgPath(d, o));
return this._drawable('path', paths, o);
}
}

0
bin/renderer-factory-old.d.ts vendored Normal file
View File

View File

@@ -0,0 +1,16 @@
"use strict";
// import { Config } from './core';
// import { RoughRenderer } from './renderer';
// const hasSelf = typeof self !== 'undefined';
// const roughScript = hasSelf && self && self.document && self.document.currentScript && (self.document.currentScript as HTMLScriptElement).src;
// export function createRenderer(config: Config): RoughRenderer {
// if (hasSelf && roughScript && self && (self as any).workly && config.async && (!config.noWorker)) {
// const worklySource = config.worklyURL || 'https://cdn.jsdelivr.net/gh/pshihn/workly/dist/workly.min.js';
// if (worklySource) {
// const code = `importScripts('${worklySource}', '${roughScript}');\nworkly.expose(self.rough.createRenderer());`;
// const ourl = URL.createObjectURL(new Blob([code]));
// return (self as any).workly.proxy(ourl);
// }
// }
// return new RoughRenderer();
// }

14
bin/renderer-new.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
import { ResolvedOptions, OpSet } from './core';
import { Point } from './geometry';
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(points: Point[], o: ResolvedOptions): OpSet;
export declare function ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
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(points: Point[], o: ResolvedOptions): OpSet;
export declare function patternFillPolygon(points: Point[], o: ResolvedOptions): OpSet;
export declare function patternFillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet;
export declare function patternFillArc(x: number, y: number, width: number, height: number, start: number, stop: number, o: ResolvedOptions): OpSet;

598
bin/renderer-new.js Normal file
View File

@@ -0,0 +1,598 @@
import { RoughPath, PathFitter, RoughArcConverter } from './path.js';
import { getFiller } from './fillers/filler';
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) {
let ops = [];
for (let i = 0; i < (len - 1); i++) {
ops = ops.concat(_doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
}
if (close) {
ops = ops.concat(_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(points, o) {
const o1 = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
const o2 = _curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), o);
return { type: 'path', ops: o1.concat(o2) };
}
export function ellipse(x, y, width, height, o) {
const increment = (Math.PI * 2) / o.curveStepCount;
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
rx += _offsetOpt(rx * 0.05, o);
ry += _offsetOpt(ry * 0.05, o);
const o1 = _ellipse(increment, x, y, rx, ry, 1, increment * _offset(0.1, _offset(0.4, 1, o), o), o);
const o2 = _ellipse(increment, x, y, rx, ry, 1.5, 0, o);
return { type: 'path', ops: o1.concat(o2) };
}
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 o1 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
const o2 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
let ops = o1.concat(o2);
if (closed) {
if (roughClosure) {
ops = ops.concat(_doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o));
ops = ops.concat(_doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o));
}
else {
ops.push({ op: 'lineTo', data: [cx, cy] });
ops.push({ op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] });
}
}
return { type: 'path', ops };
}
export function svgPath(path, o) {
path = (path || '').replace(/\n/g, ' ').replace(/(-\s)/g, '-').replace('/(\s\s)/g', ' ');
let p = new RoughPath(path);
if (o.simplification) {
const fitter = new PathFitter(p.linearPoints, p.closed);
const d = fitter.fit(o.simplification);
p = new RoughPath(d);
}
let ops = [];
const segments = p.segments || [];
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
const prev = i > 0 ? segments[i - 1] : null;
const opList = _processSegment(p, s, prev, o);
if (opList && opList.length) {
ops = ops.concat(opList);
}
}
return { type: 'path', ops };
}
// Fills
export function solidFillPolygon(points, o) {
const ops = [];
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 patternFillPolygon(points, o) {
return getFiller(o).fillPolygon(points, o);
}
export function patternFillEllipse(cx, cy, width, height, o) {
return getFiller(o).fillEllipse(cx, cy, width, height, 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 patternFillPolygon(points, o);
}
// Private helpers
function _offset(min, max, ops) {
return ops.roughness * ((Math.random() * (max - min)) + min);
}
function _offsetOpt(x, ops) {
return _offset(-x, x, ops);
}
function _doubleLine(x1, y1, x2, y2, o) {
const o1 = _line(x1, y1, x2, y2, o, true, false);
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);
let offset = o.maxRandomnessOffset || 0;
if ((offset * offset * 100) > lengthSq) {
offset = Math.sqrt(lengthSq) / 10;
}
const halfOffset = offset / 2;
const divergePoint = 0.2 + Math.random() * 0.2;
let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
let midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200;
midDispX = _offsetOpt(midDispX, o);
midDispY = _offsetOpt(midDispY, o);
const ops = [];
const randomHalf = () => _offsetOpt(halfOffset, o);
const randomFull = () => _offsetOpt(offset, o);
if (move) {
if (overlay) {
ops.push({
op: 'move', data: [
x1 + randomHalf(),
y1 + randomHalf()
]
});
}
else {
ops.push({
op: 'move', data: [
x1 + _offsetOpt(offset, o),
y1 + _offsetOpt(offset, o)
]
});
}
}
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 + randomHalf(),
y2 + 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 + randomFull(),
y2 + randomFull()
]
});
}
return ops;
}
function _curveWithOffset(points, offset, o) {
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;
let 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 = ops.concat(_doubleLine(points[0][0], points[0][1], points[1][0], points[1][1], o));
}
return ops;
}
function _ellipse(increment, cx, cy, rx, ry, offset, overlap, o) {
const radOffset = _offsetOpt(0.5, o) - (Math.PI / 2);
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 < (Math.PI * 2 + radOffset - 0.01); angle = angle + increment) {
points.push([
_offsetOpt(offset, o) + cx + rx * Math.cos(angle),
_offsetOpt(offset, o) + cy + ry * Math.sin(angle)
]);
}
points.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)
]);
points.push([
_offsetOpt(offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
_offsetOpt(offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap)
]);
points.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 _curve(points, null, o);
}
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, path, o) {
const ops = [];
const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.5];
let f = [0, 0];
for (let i = 0; i < 2; i++) {
if (i === 0) {
ops.push({ op: 'move', data: [path.x, path.y] });
}
else {
ops.push({ op: 'move', data: [path.x + _offsetOpt(ros[0], o), path.y + _offsetOpt(ros[0], o)] });
}
f = [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]
]
});
}
path.setPosition(f[0], f[1]);
return ops;
}
function _processSegment(path, seg, prevSeg, o) {
let ops = [];
switch (seg.key) {
case 'M':
case 'm': {
const delta = seg.key === 'm';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
const ro = 1 * (o.maxRandomnessOffset || 0);
x = x + _offsetOpt(ro, o);
y = y + _offsetOpt(ro, o);
path.setPosition(x, y);
ops.push({ op: 'move', data: [x, y] });
}
break;
}
case 'L':
case 'l': {
const delta = seg.key === 'l';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
ops = ops.concat(_doubleLine(path.x, path.y, x, y, o));
path.setPosition(x, y);
}
break;
}
case 'H':
case 'h': {
const delta = seg.key === 'h';
if (seg.data.length) {
let x = +seg.data[0];
if (delta) {
x += path.x;
}
ops = ops.concat(_doubleLine(path.x, path.y, x, path.y, o));
path.setPosition(x, path.y);
}
break;
}
case 'V':
case 'v': {
const delta = seg.key === 'v';
if (seg.data.length) {
let y = +seg.data[0];
if (delta) {
y += path.y;
}
ops = ops.concat(_doubleLine(path.x, path.y, path.x, y, o));
path.setPosition(path.x, y);
}
break;
}
case 'Z':
case 'z': {
if (path.first) {
ops = ops.concat(_doubleLine(path.x, path.y, path.first[0], path.first[1], o));
path.setPosition(path.first[0], path.first[1]);
path.first = null;
}
break;
}
case 'C':
case 'c': {
const delta = seg.key === 'c';
if (seg.data.length >= 6) {
let x1 = +seg.data[0];
let y1 = +seg.data[1];
let x2 = +seg.data[2];
let y2 = +seg.data[3];
let x = +seg.data[4];
let y = +seg.data[5];
if (delta) {
x1 += path.x;
x2 += path.x;
x += path.x;
y1 += path.y;
y2 += path.y;
y += path.y;
}
const ob = _bezierTo(x1, y1, x2, y2, x, y, path, o);
ops = ops.concat(ob);
path.bezierReflectionPoint = [x + (x - x2), y + (y - y2)];
}
break;
}
case 'S':
case 's': {
const delta = seg.key === 's';
if (seg.data.length >= 4) {
let x2 = +seg.data[0];
let y2 = +seg.data[1];
let x = +seg.data[2];
let y = +seg.data[3];
if (delta) {
x2 += path.x;
x += path.x;
y2 += path.y;
y += path.y;
}
let x1 = x2;
let y1 = y2;
const prevKey = prevSeg ? prevSeg.key : '';
let ref = null;
if (prevKey === 'c' || prevKey === 'C' || prevKey === 's' || prevKey === 'S') {
ref = path.bezierReflectionPoint;
}
if (ref) {
x1 = ref[0];
y1 = ref[1];
}
const ob = _bezierTo(x1, y1, x2, y2, x, y, path, o);
ops = ops.concat(ob);
path.bezierReflectionPoint = [x + (x - x2), y + (y - y2)];
}
break;
}
case 'Q':
case 'q': {
const delta = seg.key === 'q';
if (seg.data.length >= 4) {
let x1 = +seg.data[0];
let y1 = +seg.data[1];
let x = +seg.data[2];
let y = +seg.data[3];
if (delta) {
x1 += path.x;
x += path.x;
y1 += path.y;
y += path.y;
}
const offset1 = 1 * (1 + o.roughness * 0.2);
const offset2 = 1.5 * (1 + o.roughness * 0.22);
ops.push({ op: 'move', data: [path.x + _offsetOpt(offset1, o), path.y + _offsetOpt(offset1, o)] });
let f = [x + _offsetOpt(offset1, o), y + _offsetOpt(offset1, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + _offsetOpt(offset1, o), y1 + _offsetOpt(offset1, o),
f[0], f[1]
]
});
ops.push({ op: 'move', data: [path.x + _offsetOpt(offset2, o), path.y + _offsetOpt(offset2, o)] });
f = [x + _offsetOpt(offset2, o), y + _offsetOpt(offset2, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + _offsetOpt(offset2, o), y1 + _offsetOpt(offset2, o),
f[0], f[1]
]
});
path.setPosition(f[0], f[1]);
path.quadReflectionPoint = [x + (x - x1), y + (y - y1)];
}
break;
}
case 'T':
case 't': {
const delta = seg.key === 't';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
let x1 = x;
let y1 = y;
const prevKey = prevSeg ? prevSeg.key : '';
let ref = null;
if (prevKey === 'q' || prevKey === 'Q' || prevKey === 't' || prevKey === 'T') {
ref = path.quadReflectionPoint;
}
if (ref) {
x1 = ref[0];
y1 = ref[1];
}
const offset1 = 1 * (1 + o.roughness * 0.2);
const offset2 = 1.5 * (1 + o.roughness * 0.22);
ops.push({ op: 'move', data: [path.x + _offsetOpt(offset1, o), path.y + _offsetOpt(offset1, o)] });
let f = [x + _offsetOpt(offset1, o), y + _offsetOpt(offset1, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + _offsetOpt(offset1, o), y1 + _offsetOpt(offset1, o),
f[0], f[1]
]
});
ops.push({ op: 'move', data: [path.x + _offsetOpt(offset2, o), path.y + _offsetOpt(offset2, o)] });
f = [x + _offsetOpt(offset2, o), y + _offsetOpt(offset2, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + _offsetOpt(offset2, o), y1 + _offsetOpt(offset2, o),
f[0], f[1]
]
});
path.setPosition(f[0], f[1]);
path.quadReflectionPoint = [x + (x - x1), y + (y - y1)];
}
break;
}
case 'A':
case 'a': {
const delta = seg.key === 'a';
if (seg.data.length >= 7) {
const rx = +seg.data[0];
const ry = +seg.data[1];
const angle = +seg.data[2];
const largeArcFlag = +seg.data[3];
const sweepFlag = +seg.data[4];
let x = +seg.data[5];
let y = +seg.data[6];
if (delta) {
x += path.x;
y += path.y;
}
if (x === path.x && y === path.y) {
break;
}
if (rx === 0 || ry === 0) {
ops = ops.concat(_doubleLine(path.x, path.y, x, y, o));
path.setPosition(x, y);
}
else {
for (let i = 0; i < 1; i++) {
const arcConverter = new RoughArcConverter([path.x, path.y], [x, y], [rx, ry], angle, largeArcFlag ? true : false, sweepFlag ? true : false);
let segment = arcConverter.getNextSegment();
while (segment) {
const ob = _bezierTo(segment.cp1[0], segment.cp1[1], segment.cp2[0], segment.cp2[1], segment.to[0], segment.to[1], path, o);
ops = ops.concat(ob);
segment = arcConverter.getNextSegment();
}
}
}
}
break;
}
default:
break;
}
return ops;
}

25
bin/renderer-old.d.ts vendored Normal file
View File

@@ -0,0 +1,25 @@
import { ResolvedOptions, OpSet, Op } from './core';
import { Point } from './geometry';
export declare class RoughRenderer {
line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): OpSet;
linearPath(points: Point[], close: boolean, o: ResolvedOptions): OpSet;
polygon(points: Point[], o: ResolvedOptions): OpSet;
rectangle(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
curve(points: Point[], o: ResolvedOptions): OpSet;
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean, roughClosure: boolean, o: ResolvedOptions): OpSet;
svgPath(path: string, o: ResolvedOptions): OpSet;
solidFillPolygon(points: Point[], o: ResolvedOptions): OpSet;
patternFillPolygon(points: Point[], o: ResolvedOptions): OpSet;
patternFillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet;
patternFillArc(x: number, y: number, width: number, height: number, start: number, stop: number, o: ResolvedOptions): OpSet;
getOffset(min: number, max: number, ops: ResolvedOptions): number;
doubleLine(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
private _line;
private _curve;
private _ellipse;
private _curveWithOffset;
private _arc;
private _bezierTo;
private _processSegment;
}

597
bin/renderer-old.js Normal file
View File

@@ -0,0 +1,597 @@
import { RoughPath, RoughArcConverter, PathFitter } from './path.js';
import { getFiller } from './fillers/filler';
export class RoughRenderer {
line(x1, y1, x2, y2, o) {
const ops = this.doubleLine(x1, y1, x2, y2, o);
return { type: 'path', ops };
}
linearPath(points, close, o) {
const len = (points || []).length;
if (len > 2) {
let ops = [];
for (let i = 0; i < (len - 1); i++) {
ops = ops.concat(this.doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
}
if (close) {
ops = ops.concat(this.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 this.line(points[0][0], points[0][1], points[1][0], points[1][1], o);
}
return { type: 'path', ops: [] };
}
polygon(points, o) {
return this.linearPath(points, true, o);
}
rectangle(x, y, width, height, o) {
const points = [
[x, y], [x + width, y], [x + width, y + height], [x, y + height]
];
return this.polygon(points, o);
}
curve(points, o) {
const o1 = this._curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
const o2 = this._curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), o);
return { type: 'path', ops: o1.concat(o2) };
}
ellipse(x, y, width, height, o) {
const increment = (Math.PI * 2) / o.curveStepCount;
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
rx += this.getOffset(-rx * 0.05, rx * 0.05, o);
ry += this.getOffset(-ry * 0.05, ry * 0.05, o);
const o1 = this._ellipse(increment, x, y, rx, ry, 1, increment * this.getOffset(0.1, this.getOffset(0.4, 1, o), o), o);
const o2 = this._ellipse(increment, x, y, rx, ry, 1.5, 0, o);
return { type: 'path', ops: o1.concat(o2) };
}
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 += this.getOffset(-rx * 0.01, rx * 0.01, o);
ry += this.getOffset(-ry * 0.01, 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 o1 = this._arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
const o2 = this._arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
let ops = o1.concat(o2);
if (closed) {
if (roughClosure) {
ops = ops.concat(this.doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o));
ops = ops.concat(this.doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o));
}
else {
ops.push({ op: 'lineTo', data: [cx, cy] });
ops.push({ op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] });
}
}
return { type: 'path', ops };
}
svgPath(path, o) {
path = (path || '').replace(/\n/g, ' ').replace(/(-\s)/g, '-').replace('/(\s\s)/g', ' ');
let p = new RoughPath(path);
if (o.simplification) {
const fitter = new PathFitter(p.linearPoints, p.closed);
const d = fitter.fit(o.simplification);
p = new RoughPath(d);
}
let ops = [];
const segments = p.segments || [];
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
const prev = i > 0 ? segments[i - 1] : null;
const opList = this._processSegment(p, s, prev, o);
if (opList && opList.length) {
ops = ops.concat(opList);
}
}
return { type: 'path', ops };
}
solidFillPolygon(points, o) {
const ops = [];
if (points.length) {
const offset = o.maxRandomnessOffset || 0;
const len = points.length;
if (len > 2) {
ops.push({ op: 'move', data: [points[0][0] + this.getOffset(-offset, offset, o), points[0][1] + this.getOffset(-offset, offset, o)] });
for (let i = 1; i < len; i++) {
ops.push({ op: 'lineTo', data: [points[i][0] + this.getOffset(-offset, offset, o), points[i][1] + this.getOffset(-offset, offset, o)] });
}
}
}
return { type: 'fillPath', ops };
}
patternFillPolygon(points, o) {
const filler = getFiller(this, o);
return filler.fillPolygon(points, o);
}
patternFillEllipse(cx, cy, width, height, o) {
const filler = getFiller(this, o);
return filler.fillEllipse(cx, cy, width, height, o);
}
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 += this.getOffset(-rx * 0.01, rx * 0.01, o);
ry += this.getOffset(-ry * 0.01, 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 this.patternFillPolygon(points, o);
}
///
getOffset(min, max, ops) {
return ops.roughness * ((Math.random() * (max - min)) + min);
}
doubleLine(x1, y1, x2, y2, o) {
const o1 = this._line(x1, y1, x2, y2, o, true, false);
const o2 = this._line(x1, y1, x2, y2, o, true, true);
return o1.concat(o2);
}
_line(x1, y1, x2, y2, o, move, overlay) {
const lengthSq = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
let offset = o.maxRandomnessOffset || 0;
if ((offset * offset * 100) > lengthSq) {
offset = Math.sqrt(lengthSq) / 10;
}
const halfOffset = offset / 2;
const divergePoint = 0.2 + Math.random() * 0.2;
let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
let midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200;
midDispX = this.getOffset(-midDispX, midDispX, o);
midDispY = this.getOffset(-midDispY, midDispY, o);
const ops = [];
if (move) {
if (overlay) {
ops.push({
op: 'move', data: [
x1 + this.getOffset(-halfOffset, halfOffset, o),
y1 + this.getOffset(-halfOffset, halfOffset, o)
]
});
}
else {
ops.push({
op: 'move', data: [
x1 + this.getOffset(-offset, offset, o),
y1 + this.getOffset(-offset, offset, o)
]
});
}
}
if (overlay) {
ops.push({
op: 'bcurveTo', data: [
midDispX + x1 + (x2 - x1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
midDispY + y1 + (y2 - y1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
x2 + this.getOffset(-halfOffset, halfOffset, o),
y2 + this.getOffset(-halfOffset, halfOffset, o)
]
});
}
else {
ops.push({
op: 'bcurveTo', data: [
midDispX + x1 + (x2 - x1) * divergePoint + this.getOffset(-offset, offset, o),
midDispY + y1 + (y2 - y1) * divergePoint + this.getOffset(-offset, offset, o),
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this.getOffset(-offset, offset, o),
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this.getOffset(-offset, offset, o),
x2 + this.getOffset(-offset, offset, o),
y2 + this.getOffset(-offset, offset, o)
]
});
}
return ops;
}
_curve(points, closePoint, o) {
const len = points.length;
let 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] + this.getOffset(-ro, ro, o), closePoint[1] + +this.getOffset(-ro, 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 = ops.concat(this.doubleLine(points[0][0], points[0][1], points[1][0], points[1][1], o));
}
return ops;
}
_ellipse(increment, cx, cy, rx, ry, offset, overlap, o) {
const radOffset = this.getOffset(-0.5, 0.5, o) - (Math.PI / 2);
const points = [];
points.push([
this.getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
this.getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment)
]);
for (let angle = radOffset; angle < (Math.PI * 2 + radOffset - 0.01); angle = angle + increment) {
points.push([
this.getOffset(-offset, offset, o) + cx + rx * Math.cos(angle),
this.getOffset(-offset, offset, o) + cy + ry * Math.sin(angle)
]);
}
points.push([
this.getOffset(-offset, offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
this.getOffset(-offset, offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5)
]);
points.push([
this.getOffset(-offset, offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
this.getOffset(-offset, offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap)
]);
points.push([
this.getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
this.getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5)
]);
return this._curve(points, null, o);
}
_curveWithOffset(points, offset, o) {
const ps = [];
ps.push([
points[0][0] + this.getOffset(-offset, offset, o),
points[0][1] + this.getOffset(-offset, offset, o),
]);
ps.push([
points[0][0] + this.getOffset(-offset, offset, o),
points[0][1] + this.getOffset(-offset, offset, o),
]);
for (let i = 1; i < points.length; i++) {
ps.push([
points[i][0] + this.getOffset(-offset, offset, o),
points[i][1] + this.getOffset(-offset, offset, o),
]);
if (i === (points.length - 1)) {
ps.push([
points[i][0] + this.getOffset(-offset, offset, o),
points[i][1] + this.getOffset(-offset, offset, o),
]);
}
}
return this._curve(ps, null, o);
}
_arc(increment, cx, cy, rx, ry, strt, stp, offset, o) {
const radOffset = strt + this.getOffset(-0.1, 0.1, o);
const points = [];
points.push([
this.getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
this.getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment)
]);
for (let angle = radOffset; angle <= stp; angle = angle + increment) {
points.push([
this.getOffset(-offset, offset, o) + cx + rx * Math.cos(angle),
this.getOffset(-offset, 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 this._curve(points, null, o);
}
_bezierTo(x1, y1, x2, y2, x, y, path, o) {
const ops = [];
const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.5];
let f = [0, 0];
for (let i = 0; i < 2; i++) {
if (i === 0) {
ops.push({ op: 'move', data: [path.x, path.y] });
}
else {
ops.push({ op: 'move', data: [path.x + this.getOffset(-ros[0], ros[0], o), path.y + this.getOffset(-ros[0], ros[0], o)] });
}
f = [x + this.getOffset(-ros[i], ros[i], o), y + this.getOffset(-ros[i], ros[i], o)];
ops.push({
op: 'bcurveTo', data: [
x1 + this.getOffset(-ros[i], ros[i], o), y1 + this.getOffset(-ros[i], ros[i], o),
x2 + this.getOffset(-ros[i], ros[i], o), y2 + this.getOffset(-ros[i], ros[i], o),
f[0], f[1]
]
});
}
path.setPosition(f[0], f[1]);
return ops;
}
_processSegment(path, seg, prevSeg, o) {
let ops = [];
switch (seg.key) {
case 'M':
case 'm': {
const delta = seg.key === 'm';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
const ro = 1 * (o.maxRandomnessOffset || 0);
x = x + this.getOffset(-ro, ro, o);
y = y + this.getOffset(-ro, ro, o);
path.setPosition(x, y);
ops.push({ op: 'move', data: [x, y] });
}
break;
}
case 'L':
case 'l': {
const delta = seg.key === 'l';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
ops = ops.concat(this.doubleLine(path.x, path.y, x, y, o));
path.setPosition(x, y);
}
break;
}
case 'H':
case 'h': {
const delta = seg.key === 'h';
if (seg.data.length) {
let x = +seg.data[0];
if (delta) {
x += path.x;
}
ops = ops.concat(this.doubleLine(path.x, path.y, x, path.y, o));
path.setPosition(x, path.y);
}
break;
}
case 'V':
case 'v': {
const delta = seg.key === 'v';
if (seg.data.length) {
let y = +seg.data[0];
if (delta) {
y += path.y;
}
ops = ops.concat(this.doubleLine(path.x, path.y, path.x, y, o));
path.setPosition(path.x, y);
}
break;
}
case 'Z':
case 'z': {
if (path.first) {
ops = ops.concat(this.doubleLine(path.x, path.y, path.first[0], path.first[1], o));
path.setPosition(path.first[0], path.first[1]);
path.first = null;
}
break;
}
case 'C':
case 'c': {
const delta = seg.key === 'c';
if (seg.data.length >= 6) {
let x1 = +seg.data[0];
let y1 = +seg.data[1];
let x2 = +seg.data[2];
let y2 = +seg.data[3];
let x = +seg.data[4];
let y = +seg.data[5];
if (delta) {
x1 += path.x;
x2 += path.x;
x += path.x;
y1 += path.y;
y2 += path.y;
y += path.y;
}
const ob = this._bezierTo(x1, y1, x2, y2, x, y, path, o);
ops = ops.concat(ob);
path.bezierReflectionPoint = [x + (x - x2), y + (y - y2)];
}
break;
}
case 'S':
case 's': {
const delta = seg.key === 's';
if (seg.data.length >= 4) {
let x2 = +seg.data[0];
let y2 = +seg.data[1];
let x = +seg.data[2];
let y = +seg.data[3];
if (delta) {
x2 += path.x;
x += path.x;
y2 += path.y;
y += path.y;
}
let x1 = x2;
let y1 = y2;
const prevKey = prevSeg ? prevSeg.key : '';
let ref = null;
if (prevKey === 'c' || prevKey === 'C' || prevKey === 's' || prevKey === 'S') {
ref = path.bezierReflectionPoint;
}
if (ref) {
x1 = ref[0];
y1 = ref[1];
}
const ob = this._bezierTo(x1, y1, x2, y2, x, y, path, o);
ops = ops.concat(ob);
path.bezierReflectionPoint = [x + (x - x2), y + (y - y2)];
}
break;
}
case 'Q':
case 'q': {
const delta = seg.key === 'q';
if (seg.data.length >= 4) {
let x1 = +seg.data[0];
let y1 = +seg.data[1];
let x = +seg.data[2];
let y = +seg.data[3];
if (delta) {
x1 += path.x;
x += path.x;
y1 += path.y;
y += path.y;
}
const offset1 = 1 * (1 + o.roughness * 0.2);
const offset2 = 1.5 * (1 + o.roughness * 0.22);
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset1, offset1, o), path.y + this.getOffset(-offset1, offset1, o)] });
let f = [x + this.getOffset(-offset1, offset1, o), y + this.getOffset(-offset1, offset1, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset1, offset1, o), y1 + this.getOffset(-offset1, offset1, o),
f[0], f[1]
]
});
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset2, offset2, o), path.y + this.getOffset(-offset2, offset2, o)] });
f = [x + this.getOffset(-offset2, offset2, o), y + this.getOffset(-offset2, offset2, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset2, offset2, o), y1 + this.getOffset(-offset2, offset2, o),
f[0], f[1]
]
});
path.setPosition(f[0], f[1]);
path.quadReflectionPoint = [x + (x - x1), y + (y - y1)];
}
break;
}
case 'T':
case 't': {
const delta = seg.key === 't';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
let x1 = x;
let y1 = y;
const prevKey = prevSeg ? prevSeg.key : '';
let ref = null;
if (prevKey === 'q' || prevKey === 'Q' || prevKey === 't' || prevKey === 'T') {
ref = path.quadReflectionPoint;
}
if (ref) {
x1 = ref[0];
y1 = ref[1];
}
const offset1 = 1 * (1 + o.roughness * 0.2);
const offset2 = 1.5 * (1 + o.roughness * 0.22);
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset1, offset1, o), path.y + this.getOffset(-offset1, offset1, o)] });
let f = [x + this.getOffset(-offset1, offset1, o), y + this.getOffset(-offset1, offset1, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset1, offset1, o), y1 + this.getOffset(-offset1, offset1, o),
f[0], f[1]
]
});
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset2, offset2, o), path.y + this.getOffset(-offset2, offset2, o)] });
f = [x + this.getOffset(-offset2, offset2, o), y + this.getOffset(-offset2, offset2, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset2, offset2, o), y1 + this.getOffset(-offset2, offset2, o),
f[0], f[1]
]
});
path.setPosition(f[0], f[1]);
path.quadReflectionPoint = [x + (x - x1), y + (y - y1)];
}
break;
}
case 'A':
case 'a': {
const delta = seg.key === 'a';
if (seg.data.length >= 7) {
const rx = +seg.data[0];
const ry = +seg.data[1];
const angle = +seg.data[2];
const largeArcFlag = +seg.data[3];
const sweepFlag = +seg.data[4];
let x = +seg.data[5];
let y = +seg.data[6];
if (delta) {
x += path.x;
y += path.y;
}
if (x === path.x && y === path.y) {
break;
}
if (rx === 0 || ry === 0) {
ops = ops.concat(this.doubleLine(path.x, path.y, x, y, o));
path.setPosition(x, y);
}
else {
for (let i = 0; i < 1; i++) {
const arcConverter = new RoughArcConverter([path.x, path.y], [x, y], [rx, ry], angle, largeArcFlag ? true : false, sweepFlag ? true : false);
let segment = arcConverter.getNextSegment();
while (segment) {
const ob = this._bezierTo(segment.cp1[0], segment.cp1[1], segment.cp2[0], segment.cp2[1], segment.to[0], segment.to[1], path, o);
ops = ops.concat(ob);
segment = arcConverter.getNextSegment();
}
}
}
}
break;
}
default:
break;
}
return ops;
}
}

40
bin/renderer.d.ts vendored
View File

@@ -1,25 +1,17 @@
import { ResolvedOptions, OpSet, Op } from './core';
import { ResolvedOptions, Op, OpSet } from './core';
import { Point } from './geometry';
export declare class RoughRenderer {
line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): OpSet;
linearPath(points: Point[], close: boolean, o: ResolvedOptions): OpSet;
polygon(points: Point[], o: ResolvedOptions): OpSet;
rectangle(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
curve(points: Point[], o: ResolvedOptions): OpSet;
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean, roughClosure: boolean, o: ResolvedOptions): OpSet;
svgPath(path: string, o: ResolvedOptions): OpSet;
solidFillPolygon(points: Point[], o: ResolvedOptions): OpSet;
patternFillPolygon(points: Point[], o: ResolvedOptions): OpSet;
patternFillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet;
patternFillArc(x: number, y: number, width: number, height: number, start: number, stop: number, o: ResolvedOptions): OpSet;
getOffset(min: number, max: number, ops: ResolvedOptions): number;
doubleLine(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
private _line;
private _curve;
private _ellipse;
private _curveWithOffset;
private _arc;
private _bezierTo;
private _processSegment;
}
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(points: Point[], o: ResolvedOptions): OpSet;
export declare function ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
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(points: Point[], o: ResolvedOptions): OpSet;
export declare function patternFillPolygon(points: Point[], o: ResolvedOptions): OpSet;
export declare function patternFillEllipse(cx: number, cy: number, width: number, height: number, 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 doubleLineOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];

File diff suppressed because it is too large Load Diff

11
bin/rough.d.ts vendored
View File

@@ -1,15 +1,10 @@
import { Config, DrawingSurface } from './core';
import { RoughCanvas } from './canvas';
import { RoughRenderer } from './renderer';
import { RoughGenerator } from './generator';
import { RoughGeneratorAsync } from './generator-async';
import { RoughCanvasAsync } from './canvas-async';
import { RoughSVG } from './svg';
import { RoughSVGAsync } from './svg-async';
declare const _default: {
canvas(canvas: HTMLCanvasElement, config?: Config | undefined): RoughCanvasAsync | RoughCanvas;
svg(svg: SVGSVGElement, config?: Config | undefined): RoughSVG | RoughSVGAsync;
createRenderer(): RoughRenderer;
generator(config: Config | null, surface: DrawingSurface): RoughGeneratorAsync | RoughGenerator;
canvas(canvas: HTMLCanvasElement, config?: Config | undefined): RoughCanvas;
svg(svg: SVGSVGElement, config?: Config | undefined): RoughSVG;
generator(config: Config | null, surface: DrawingSurface): RoughGenerator;
};
export default _default;

View File

@@ -1,29 +1,14 @@
import { RoughCanvas } from './canvas';
import { RoughGenerator } from './generator';
import { RoughGeneratorAsync } from './generator-async';
import { RoughCanvasAsync } from './canvas-async';
import { RoughSVG } from './svg';
import { RoughSVGAsync } from './svg-async';
export default {
canvas(canvas, config) {
if (config && config.async) {
return new RoughCanvasAsync(canvas, config);
}
return new RoughCanvas(canvas, config);
},
svg(svg, config) {
if (config && config.async) {
return new RoughSVGAsync(svg, config);
}
return new RoughSVG(svg, config);
},
createRenderer() {
return RoughCanvas.createRenderer();
},
generator(config, surface) {
if (config && config.async) {
return new RoughGeneratorAsync(config, surface);
}
return new RoughGenerator(config, surface);
}
};

2
bin/svg-base.d.ts vendored
View File

@@ -1,12 +1,10 @@
import { Drawable, OpSet, ResolvedOptions } from './core';
import { RoughRenderer } from './renderer';
export declare abstract class RoughSVGBase {
protected svg: SVGSVGElement;
protected _defs?: SVGDefsElement;
constructor(svg: SVGSVGElement);
abstract getDefaultOptions(): ResolvedOptions;
abstract opsToPath(drawing: OpSet): string;
static createRenderer(): RoughRenderer;
readonly defs: SVGDefsElement | null;
draw(drawable: Drawable): SVGGElement;
private fillSketch;

View File

@@ -1,12 +1,8 @@
import { RoughRenderer } from './renderer';
const hasDocument = typeof document !== 'undefined';
export class RoughSVGBase {
constructor(svg) {
this.svg = svg;
}
static createRenderer() {
return new RoughRenderer();
}
get defs() {
const doc = this.svg.ownerDocument || (hasDocument && document);
if (doc) {
@@ -26,7 +22,7 @@ export class RoughSVGBase {
draw(drawable) {
const sets = drawable.sets || [];
const o = drawable.options || this.getDefaultOptions();
const doc = this.svg.ownerDocument || (hasDocument && document);
const doc = this.svg.ownerDocument || window.document;
const g = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
for (const drawing of sets) {
let path = null;

1404
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,15 +29,8 @@
},
"homepage": "https://roughjs.com",
"devDependencies": {
"babel-plugin-external-helpers": "^6.22.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.24.1",
"babelrc-rollup": "^3.0.0",
"rollup": "^0.60.7",
"rollup-plugin-babel": "^3.0.4",
"rollup-plugin-babel-minify": "^5.0.0",
"tslint": "^5.10.0",
"typescript": "^2.9.2"
"rollup": "^0.68.1",
"tslint": "^5.12.0",
"typescript": "^3.2.2"
}
}
}

View File

@@ -1,5 +1,4 @@
import { ResolvedOptions, Drawable, OpSet } from './core';
import { RoughRenderer } from './renderer';
const hasDocument = typeof document !== 'undefined';
@@ -12,10 +11,6 @@ export abstract class RoughCanvasBase {
this.ctx = this.canvas.getContext('2d')!;
}
static createRenderer(): RoughRenderer {
return new RoughRenderer();
}
abstract getDefaultOptions(): ResolvedOptions;
draw(drawable: Drawable) {
@@ -65,7 +60,7 @@ export abstract class RoughCanvasBase {
}
this.fillSketch(hcontext, drawing, o);
this.ctx.save();
this.ctx.fillStyle = this.ctx.createPattern(hcanvas, 'repeat');
this.ctx.fillStyle = this.ctx.createPattern(hcanvas, 'repeat')!;
const p2d = new Path2D(drawing.path);
this.ctx.fill(p2d);
this.ctx.restore();

View File

@@ -1,15 +1,10 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { PatternFiller } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line } from '../geometry';
import { hachureLinesForPolygon, hachureLinesForEllipse, lineLength } from './filler-utils';
import { randOffsetWithRange, ellipse } from '../renderer';
export class DotFiller implements PatternFiller {
renderer: RenderHelper;
constructor(renderer: RenderHelper) {
this.renderer = renderer;
}
fillPolygon(points: Point[], o: ResolvedOptions): OpSet {
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0 });
const lines = hachureLinesForPolygon(points, o);
@@ -18,7 +13,7 @@ export class DotFiller implements PatternFiller {
fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet {
o = Object.assign({}, o, { curveStepCount: 4, hachureAngle: 0 });
const lines = hachureLinesForEllipse(cx, cy, width, height, o, this.renderer);
const lines = hachureLinesForEllipse(cx, cy, width, height, o);
return this.dotsOnLines(lines, o);
}
@@ -43,10 +38,10 @@ export class DotFiller implements PatternFiller {
const dy = l * Math.sin(alpha);
const dx = l * Math.cos(alpha);
const c: Point = [line[0][0] - dx, line[0][1] + dy];
const cx = this.renderer.getOffset(c[0] - gap / 4, c[0] + gap / 4, o);
const cy = this.renderer.getOffset(c[1] - gap / 4, c[1] + gap / 4, o);
const ellipse = this.renderer.ellipse(cx, cy, fweight, fweight, o);
ops = ops.concat(ellipse.ops);
const cx = randOffsetWithRange(c[0] - gap / 4, c[0] + gap / 4, o);
const cy = randOffsetWithRange(c[1] - gap / 4, c[1] + gap / 4, o);
const el = ellipse(cx, cy, fweight, fweight, o);
ops = ops.concat(el.ops);
}
}
return { type: 'fillSketch', ops };

View File

@@ -1,13 +1,7 @@
import { ResolvedOptions, OpSet, Op } from '../core';
import { ResolvedOptions, OpSet } from '../core';
import { Point } from '../geometry';
export interface PatternFiller {
fillPolygon(points: Point[], o: ResolvedOptions): OpSet;
fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet;
}
export interface RenderHelper {
doubleLine(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
getOffset(min: number, max: number, ops: ResolvedOptions): number;
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
}

View File

@@ -1,7 +1,7 @@
import { Point, Segment, Line } from '../geometry';
import { ResolvedOptions } from '../core';
import { HachureIterator } from '../utils/hachure';
import { RenderHelper } from './filler-interface';
import { randOffset } from '../renderer';
export function lineLength(line: Line): number {
const p1 = line[0];
@@ -74,12 +74,12 @@ export function hachureLinesForPolygon(points: Point[], o: ResolvedOptions): Lin
return ret;
}
export function hachureLinesForEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions, renderer: RenderHelper): Line[] {
export function hachureLinesForEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): Line[] {
const ret: Line[] = [];
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
rx += renderer.getOffset(-rx * 0.05, rx * 0.05, o);
ry += renderer.getOffset(-ry * 0.05, ry * 0.05, o);
rx += randOffset(rx * 0.05, o);
ry += randOffset(ry * 0.05, o);
const angle = o.hachureAngle;
let gap = o.hachureGap;
if (gap <= 0) {

View File

@@ -1,5 +1,5 @@
import { ResolvedOptions } from '../core';
import { PatternFiller, RenderHelper } from './filler-interface';
import { PatternFiller } from './filler-interface';
import { HachureFiller } from './hachure-filler';
import { ZigZagFiller } from './zigzag-filler';
import { HatchFiller } from './hatch-filler';
@@ -7,30 +7,30 @@ import { DotFiller } from './dot-filler';
const fillers: { [name: string]: PatternFiller } = {};
export function getFiller(renderer: RenderHelper, o: ResolvedOptions): PatternFiller {
export function getFiller(o: ResolvedOptions): PatternFiller {
let fillerName = o.fillStyle || 'hachure';
if (!fillers[fillerName]) {
switch (fillerName) {
case 'zigzag':
if (!fillers[fillerName]) {
fillers[fillerName] = new ZigZagFiller(renderer);
fillers[fillerName] = new ZigZagFiller();
}
break;
case 'cross-hatch':
if (!fillers[fillerName]) {
fillers[fillerName] = new HatchFiller(renderer);
fillers[fillerName] = new HatchFiller();
}
break;
case 'dots':
if (!fillers[fillerName]) {
fillers[fillerName] = new DotFiller(renderer);
fillers[fillerName] = new DotFiller();
}
break;
case 'hachure':
default:
fillerName = 'hachure';
if (!fillers[fillerName]) {
fillers[fillerName] = new HachureFiller(renderer);
fillers[fillerName] = new HachureFiller();
}
break;
}

View File

@@ -1,15 +1,10 @@
import { PatternFiller, RenderHelper } from './filler-interface';
import { PatternFiller } from './filler-interface';
import { ResolvedOptions, OpSet, Op } from '../core';
import { Point, Line } from '../geometry';
import { hachureLinesForPolygon, hachureLinesForEllipse } from './filler-utils';
import { doubleLineOps } from '../renderer';
export class HachureFiller implements PatternFiller {
renderer: RenderHelper;
constructor(renderer: RenderHelper) {
this.renderer = renderer;
}
fillPolygon(points: Point[], o: ResolvedOptions): OpSet {
return this._fillPolygon(points, o);
}
@@ -25,7 +20,7 @@ export class HachureFiller implements PatternFiller {
}
protected _fillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions, connectEnds: boolean = false): OpSet {
const lines = hachureLinesForEllipse(cx, cy, width, height, o, this.renderer);
const lines = hachureLinesForEllipse(cx, cy, width, height, o);
const ops = this.renderLines(lines, o, connectEnds);
return { type: 'fillSketch', ops };
}
@@ -34,9 +29,9 @@ export class HachureFiller implements PatternFiller {
let ops: Op[] = [];
let prevPoint: Point | null = null;
for (const line of lines) {
ops = ops.concat(this.renderer.doubleLine(line[0][0], line[0][1], line[1][0], line[1][1], o));
ops = ops.concat(doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
if (connectEnds && prevPoint) {
ops = ops.concat(this.renderer.doubleLine(prevPoint[0], prevPoint[1], line[0][0], line[0][1], o));
ops = ops.concat(doubleLineOps(prevPoint[0], prevPoint[1], line[0][0], line[0][1], o));
}
prevPoint = line[1];
}

View File

@@ -1,14 +1,12 @@
import { RoughRenderer } from './renderer.js';
import { Config, DrawingSurface, Options, ResolvedOptions, Drawable, OpSet, PathInfo, PatternInfo } from './core';
import { Point } from './geometry.js';
import { createRenderer } from './renderer-factory.js';
const hasSelf = typeof self !== 'undefined';
export abstract class RoughGeneratorBase {
protected config: Config;
protected surface: DrawingSurface;
protected renderer: RoughRenderer;
defaultOptions: ResolvedOptions = {
maxRandomnessOffset: 2,
roughness: 1,
@@ -26,7 +24,6 @@ export abstract class RoughGeneratorBase {
constructor(config: Config | null, surface: DrawingSurface) {
this.config = config || {};
this.surface = surface;
this.renderer = createRenderer(this.config);
if (this.config.options) {
this.defaultOptions = this._options(this.config.options);
}
@@ -40,10 +37,6 @@ export abstract class RoughGeneratorBase {
return { shape, sets: sets || [], options: options || this.defaultOptions };
}
protected get lib(): RoughRenderer {
return this.renderer;
}
private getCanvasSize(): Point {
const val = (w: any): number => {
if (w && typeof w === 'object') {

View File

@@ -1,6 +1,7 @@
import { Config, DrawingSurface, Options, Drawable, OpSet } from './core';
import { Point } from './geometry.js';
import { RoughGeneratorBase } from './generator-base';
import { RoughGeneratorBase } from './generator-base.js';
import { line, solidFillPolygon, patternFillPolygon, rectangle, ellipse, patternFillEllipse, linearPath, arc, patternFillArc, curve, svgPath } from './renderer.js';
export class RoughGenerator extends RoughGeneratorBase {
constructor(config: Config | null, surface: DrawingSurface) {
@@ -9,7 +10,7 @@ export class RoughGenerator extends RoughGeneratorBase {
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable {
const o = this._options(options);
return this._drawable('line', [this.lib.line(x1, y1, x2, y2, o)], o);
return this._drawable('line', [line(x1, y1, x2, y2, o)], o);
}
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable {
@@ -18,12 +19,12 @@ export class RoughGenerator extends RoughGeneratorBase {
if (o.fill) {
const points: Point[] = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]];
if (o.fillStyle === 'solid') {
paths.push(this.lib.solidFillPolygon(points, o));
paths.push(solidFillPolygon(points, o));
} else {
paths.push(this.lib.patternFillPolygon(points, o));
paths.push(patternFillPolygon(points, o));
}
}
paths.push(this.lib.rectangle(x, y, width, height, o));
paths.push(rectangle(x, y, width, height, o));
return this._drawable('rectangle', paths, o);
}
@@ -32,14 +33,14 @@ export class RoughGenerator extends RoughGeneratorBase {
const paths = [];
if (o.fill) {
if (o.fillStyle === 'solid') {
const shape = this.lib.ellipse(x, y, width, height, o);
const shape = ellipse(x, y, width, height, o);
shape.type = 'fillPath';
paths.push(shape);
} else {
paths.push(this.lib.patternFillEllipse(x, y, width, height, o));
paths.push(patternFillEllipse(x, y, width, height, o));
}
}
paths.push(this.lib.ellipse(x, y, width, height, o));
paths.push(ellipse(x, y, width, height, o));
return this._drawable('ellipse', paths, o);
}
@@ -51,7 +52,7 @@ export class RoughGenerator extends RoughGeneratorBase {
linearPath(points: Point[], options?: Options): Drawable {
const o = this._options(options);
return this._drawable('linearPath', [this.lib.linearPath(points, false, o)], o);
return this._drawable('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 {
@@ -59,20 +60,20 @@ export class RoughGenerator extends RoughGeneratorBase {
const paths = [];
if (closed && o.fill) {
if (o.fillStyle === 'solid') {
const shape = this.lib.arc(x, y, width, height, start, stop, true, false, o);
const shape = arc(x, y, width, height, start, stop, true, false, o);
shape.type = 'fillPath';
paths.push(shape);
} else {
paths.push(this.lib.patternFillArc(x, y, width, height, start, stop, o));
paths.push(patternFillArc(x, y, width, height, start, stop, o));
}
}
paths.push(this.lib.arc(x, y, width, height, start, stop, closed, true, o));
paths.push(arc(x, y, width, height, start, stop, closed, true, o));
return this._drawable('arc', paths, o);
}
curve(points: Point[], options?: Options): Drawable {
const o = this._options(options);
return this._drawable('curve', [this.lib.curve(points, o)], o);
return this._drawable('curve', [curve(points, o)], o);
}
polygon(points: Point[], options?: Options): Drawable {
@@ -80,7 +81,7 @@ export class RoughGenerator extends RoughGeneratorBase {
const paths = [];
if (o.fill) {
if (o.fillStyle === 'solid') {
paths.push(this.lib.solidFillPolygon(points, o));
paths.push(solidFillPolygon(points, o));
} else {
const size = this.computePolygonSize(points);
const fillPoints: Point[] = [
@@ -89,14 +90,14 @@ export class RoughGenerator extends RoughGeneratorBase {
[size[0], size[1]],
[0, size[1]]
];
const shape = this.lib.patternFillPolygon(fillPoints, o);
const shape = patternFillPolygon(fillPoints, o);
shape.type = 'path2Dpattern';
shape.size = size;
shape.path = this.polygonPath(points);
paths.push(shape);
}
}
paths.push(this.lib.linearPath(points, true, o));
paths.push(linearPath(points, true, o));
return this._drawable('polygon', paths, o);
}
@@ -118,14 +119,14 @@ export class RoughGenerator extends RoughGeneratorBase {
[size[0], size[1]],
[0, size[1]]
];
const shape = this.lib.patternFillPolygon(points, o);
const shape = patternFillPolygon(points, o);
shape.type = 'path2Dpattern';
shape.size = size;
shape.path = d;
paths.push(shape);
}
}
paths.push(this.lib.svgPath(d, o));
paths.push(svgPath(d, o));
return this._drawable('path', paths, o);
}
}

View File

@@ -1,17 +0,0 @@
import { Config } from './core';
import { RoughRenderer } from './renderer';
const hasSelf = typeof self !== 'undefined';
const roughScript = hasSelf && self && self.document && self.document.currentScript && (self.document.currentScript as HTMLScriptElement).src;
export function createRenderer(config: Config): RoughRenderer {
if (hasSelf && roughScript && self && (self as any).workly && config.async && (!config.noWorker)) {
const worklySource = config.worklyURL || 'https://cdn.jsdelivr.net/gh/pshihn/workly/dist/workly.min.js';
if (worklySource) {
const code = `importScripts('${worklySource}', '${roughScript}');\nworkly.expose(self.rough.createRenderer());`;
const ourl = URL.createObjectURL(new Blob([code]));
return (self as any).workly.proxy(ourl);
}
}
return new RoughRenderer();
}

View File

@@ -0,0 +1,17 @@
// import { Config } from './core';
// import { RoughRenderer } from './renderer';
// const hasSelf = typeof self !== 'undefined';
// const roughScript = hasSelf && self && self.document && self.document.currentScript && (self.document.currentScript as HTMLScriptElement).src;
// export function createRenderer(config: Config): RoughRenderer {
// if (hasSelf && roughScript && self && (self as any).workly && config.async && (!config.noWorker)) {
// const worklySource = config.worklyURL || 'https://cdn.jsdelivr.net/gh/pshihn/workly/dist/workly.min.js';
// if (worklySource) {
// const code = `importScripts('${worklySource}', '${roughScript}');\nworkly.expose(self.rough.createRenderer());`;
// const ourl = URL.createObjectURL(new Blob([code]));
// return (self as any).workly.proxy(ourl);
// }
// }
// return new RoughRenderer();
// }

File diff suppressed because it is too large Load Diff

619
src/renderer.ts.old Normal file
View File

@@ -0,0 +1,619 @@
import { ResolvedOptions, OpSet, Op } from './core';
import { RoughPath, RoughArcConverter, PathFitter, Segment } from './path.js';
import { Point } from './geometry';
import { getFiller } from './fillers/filler';
export class RoughRenderer {
line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): OpSet {
const ops = this.doubleLine(x1, y1, x2, y2, o);
return { type: 'path', ops };
}
linearPath(points: Point[], close: boolean, o: ResolvedOptions): OpSet {
const len = (points || []).length;
if (len > 2) {
let ops: Op[] = [];
for (let i = 0; i < (len - 1); i++) {
ops = ops.concat(this.doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
}
if (close) {
ops = ops.concat(this.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 this.line(points[0][0], points[0][1], points[1][0], points[1][1], o);
}
return { type: 'path', ops: [] };
}
polygon(points: Point[], o: ResolvedOptions): OpSet {
return this.linearPath(points, true, o);
}
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 this.polygon(points, o);
}
curve(points: Point[], o: ResolvedOptions): OpSet {
const o1 = this._curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
const o2 = this._curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), o);
return { type: 'path', ops: o1.concat(o2) };
}
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet {
const increment = (Math.PI * 2) / o.curveStepCount;
let rx = Math.abs(width / 2);
let ry = Math.abs(height / 2);
rx += this.getOffset(-rx * 0.05, rx * 0.05, o);
ry += this.getOffset(-ry * 0.05, ry * 0.05, o);
const o1 = this._ellipse(increment, x, y, rx, ry, 1, increment * this.getOffset(0.1, this.getOffset(0.4, 1, o), o), o);
const o2 = this._ellipse(increment, x, y, rx, ry, 1.5, 0, o);
return { type: 'path', ops: o1.concat(o2) };
}
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 += this.getOffset(-rx * 0.01, rx * 0.01, o);
ry += this.getOffset(-ry * 0.01, 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 o1 = this._arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
const o2 = this._arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
let ops = o1.concat(o2);
if (closed) {
if (roughClosure) {
ops = ops.concat(this.doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o));
ops = ops.concat(this.doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o));
} else {
ops.push({ op: 'lineTo', data: [cx, cy] });
ops.push({ op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] });
}
}
return { type: 'path', ops };
}
svgPath(path: string, o: ResolvedOptions): OpSet {
path = (path || '').replace(/\n/g, ' ').replace(/(-\s)/g, '-').replace('/(\s\s)/g', ' ');
let p = new RoughPath(path);
if (o.simplification) {
const fitter = new PathFitter(p.linearPoints, p.closed);
const d = fitter.fit(o.simplification);
p = new RoughPath(d);
}
let ops: Op[] = [];
const segments = p.segments || [];
for (let i = 0; i < segments.length; i++) {
const s = segments[i];
const prev = i > 0 ? segments[i - 1] : null;
const opList = this._processSegment(p, s, prev, o);
if (opList && opList.length) {
ops = ops.concat(opList);
}
}
return { type: 'path', ops };
}
solidFillPolygon(points: Point[], o: ResolvedOptions): OpSet {
const ops: Op[] = [];
if (points.length) {
const offset = o.maxRandomnessOffset || 0;
const len = points.length;
if (len > 2) {
ops.push({ op: 'move', data: [points[0][0] + this.getOffset(-offset, offset, o), points[0][1] + this.getOffset(-offset, offset, o)] });
for (let i = 1; i < len; i++) {
ops.push({ op: 'lineTo', data: [points[i][0] + this.getOffset(-offset, offset, o), points[i][1] + this.getOffset(-offset, offset, o)] });
}
}
}
return { type: 'fillPath', ops };
}
patternFillPolygon(points: Point[], o: ResolvedOptions): OpSet {
const filler = getFiller(this, o);
return filler.fillPolygon(points, o);
}
patternFillEllipse(cx: number, cy: number, width: number, height: number, o: ResolvedOptions): OpSet {
const filler = getFiller(this, o);
return filler.fillEllipse(cx, cy, width, height, o);
}
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 += this.getOffset(-rx * 0.01, rx * 0.01, o);
ry += this.getOffset(-ry * 0.01, 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 this.patternFillPolygon(points, o);
}
///
getOffset(min: number, max: number, ops: ResolvedOptions): number {
return ops.roughness * ((Math.random() * (max - min)) + min);
}
doubleLine(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[] {
const o1 = this._line(x1, y1, x2, y2, o, true, false);
const o2 = this._line(x1, y1, x2, y2, o, true, true);
return o1.concat(o2);
}
private _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);
let offset = o.maxRandomnessOffset || 0;
if ((offset * offset * 100) > lengthSq) {
offset = Math.sqrt(lengthSq) / 10;
}
const halfOffset = offset / 2;
const divergePoint = 0.2 + Math.random() * 0.2;
let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
let midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200;
midDispX = this.getOffset(-midDispX, midDispX, o);
midDispY = this.getOffset(-midDispY, midDispY, o);
const ops: Op[] = [];
if (move) {
if (overlay) {
ops.push({
op: 'move', data: [
x1 + this.getOffset(-halfOffset, halfOffset, o),
y1 + this.getOffset(-halfOffset, halfOffset, o)
]
});
} else {
ops.push({
op: 'move', data: [
x1 + this.getOffset(-offset, offset, o),
y1 + this.getOffset(-offset, offset, o)
]
});
}
}
if (overlay) {
ops.push({
op: 'bcurveTo', data: [
midDispX + x1 + (x2 - x1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
midDispY + y1 + (y2 - y1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this.getOffset(-halfOffset, halfOffset, o),
x2 + this.getOffset(-halfOffset, halfOffset, o),
y2 + this.getOffset(-halfOffset, halfOffset, o)
]
});
} else {
ops.push({
op: 'bcurveTo', data: [
midDispX + x1 + (x2 - x1) * divergePoint + this.getOffset(-offset, offset, o),
midDispY + y1 + (y2 - y1) * divergePoint + this.getOffset(-offset, offset, o),
midDispX + x1 + 2 * (x2 - x1) * divergePoint + this.getOffset(-offset, offset, o),
midDispY + y1 + 2 * (y2 - y1) * divergePoint + this.getOffset(-offset, offset, o),
x2 + this.getOffset(-offset, offset, o),
y2 + this.getOffset(-offset, offset, o)
]
});
}
return ops;
}
private _curve(points: Point[], closePoint: Point | null, o: ResolvedOptions): Op[] {
const len = points.length;
let 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] + this.getOffset(-ro, ro, o), closePoint[1] + + this.getOffset(-ro, 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 = ops.concat(this.doubleLine(points[0][0], points[0][1], points[1][0], points[1][1], o));
}
return ops;
}
private _ellipse(increment: number, cx: number, cy: number, rx: number, ry: number, offset: number, overlap: number, o: ResolvedOptions): Op[] {
const radOffset = this.getOffset(-0.5, 0.5, o) - (Math.PI / 2);
const points: Point[] = [];
points.push([
this.getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
this.getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment)
]);
for (let angle = radOffset; angle < (Math.PI * 2 + radOffset - 0.01); angle = angle + increment) {
points.push([
this.getOffset(-offset, offset, o) + cx + rx * Math.cos(angle),
this.getOffset(-offset, offset, o) + cy + ry * Math.sin(angle)
]);
}
points.push([
this.getOffset(-offset, offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
this.getOffset(-offset, offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5)
]);
points.push([
this.getOffset(-offset, offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
this.getOffset(-offset, offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap)
]);
points.push([
this.getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
this.getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5)
]);
return this._curve(points, null, o);
}
private _curveWithOffset(points: Point[], offset: number, o: ResolvedOptions): Op[] {
const ps: Point[] = [];
ps.push([
points[0][0] + this.getOffset(-offset, offset, o),
points[0][1] + this.getOffset(-offset, offset, o),
]);
ps.push([
points[0][0] + this.getOffset(-offset, offset, o),
points[0][1] + this.getOffset(-offset, offset, o),
]);
for (let i = 1; i < points.length; i++) {
ps.push([
points[i][0] + this.getOffset(-offset, offset, o),
points[i][1] + this.getOffset(-offset, offset, o),
]);
if (i === (points.length - 1)) {
ps.push([
points[i][0] + this.getOffset(-offset, offset, o),
points[i][1] + this.getOffset(-offset, offset, o),
]);
}
}
return this._curve(ps, null, o);
}
private _arc(increment: number, cx: number, cy: number, rx: number, ry: number, strt: number, stp: number, offset: number, o: ResolvedOptions) {
const radOffset = strt + this.getOffset(-0.1, 0.1, o);
const points: Point[] = [];
points.push([
this.getOffset(-offset, offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
this.getOffset(-offset, offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment)
]);
for (let angle = radOffset; angle <= stp; angle = angle + increment) {
points.push([
this.getOffset(-offset, offset, o) + cx + rx * Math.cos(angle),
this.getOffset(-offset, 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 this._curve(points, null, o);
}
private _bezierTo(x1: number, y1: number, x2: number, y2: number, x: number, y: number, path: RoughPath, o: ResolvedOptions): Op[] {
const ops: Op[] = [];
const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.5];
let f: Point = [0, 0];
for (let i = 0; i < 2; i++) {
if (i === 0) {
ops.push({ op: 'move', data: [path.x, path.y] });
} else {
ops.push({ op: 'move', data: [path.x + this.getOffset(-ros[0], ros[0], o), path.y + this.getOffset(-ros[0], ros[0], o)] });
}
f = [x + this.getOffset(-ros[i], ros[i], o), y + this.getOffset(-ros[i], ros[i], o)];
ops.push({
op: 'bcurveTo', data: [
x1 + this.getOffset(-ros[i], ros[i], o), y1 + this.getOffset(-ros[i], ros[i], o),
x2 + this.getOffset(-ros[i], ros[i], o), y2 + this.getOffset(-ros[i], ros[i], o),
f[0], f[1]
]
});
}
path.setPosition(f[0], f[1]);
return ops;
}
private _processSegment(path: RoughPath, seg: Segment, prevSeg: Segment | null, o: ResolvedOptions): Op[] {
let ops: Op[] = [];
switch (seg.key) {
case 'M':
case 'm': {
const delta = seg.key === 'm';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
const ro = 1 * (o.maxRandomnessOffset || 0);
x = x + this.getOffset(-ro, ro, o);
y = y + this.getOffset(-ro, ro, o);
path.setPosition(x, y);
ops.push({ op: 'move', data: [x, y] });
}
break;
}
case 'L':
case 'l': {
const delta = seg.key === 'l';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
ops = ops.concat(this.doubleLine(path.x, path.y, x, y, o));
path.setPosition(x, y);
}
break;
}
case 'H':
case 'h': {
const delta = seg.key === 'h';
if (seg.data.length) {
let x = +seg.data[0];
if (delta) {
x += path.x;
}
ops = ops.concat(this.doubleLine(path.x, path.y, x, path.y, o));
path.setPosition(x, path.y);
}
break;
}
case 'V':
case 'v': {
const delta = seg.key === 'v';
if (seg.data.length) {
let y = +seg.data[0];
if (delta) {
y += path.y;
}
ops = ops.concat(this.doubleLine(path.x, path.y, path.x, y, o));
path.setPosition(path.x, y);
}
break;
}
case 'Z':
case 'z': {
if (path.first) {
ops = ops.concat(this.doubleLine(path.x, path.y, path.first[0], path.first[1], o));
path.setPosition(path.first[0], path.first[1]);
path.first = null;
}
break;
}
case 'C':
case 'c': {
const delta = seg.key === 'c';
if (seg.data.length >= 6) {
let x1 = +seg.data[0];
let y1 = +seg.data[1];
let x2 = +seg.data[2];
let y2 = +seg.data[3];
let x = +seg.data[4];
let y = +seg.data[5];
if (delta) {
x1 += path.x;
x2 += path.x;
x += path.x;
y1 += path.y;
y2 += path.y;
y += path.y;
}
const ob = this._bezierTo(x1, y1, x2, y2, x, y, path, o);
ops = ops.concat(ob);
path.bezierReflectionPoint = [x + (x - x2), y + (y - y2)];
}
break;
}
case 'S':
case 's': {
const delta = seg.key === 's';
if (seg.data.length >= 4) {
let x2 = +seg.data[0];
let y2 = +seg.data[1];
let x = +seg.data[2];
let y = +seg.data[3];
if (delta) {
x2 += path.x;
x += path.x;
y2 += path.y;
y += path.y;
}
let x1 = x2;
let y1 = y2;
const prevKey = prevSeg ? prevSeg.key : '';
let ref: Point | null = null;
if (prevKey === 'c' || prevKey === 'C' || prevKey === 's' || prevKey === 'S') {
ref = path.bezierReflectionPoint;
}
if (ref) {
x1 = ref[0];
y1 = ref[1];
}
const ob = this._bezierTo(x1, y1, x2, y2, x, y, path, o);
ops = ops.concat(ob);
path.bezierReflectionPoint = [x + (x - x2), y + (y - y2)];
}
break;
}
case 'Q':
case 'q': {
const delta = seg.key === 'q';
if (seg.data.length >= 4) {
let x1 = +seg.data[0];
let y1 = +seg.data[1];
let x = +seg.data[2];
let y = +seg.data[3];
if (delta) {
x1 += path.x;
x += path.x;
y1 += path.y;
y += path.y;
}
const offset1 = 1 * (1 + o.roughness * 0.2);
const offset2 = 1.5 * (1 + o.roughness * 0.22);
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset1, offset1, o), path.y + this.getOffset(-offset1, offset1, o)] });
let f = [x + this.getOffset(-offset1, offset1, o), y + this.getOffset(-offset1, offset1, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset1, offset1, o), y1 + this.getOffset(-offset1, offset1, o),
f[0], f[1]
]
});
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset2, offset2, o), path.y + this.getOffset(-offset2, offset2, o)] });
f = [x + this.getOffset(-offset2, offset2, o), y + this.getOffset(-offset2, offset2, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset2, offset2, o), y1 + this.getOffset(-offset2, offset2, o),
f[0], f[1]
]
});
path.setPosition(f[0], f[1]);
path.quadReflectionPoint = [x + (x - x1), y + (y - y1)];
}
break;
}
case 'T':
case 't': {
const delta = seg.key === 't';
if (seg.data.length >= 2) {
let x = +seg.data[0];
let y = +seg.data[1];
if (delta) {
x += path.x;
y += path.y;
}
let x1 = x;
let y1 = y;
const prevKey = prevSeg ? prevSeg.key : '';
let ref: Point | null = null;
if (prevKey === 'q' || prevKey === 'Q' || prevKey === 't' || prevKey === 'T') {
ref = path.quadReflectionPoint;
}
if (ref) {
x1 = ref[0];
y1 = ref[1];
}
const offset1 = 1 * (1 + o.roughness * 0.2);
const offset2 = 1.5 * (1 + o.roughness * 0.22);
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset1, offset1, o), path.y + this.getOffset(-offset1, offset1, o)] });
let f = [x + this.getOffset(-offset1, offset1, o), y + this.getOffset(-offset1, offset1, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset1, offset1, o), y1 + this.getOffset(-offset1, offset1, o),
f[0], f[1]
]
});
ops.push({ op: 'move', data: [path.x + this.getOffset(-offset2, offset2, o), path.y + this.getOffset(-offset2, offset2, o)] });
f = [x + this.getOffset(-offset2, offset2, o), y + this.getOffset(-offset2, offset2, o)];
ops.push({
op: 'qcurveTo', data: [
x1 + this.getOffset(-offset2, offset2, o), y1 + this.getOffset(-offset2, offset2, o),
f[0], f[1]
]
});
path.setPosition(f[0], f[1]);
path.quadReflectionPoint = [x + (x - x1), y + (y - y1)];
}
break;
}
case 'A':
case 'a': {
const delta = seg.key === 'a';
if (seg.data.length >= 7) {
const rx = +seg.data[0];
const ry = +seg.data[1];
const angle = +seg.data[2];
const largeArcFlag = +seg.data[3];
const sweepFlag = +seg.data[4];
let x = +seg.data[5];
let y = +seg.data[6];
if (delta) {
x += path.x;
y += path.y;
}
if (x === path.x && y === path.y) {
break;
}
if (rx === 0 || ry === 0) {
ops = ops.concat(this.doubleLine(path.x, path.y, x, y, o));
path.setPosition(x, y);
} else {
for (let i = 0; i < 1; i++) {
const arcConverter = new RoughArcConverter(
[path.x, path.y],
[x, y],
[rx, ry],
angle,
largeArcFlag ? true : false,
sweepFlag ? true : false
);
let segment = arcConverter.getNextSegment();
while (segment) {
const ob = this._bezierTo(segment.cp1[0], segment.cp1[1], segment.cp2[0], segment.cp2[1], segment.to[0], segment.to[1], path, o);
ops = ops.concat(ob);
segment = arcConverter.getNextSegment();
}
}
}
}
break;
}
default:
break;
}
return ops;
}
}

View File

@@ -1,35 +1,18 @@
import { Config, DrawingSurface } from './core';
import { RoughCanvas } from './canvas';
import { RoughRenderer } from './renderer';
import { RoughGenerator } from './generator';
import { RoughGeneratorAsync } from './generator-async';
import { RoughCanvasAsync } from './canvas-async';
import { RoughSVG } from './svg';
import { RoughSVGAsync } from './svg-async';
export default {
canvas(canvas: HTMLCanvasElement, config?: Config): RoughCanvas | RoughCanvasAsync {
if (config && config.async) {
return new RoughCanvasAsync(canvas, config);
}
canvas(canvas: HTMLCanvasElement, config?: Config): RoughCanvas {
return new RoughCanvas(canvas, config);
},
svg(svg: SVGSVGElement, config?: Config): RoughSVG | RoughSVGAsync {
if (config && config.async) {
return new RoughSVGAsync(svg, config);
}
svg(svg: SVGSVGElement, config?: Config): RoughSVG {
return new RoughSVG(svg, config);
},
createRenderer(): RoughRenderer {
return RoughCanvas.createRenderer();
},
generator(config: Config | null, surface: DrawingSurface): RoughGenerator | RoughGeneratorAsync {
if (config && config.async) {
return new RoughGeneratorAsync(config, surface);
}
generator(config: Config | null, surface: DrawingSurface): RoughGenerator {
return new RoughGenerator(config, surface);
}
};

View File

@@ -1,5 +1,4 @@
import { Drawable, OpSet, ResolvedOptions } from './core';
import { RoughRenderer } from './renderer';
const hasDocument = typeof document !== 'undefined';
@@ -15,10 +14,6 @@ export abstract class RoughSVGBase {
abstract opsToPath(drawing: OpSet): string;
static createRenderer(): RoughRenderer {
return new RoughRenderer();
}
get defs(): SVGDefsElement | null {
const doc = this.svg.ownerDocument || (hasDocument && document);
if (doc) {
@@ -38,7 +33,7 @@ export abstract class RoughSVGBase {
draw(drawable: Drawable): SVGGElement {
const sets = drawable.sets || [];
const o = drawable.options || this.getDefaultOptions();
const doc = this.svg.ownerDocument || (hasDocument && document);
const doc = this.svg.ownerDocument || window.document;
const g = doc.createElementNS('http://www.w3.org/2000/svg', 'g');
for (const drawing of sets) {
let path = null;

View File

@@ -23,47 +23,47 @@ export class RoughSVG extends RoughSVGBase {
return this.gen.opsToPath(drawing);
}
line(x1: number, y1: number, x2: number, y2: number, options?: Options) {
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) {
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) {
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) {
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) {
linearPath(points: Point[], options?: Options): SVGGElement {
const d = this.gen.linearPath(points, options);
return this.draw(d);
}
polygon(points: Point[], options?: Options) {
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) {
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[], options?: Options) {
curve(points: Point[], options?: Options): SVGGElement {
const d = this.gen.curve(points, options);
return this.draw(d);
}
path(d: string, options?: Options) {
path(d: string, options?: Options): SVGGElement {
const drawing = this.gen.path(d, options);
return this.draw(drawing);
}

View File

@@ -16,9 +16,7 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
"noFallthroughCasesInSwitch": true
},
"include": [
"src/**/*.ts"