+23
@@ -0,0 +1,23 @@
|
||||
import { Config, Options, ResolvedOptions, Drawable } from './core';
|
||||
import { RoughGenerator } from './generator';
|
||||
import { Point } from './geometry';
|
||||
export declare class RoughCanvas {
|
||||
private gen;
|
||||
private canvas;
|
||||
private ctx;
|
||||
constructor(canvas: HTMLCanvasElement, config?: Config);
|
||||
draw(drawable: Drawable): void;
|
||||
private fillSketch;
|
||||
private _drawToContext;
|
||||
get generator(): RoughGenerator;
|
||||
getDefaultOptions(): ResolvedOptions;
|
||||
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable;
|
||||
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable;
|
||||
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable;
|
||||
circle(x: number, y: number, diameter: number, options?: Options): Drawable;
|
||||
linearPath(points: Point[], options?: Options): Drawable;
|
||||
polygon(points: Point[], options?: Options): Drawable;
|
||||
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): Drawable;
|
||||
curve(points: Point[] | Point[][], options?: Options): Drawable;
|
||||
path(d: string, options?: Options): Drawable;
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
import { RoughGenerator } from './generator';
|
||||
export class RoughCanvas {
|
||||
constructor(canvas, config) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.gen = new RoughGenerator(config);
|
||||
}
|
||||
draw(drawable) {
|
||||
const sets = drawable.sets || [];
|
||||
const o = drawable.options || this.getDefaultOptions();
|
||||
const ctx = this.ctx;
|
||||
const precision = drawable.options.fixedDecimalPlaceDigits;
|
||||
for (const drawing of sets) {
|
||||
switch (drawing.type) {
|
||||
case 'path':
|
||||
ctx.save();
|
||||
ctx.strokeStyle = o.stroke === 'none' ? 'transparent' : o.stroke;
|
||||
ctx.lineWidth = o.strokeWidth;
|
||||
if (o.strokeLineDash) {
|
||||
ctx.setLineDash(o.strokeLineDash);
|
||||
}
|
||||
if (o.strokeLineDashOffset) {
|
||||
ctx.lineDashOffset = o.strokeLineDashOffset;
|
||||
}
|
||||
this._drawToContext(ctx, drawing, precision);
|
||||
ctx.restore();
|
||||
break;
|
||||
case 'fillPath': {
|
||||
ctx.save();
|
||||
ctx.fillStyle = o.fill || '';
|
||||
const fillRule = (drawable.shape === 'curve' || drawable.shape === 'polygon' || drawable.shape === 'path') ? 'evenodd' : 'nonzero';
|
||||
this._drawToContext(ctx, drawing, precision, fillRule);
|
||||
ctx.restore();
|
||||
break;
|
||||
}
|
||||
case 'fillSketch':
|
||||
this.fillSketch(ctx, drawing, o);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fillSketch(ctx, drawing, o) {
|
||||
let fweight = o.fillWeight;
|
||||
if (fweight < 0) {
|
||||
fweight = o.strokeWidth / 2;
|
||||
}
|
||||
ctx.save();
|
||||
if (o.fillLineDash) {
|
||||
ctx.setLineDash(o.fillLineDash);
|
||||
}
|
||||
if (o.fillLineDashOffset) {
|
||||
ctx.lineDashOffset = o.fillLineDashOffset;
|
||||
}
|
||||
ctx.strokeStyle = o.fill || '';
|
||||
ctx.lineWidth = fweight;
|
||||
this._drawToContext(ctx, drawing, o.fixedDecimalPlaceDigits);
|
||||
ctx.restore();
|
||||
}
|
||||
_drawToContext(ctx, drawing, fixedDecimals, rule = 'nonzero') {
|
||||
ctx.beginPath();
|
||||
for (const item of drawing.ops) {
|
||||
const data = ((typeof fixedDecimals === 'number') && fixedDecimals >= 0) ? (item.data.map((d) => +d.toFixed(fixedDecimals))) : item.data;
|
||||
switch (item.op) {
|
||||
case 'move':
|
||||
ctx.moveTo(data[0], data[1]);
|
||||
break;
|
||||
case 'bcurveTo':
|
||||
ctx.bezierCurveTo(data[0], data[1], data[2], data[3], data[4], data[5]);
|
||||
break;
|
||||
case 'lineTo':
|
||||
ctx.lineTo(data[0], data[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (drawing.type === 'fillPath') {
|
||||
ctx.fill(rule);
|
||||
}
|
||||
else {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
get generator() {
|
||||
return this.gen;
|
||||
}
|
||||
getDefaultOptions() {
|
||||
return this.gen.defaultOptions;
|
||||
}
|
||||
line(x1, y1, x2, y2, options) {
|
||||
const d = this.gen.line(x1, y1, x2, y2, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
rectangle(x, y, width, height, options) {
|
||||
const d = this.gen.rectangle(x, y, width, height, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
ellipse(x, y, width, height, options) {
|
||||
const d = this.gen.ellipse(x, y, width, height, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
circle(x, y, diameter, options) {
|
||||
const d = this.gen.circle(x, y, diameter, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
linearPath(points, options) {
|
||||
const d = this.gen.linearPath(points, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
polygon(points, options) {
|
||||
const d = this.gen.polygon(points, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
arc(x, y, width, height, start, stop, closed = false, options) {
|
||||
const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
curve(points, options) {
|
||||
const d = this.gen.curve(points, options);
|
||||
this.draw(d);
|
||||
return d;
|
||||
}
|
||||
path(d, options) {
|
||||
const drawing = this.gen.path(d, options);
|
||||
this.draw(drawing);
|
||||
return drawing;
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { Point } from './geometry';
|
||||
import { Random } from './math';
|
||||
export declare const SVGNS = "http://www.w3.org/2000/svg";
|
||||
export interface Config {
|
||||
options?: Options;
|
||||
}
|
||||
export interface DrawingSurface {
|
||||
width: number | SVGAnimatedLength;
|
||||
height: number | SVGAnimatedLength;
|
||||
}
|
||||
export interface Options {
|
||||
maxRandomnessOffset?: number;
|
||||
roughness?: number;
|
||||
bowing?: number;
|
||||
stroke?: string;
|
||||
strokeWidth?: number;
|
||||
curveFitting?: number;
|
||||
curveTightness?: number;
|
||||
curveStepCount?: number;
|
||||
fill?: string;
|
||||
fillStyle?: string;
|
||||
fillWeight?: number;
|
||||
hachureAngle?: number;
|
||||
hachureGap?: number;
|
||||
simplification?: number;
|
||||
dashOffset?: number;
|
||||
dashGap?: number;
|
||||
zigzagOffset?: number;
|
||||
seed?: number;
|
||||
strokeLineDash?: number[];
|
||||
strokeLineDashOffset?: number;
|
||||
fillLineDash?: number[];
|
||||
fillLineDashOffset?: number;
|
||||
disableMultiStroke?: boolean;
|
||||
disableMultiStrokeFill?: boolean;
|
||||
preserveVertices?: boolean;
|
||||
fixedDecimalPlaceDigits?: number;
|
||||
fillShapeRoughnessGain?: number;
|
||||
}
|
||||
export interface ResolvedOptions extends Options {
|
||||
maxRandomnessOffset: number;
|
||||
roughness: number;
|
||||
bowing: number;
|
||||
stroke: string;
|
||||
strokeWidth: number;
|
||||
curveFitting: number;
|
||||
curveTightness: number;
|
||||
curveStepCount: number;
|
||||
fillStyle: string;
|
||||
fillWeight: number;
|
||||
hachureAngle: number;
|
||||
hachureGap: number;
|
||||
dashOffset: number;
|
||||
dashGap: number;
|
||||
zigzagOffset: number;
|
||||
seed: number;
|
||||
randomizer?: Random;
|
||||
disableMultiStroke: boolean;
|
||||
disableMultiStrokeFill: boolean;
|
||||
preserveVertices: boolean;
|
||||
fillShapeRoughnessGain: number;
|
||||
}
|
||||
export declare type OpType = 'move' | 'bcurveTo' | 'lineTo';
|
||||
export declare type OpSetType = 'path' | 'fillPath' | 'fillSketch';
|
||||
export interface Op {
|
||||
op: OpType;
|
||||
data: number[];
|
||||
}
|
||||
export interface OpSet {
|
||||
type: OpSetType;
|
||||
ops: Op[];
|
||||
size?: Point;
|
||||
path?: string;
|
||||
}
|
||||
export interface Drawable {
|
||||
shape: string;
|
||||
options: ResolvedOptions;
|
||||
sets: OpSet[];
|
||||
}
|
||||
export interface PathInfo {
|
||||
d: string;
|
||||
stroke: string;
|
||||
strokeWidth: number;
|
||||
fill?: string;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const SVGNS = 'http://www.w3.org/2000/svg';
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet } from '../core';
|
||||
import { Point } from '../geometry';
|
||||
export declare class DashedFiller implements PatternFiller {
|
||||
private helper;
|
||||
constructor(helper: RenderHelper);
|
||||
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
private dashedLine;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { lineLength } from '../geometry';
|
||||
import { polygonHachureLines } from './scan-line-hachure';
|
||||
export class DashedFiller {
|
||||
constructor(helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
fillPolygons(polygonList, o) {
|
||||
const lines = polygonHachureLines(polygonList, o);
|
||||
return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
|
||||
}
|
||||
dashedLine(lines, o) {
|
||||
const offset = o.dashOffset < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashOffset;
|
||||
const gap = o.dashGap < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashGap;
|
||||
const ops = [];
|
||||
lines.forEach((line) => {
|
||||
const length = lineLength(line);
|
||||
const count = Math.floor(length / (offset + gap));
|
||||
const startOffset = (length + gap - (count * (offset + gap))) / 2;
|
||||
let p1 = line[0];
|
||||
let p2 = line[1];
|
||||
if (p1[0] > p2[0]) {
|
||||
p1 = line[1];
|
||||
p2 = line[0];
|
||||
}
|
||||
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
|
||||
for (let i = 0; i < count; i++) {
|
||||
const lstart = i * (offset + gap);
|
||||
const lend = lstart + offset;
|
||||
const start = [p1[0] + (lstart * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha) + (startOffset * Math.sin(alpha))];
|
||||
const end = [p1[0] + (lend * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha)) + (startOffset * Math.sin(alpha))];
|
||||
ops.push(...this.helper.doubleLineOps(start[0], start[1], end[0], end[1], o));
|
||||
}
|
||||
});
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet } from '../core';
|
||||
import { Point } from '../geometry';
|
||||
export declare class DotFiller implements PatternFiller {
|
||||
private helper;
|
||||
constructor(helper: RenderHelper);
|
||||
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
private dotsOnLines;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { lineLength } from '../geometry';
|
||||
import { polygonHachureLines } from './scan-line-hachure';
|
||||
export class DotFiller {
|
||||
constructor(helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
fillPolygons(polygonList, o) {
|
||||
o = Object.assign({}, o, { hachureAngle: 0 });
|
||||
const lines = polygonHachureLines(polygonList, o);
|
||||
return this.dotsOnLines(lines, o);
|
||||
}
|
||||
dotsOnLines(lines, o) {
|
||||
const ops = [];
|
||||
let gap = o.hachureGap;
|
||||
if (gap < 0) {
|
||||
gap = o.strokeWidth * 4;
|
||||
}
|
||||
gap = Math.max(gap, 0.1);
|
||||
let fweight = o.fillWeight;
|
||||
if (fweight < 0) {
|
||||
fweight = o.strokeWidth / 2;
|
||||
}
|
||||
const ro = gap / 4;
|
||||
for (const line of lines) {
|
||||
const length = lineLength(line);
|
||||
const dl = length / gap;
|
||||
const count = Math.ceil(dl) - 1;
|
||||
const offset = length - (count * gap);
|
||||
const x = ((line[0][0] + line[1][0]) / 2) - (gap / 4);
|
||||
const minY = Math.min(line[0][1], line[1][1]);
|
||||
for (let i = 0; i < count; i++) {
|
||||
const y = minY + offset + (i * gap);
|
||||
const cx = (x - ro) + Math.random() * 2 * ro;
|
||||
const cy = (y - ro) + Math.random() * 2 * ro;
|
||||
const el = this.helper.ellipse(cx, cy, fweight, fweight, o);
|
||||
ops.push(...el.ops);
|
||||
}
|
||||
}
|
||||
return { type: 'fillSketch', ops };
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { ResolvedOptions, OpSet, Op } from '../core';
|
||||
import { Point } from '../geometry';
|
||||
export interface PatternFiller {
|
||||
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
}
|
||||
export interface RenderHelper {
|
||||
randOffset(x: number, o: ResolvedOptions): number;
|
||||
randOffsetWithRange(min: number, max: number, o: ResolvedOptions): number;
|
||||
ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
|
||||
doubleLineOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { ResolvedOptions } from '../core';
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
export declare function getFiller(o: ResolvedOptions, helper: RenderHelper): PatternFiller;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { HachureFiller } from './hachure-filler';
|
||||
import { ZigZagFiller } from './zigzag-filler';
|
||||
import { HatchFiller } from './hatch-filler';
|
||||
import { DotFiller } from './dot-filler';
|
||||
import { DashedFiller } from './dashed-filler';
|
||||
import { ZigZagLineFiller } from './zigzag-line-filler';
|
||||
const fillers = {};
|
||||
export function getFiller(o, helper) {
|
||||
let fillerName = o.fillStyle || 'hachure';
|
||||
if (!fillers[fillerName]) {
|
||||
switch (fillerName) {
|
||||
case 'zigzag':
|
||||
if (!fillers[fillerName]) {
|
||||
fillers[fillerName] = new ZigZagFiller(helper);
|
||||
}
|
||||
break;
|
||||
case 'cross-hatch':
|
||||
if (!fillers[fillerName]) {
|
||||
fillers[fillerName] = new HatchFiller(helper);
|
||||
}
|
||||
break;
|
||||
case 'dots':
|
||||
if (!fillers[fillerName]) {
|
||||
fillers[fillerName] = new DotFiller(helper);
|
||||
}
|
||||
break;
|
||||
case 'dashed':
|
||||
if (!fillers[fillerName]) {
|
||||
fillers[fillerName] = new DashedFiller(helper);
|
||||
}
|
||||
break;
|
||||
case 'zigzag-line':
|
||||
if (!fillers[fillerName]) {
|
||||
fillers[fillerName] = new ZigZagLineFiller(helper);
|
||||
}
|
||||
break;
|
||||
case 'hachure':
|
||||
default:
|
||||
fillerName = 'hachure';
|
||||
if (!fillers[fillerName]) {
|
||||
fillers[fillerName] = new HachureFiller(helper);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fillers[fillerName];
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet, Op } from '../core';
|
||||
import { Point, Line } from '../geometry';
|
||||
export declare class HachureFiller implements PatternFiller {
|
||||
private helper;
|
||||
constructor(helper: RenderHelper);
|
||||
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
protected _fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
protected renderLines(lines: Line[], o: ResolvedOptions): Op[];
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { polygonHachureLines } from './scan-line-hachure';
|
||||
export class HachureFiller {
|
||||
constructor(helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
fillPolygons(polygonList, o) {
|
||||
return this._fillPolygons(polygonList, o);
|
||||
}
|
||||
_fillPolygons(polygonList, o) {
|
||||
const lines = polygonHachureLines(polygonList, o);
|
||||
const ops = this.renderLines(lines, o);
|
||||
return { type: 'fillSketch', ops };
|
||||
}
|
||||
renderLines(lines, o) {
|
||||
const ops = [];
|
||||
for (const line of lines) {
|
||||
ops.push(...this.helper.doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { HachureFiller } from './hachure-filler';
|
||||
import { ResolvedOptions, OpSet } from '../core';
|
||||
import { Point } from '../geometry';
|
||||
export declare class HatchFiller extends HachureFiller {
|
||||
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { HachureFiller } from './hachure-filler';
|
||||
export class HatchFiller extends HachureFiller {
|
||||
fillPolygons(polygonList, o) {
|
||||
const set = this._fillPolygons(polygonList, o);
|
||||
const o2 = Object.assign({}, o, { hachureAngle: o.hachureAngle + 90 });
|
||||
const set2 = this._fillPolygons(polygonList, o2);
|
||||
set.ops = set.ops.concat(set2.ops);
|
||||
return set;
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { Point, Line } from '../geometry';
|
||||
import { ResolvedOptions } from '../core';
|
||||
export declare function polygonHachureLines(polygonList: Point[][], o: ResolvedOptions): Line[];
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { hachureLines } from 'hachure-fill';
|
||||
export function polygonHachureLines(polygonList, o) {
|
||||
var _a;
|
||||
const angle = o.hachureAngle + 90;
|
||||
let gap = o.hachureGap;
|
||||
if (gap < 0) {
|
||||
gap = o.strokeWidth * 4;
|
||||
}
|
||||
gap = Math.round(Math.max(gap, 0.1));
|
||||
let skipOffset = 1;
|
||||
if (o.roughness >= 1) {
|
||||
if ((((_a = o.randomizer) === null || _a === void 0 ? void 0 : _a.next()) || Math.random()) > 0.7) {
|
||||
skipOffset = gap;
|
||||
}
|
||||
}
|
||||
return hachureLines(polygonList, gap, angle, skipOffset || 1);
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { HachureFiller } from './hachure-filler';
|
||||
import { ResolvedOptions, OpSet } from '../core';
|
||||
import { Point } from '../geometry';
|
||||
export declare class ZigZagFiller extends HachureFiller {
|
||||
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { HachureFiller } from './hachure-filler';
|
||||
import { polygonHachureLines } from './scan-line-hachure';
|
||||
import { lineLength } from '../geometry';
|
||||
export class ZigZagFiller extends HachureFiller {
|
||||
fillPolygons(polygonList, o) {
|
||||
let gap = o.hachureGap;
|
||||
if (gap < 0) {
|
||||
gap = o.strokeWidth * 4;
|
||||
}
|
||||
gap = Math.max(gap, 0.1);
|
||||
const o2 = Object.assign({}, o, { hachureGap: gap });
|
||||
const lines = polygonHachureLines(polygonList, o2);
|
||||
const zigZagAngle = (Math.PI / 180) * o.hachureAngle;
|
||||
const zigzagLines = [];
|
||||
const dgx = gap * 0.5 * Math.cos(zigZagAngle);
|
||||
const dgy = gap * 0.5 * Math.sin(zigZagAngle);
|
||||
for (const [p1, p2] of lines) {
|
||||
if (lineLength([p1, p2])) {
|
||||
zigzagLines.push([
|
||||
[p1[0] - dgx, p1[1] + dgy],
|
||||
[...p2],
|
||||
], [
|
||||
[p1[0] + dgx, p1[1] - dgy],
|
||||
[...p2],
|
||||
]);
|
||||
}
|
||||
}
|
||||
const ops = this.renderLines(zigzagLines, o);
|
||||
return { type: 'fillSketch', ops };
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { PatternFiller, RenderHelper } from './filler-interface';
|
||||
import { ResolvedOptions, OpSet } from '../core';
|
||||
import { Point } from '../geometry';
|
||||
export declare class ZigZagLineFiller implements PatternFiller {
|
||||
private helper;
|
||||
constructor(helper: RenderHelper);
|
||||
fillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
private zigzagLines;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { lineLength } from '../geometry';
|
||||
import { polygonHachureLines } from './scan-line-hachure';
|
||||
export class ZigZagLineFiller {
|
||||
constructor(helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
fillPolygons(polygonList, o) {
|
||||
const gap = o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap;
|
||||
const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
|
||||
o = Object.assign({}, o, { hachureGap: gap + zo });
|
||||
const lines = polygonHachureLines(polygonList, o);
|
||||
return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };
|
||||
}
|
||||
zigzagLines(lines, zo, o) {
|
||||
const ops = [];
|
||||
lines.forEach((line) => {
|
||||
const length = lineLength(line);
|
||||
const count = Math.round(length / (2 * zo));
|
||||
let p1 = line[0];
|
||||
let p2 = line[1];
|
||||
if (p1[0] > p2[0]) {
|
||||
p1 = line[1];
|
||||
p2 = line[0];
|
||||
}
|
||||
const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
|
||||
for (let i = 0; i < count; i++) {
|
||||
const lstart = i * 2 * zo;
|
||||
const lend = (i + 1) * 2 * zo;
|
||||
const dz = Math.sqrt(2 * Math.pow(zo, 2));
|
||||
const start = [p1[0] + (lstart * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha)];
|
||||
const end = [p1[0] + (lend * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha))];
|
||||
const middle = [start[0] + dz * Math.cos(alpha + Math.PI / 4), start[1] + dz * Math.sin(alpha + Math.PI / 4)];
|
||||
ops.push(...this.helper.doubleLineOps(start[0], start[1], middle[0], middle[1], o), ...this.helper.doubleLineOps(middle[0], middle[1], end[0], end[1], o));
|
||||
}
|
||||
});
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { Config, Options, Drawable, OpSet, ResolvedOptions, PathInfo } from './core.js';
|
||||
import { Point } from './geometry.js';
|
||||
export declare class RoughGenerator {
|
||||
private config;
|
||||
defaultOptions: ResolvedOptions;
|
||||
constructor(config?: Config);
|
||||
static newSeed(): number;
|
||||
private _o;
|
||||
private _d;
|
||||
line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable;
|
||||
rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable;
|
||||
ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable;
|
||||
circle(x: number, y: number, diameter: number, options?: Options): Drawable;
|
||||
linearPath(points: Point[], options?: Options): Drawable;
|
||||
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): Drawable;
|
||||
curve(points: Point[] | Point[][], options?: Options): Drawable;
|
||||
polygon(points: Point[], options?: Options): Drawable;
|
||||
path(d: string, options?: Options): Drawable;
|
||||
opsToPath(drawing: OpSet, fixedDecimals?: number): string;
|
||||
toPaths(drawable: Drawable): PathInfo[];
|
||||
private fillSketch;
|
||||
private _mergedShape;
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
import { line, solidFillPolygon, patternFillPolygons, rectangle, ellipseWithParams, generateEllipseParams, linearPath, arc, patternFillArc, curve, svgPath } from './renderer.js';
|
||||
import { randomSeed } from './math.js';
|
||||
import { curveToBezier } from 'points-on-curve/lib/curve-to-bezier.js';
|
||||
import { pointsOnBezierCurves } from 'points-on-curve';
|
||||
import { pointsOnPath } from 'points-on-path';
|
||||
const NOS = 'none';
|
||||
export class RoughGenerator {
|
||||
constructor(config) {
|
||||
this.defaultOptions = {
|
||||
maxRandomnessOffset: 2,
|
||||
roughness: 1,
|
||||
bowing: 1,
|
||||
stroke: '#000',
|
||||
strokeWidth: 1,
|
||||
curveTightness: 0,
|
||||
curveFitting: 0.95,
|
||||
curveStepCount: 9,
|
||||
fillStyle: 'hachure',
|
||||
fillWeight: -1,
|
||||
hachureAngle: -41,
|
||||
hachureGap: -1,
|
||||
dashOffset: -1,
|
||||
dashGap: -1,
|
||||
zigzagOffset: -1,
|
||||
seed: 0,
|
||||
disableMultiStroke: false,
|
||||
disableMultiStrokeFill: false,
|
||||
preserveVertices: false,
|
||||
fillShapeRoughnessGain: 0.8,
|
||||
};
|
||||
this.config = config || {};
|
||||
if (this.config.options) {
|
||||
this.defaultOptions = this._o(this.config.options);
|
||||
}
|
||||
}
|
||||
static newSeed() {
|
||||
return randomSeed();
|
||||
}
|
||||
_o(options) {
|
||||
return options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
|
||||
}
|
||||
_d(shape, sets, options) {
|
||||
return { shape, sets: sets || [], options: options || this.defaultOptions };
|
||||
}
|
||||
line(x1, y1, x2, y2, options) {
|
||||
const o = this._o(options);
|
||||
return this._d('line', [line(x1, y1, x2, y2, o)], o);
|
||||
}
|
||||
rectangle(x, y, width, height, options) {
|
||||
const o = this._o(options);
|
||||
const paths = [];
|
||||
const outline = rectangle(x, y, width, height, o);
|
||||
if (o.fill) {
|
||||
const points = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]];
|
||||
if (o.fillStyle === 'solid') {
|
||||
paths.push(solidFillPolygon([points], o));
|
||||
}
|
||||
else {
|
||||
paths.push(patternFillPolygons([points], o));
|
||||
}
|
||||
}
|
||||
if (o.stroke !== NOS) {
|
||||
paths.push(outline);
|
||||
}
|
||||
return this._d('rectangle', paths, o);
|
||||
}
|
||||
ellipse(x, y, width, height, options) {
|
||||
const o = this._o(options);
|
||||
const paths = [];
|
||||
const ellipseParams = generateEllipseParams(width, height, o);
|
||||
const ellipseResponse = ellipseWithParams(x, y, o, ellipseParams);
|
||||
if (o.fill) {
|
||||
if (o.fillStyle === 'solid') {
|
||||
const shape = ellipseWithParams(x, y, o, ellipseParams).opset;
|
||||
shape.type = 'fillPath';
|
||||
paths.push(shape);
|
||||
}
|
||||
else {
|
||||
paths.push(patternFillPolygons([ellipseResponse.estimatedPoints], o));
|
||||
}
|
||||
}
|
||||
if (o.stroke !== NOS) {
|
||||
paths.push(ellipseResponse.opset);
|
||||
}
|
||||
return this._d('ellipse', paths, o);
|
||||
}
|
||||
circle(x, y, diameter, options) {
|
||||
const ret = this.ellipse(x, y, diameter, diameter, options);
|
||||
ret.shape = 'circle';
|
||||
return ret;
|
||||
}
|
||||
linearPath(points, options) {
|
||||
const o = this._o(options);
|
||||
return this._d('linearPath', [linearPath(points, false, o)], o);
|
||||
}
|
||||
arc(x, y, width, height, start, stop, closed = false, options) {
|
||||
const o = this._o(options);
|
||||
const paths = [];
|
||||
const outline = arc(x, y, width, height, start, stop, closed, true, o);
|
||||
if (closed && o.fill) {
|
||||
if (o.fillStyle === 'solid') {
|
||||
const fillOptions = Object.assign({}, o);
|
||||
fillOptions.disableMultiStroke = true;
|
||||
const shape = arc(x, y, width, height, start, stop, true, false, fillOptions);
|
||||
shape.type = 'fillPath';
|
||||
paths.push(shape);
|
||||
}
|
||||
else {
|
||||
paths.push(patternFillArc(x, y, width, height, start, stop, o));
|
||||
}
|
||||
}
|
||||
if (o.stroke !== NOS) {
|
||||
paths.push(outline);
|
||||
}
|
||||
return this._d('arc', paths, o);
|
||||
}
|
||||
curve(points, options) {
|
||||
const o = this._o(options);
|
||||
const paths = [];
|
||||
const outline = curve(points, o);
|
||||
if (o.fill && o.fill !== NOS) {
|
||||
if (o.fillStyle === 'solid') {
|
||||
const fillShape = curve(points, Object.assign(Object.assign({}, o), { disableMultiStroke: true, roughness: o.roughness ? (o.roughness + o.fillShapeRoughnessGain) : 0 }));
|
||||
paths.push({
|
||||
type: 'fillPath',
|
||||
ops: this._mergedShape(fillShape.ops),
|
||||
});
|
||||
}
|
||||
else {
|
||||
const polyPoints = [];
|
||||
const inputPoints = points;
|
||||
if (inputPoints.length) {
|
||||
const p1 = inputPoints[0];
|
||||
const pointsList = (typeof p1[0] === 'number') ? [inputPoints] : inputPoints;
|
||||
for (const points of pointsList) {
|
||||
if (points.length < 3) {
|
||||
polyPoints.push(...points);
|
||||
}
|
||||
else if (points.length === 3) {
|
||||
polyPoints.push(...pointsOnBezierCurves(curveToBezier([
|
||||
points[0],
|
||||
points[0],
|
||||
points[1],
|
||||
points[2],
|
||||
]), 10, (1 + o.roughness) / 2));
|
||||
}
|
||||
else {
|
||||
polyPoints.push(...pointsOnBezierCurves(curveToBezier(points), 10, (1 + o.roughness) / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (polyPoints.length) {
|
||||
paths.push(patternFillPolygons([polyPoints], o));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (o.stroke !== NOS) {
|
||||
paths.push(outline);
|
||||
}
|
||||
return this._d('curve', paths, o);
|
||||
}
|
||||
polygon(points, options) {
|
||||
const o = this._o(options);
|
||||
const paths = [];
|
||||
const outline = linearPath(points, true, o);
|
||||
if (o.fill) {
|
||||
if (o.fillStyle === 'solid') {
|
||||
paths.push(solidFillPolygon([points], o));
|
||||
}
|
||||
else {
|
||||
paths.push(patternFillPolygons([points], o));
|
||||
}
|
||||
}
|
||||
if (o.stroke !== NOS) {
|
||||
paths.push(outline);
|
||||
}
|
||||
return this._d('polygon', paths, o);
|
||||
}
|
||||
path(d, options) {
|
||||
const o = this._o(options);
|
||||
const paths = [];
|
||||
if (!d) {
|
||||
return this._d('path', paths, o);
|
||||
}
|
||||
d = (d || '').replace(/\n/g, ' ').replace(/(-\s)/g, '-').replace('/(\s\s)/g', ' ');
|
||||
const hasFill = o.fill && o.fill !== 'transparent' && o.fill !== NOS;
|
||||
const hasStroke = o.stroke !== NOS;
|
||||
const simplified = !!(o.simplification && (o.simplification < 1));
|
||||
const distance = simplified ? (4 - 4 * (o.simplification || 1)) : ((1 + o.roughness) / 2);
|
||||
const sets = pointsOnPath(d, 1, distance);
|
||||
const shape = svgPath(d, o);
|
||||
if (hasFill) {
|
||||
if (o.fillStyle === 'solid') {
|
||||
if (sets.length === 1) {
|
||||
const fillShape = svgPath(d, Object.assign(Object.assign({}, o), { disableMultiStroke: true, roughness: o.roughness ? (o.roughness + o.fillShapeRoughnessGain) : 0 }));
|
||||
paths.push({
|
||||
type: 'fillPath',
|
||||
ops: this._mergedShape(fillShape.ops),
|
||||
});
|
||||
}
|
||||
else {
|
||||
paths.push(solidFillPolygon(sets, o));
|
||||
}
|
||||
}
|
||||
else {
|
||||
paths.push(patternFillPolygons(sets, o));
|
||||
}
|
||||
}
|
||||
if (hasStroke) {
|
||||
if (simplified) {
|
||||
sets.forEach((set) => {
|
||||
paths.push(linearPath(set, false, o));
|
||||
});
|
||||
}
|
||||
else {
|
||||
paths.push(shape);
|
||||
}
|
||||
}
|
||||
return this._d('path', paths, o);
|
||||
}
|
||||
opsToPath(drawing, fixedDecimals) {
|
||||
let path = '';
|
||||
for (const item of drawing.ops) {
|
||||
const data = ((typeof fixedDecimals === 'number') && fixedDecimals >= 0) ? (item.data.map((d) => +d.toFixed(fixedDecimals))) : item.data;
|
||||
switch (item.op) {
|
||||
case 'move':
|
||||
path += `M${data[0]} ${data[1]} `;
|
||||
break;
|
||||
case 'bcurveTo':
|
||||
path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;
|
||||
break;
|
||||
case 'lineTo':
|
||||
path += `L${data[0]} ${data[1]} `;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return path.trim();
|
||||
}
|
||||
toPaths(drawable) {
|
||||
const sets = drawable.sets || [];
|
||||
const o = drawable.options || this.defaultOptions;
|
||||
const paths = [];
|
||||
for (const drawing of sets) {
|
||||
let path = null;
|
||||
switch (drawing.type) {
|
||||
case 'path':
|
||||
path = {
|
||||
d: this.opsToPath(drawing),
|
||||
stroke: o.stroke,
|
||||
strokeWidth: o.strokeWidth,
|
||||
fill: NOS,
|
||||
};
|
||||
break;
|
||||
case 'fillPath':
|
||||
path = {
|
||||
d: this.opsToPath(drawing),
|
||||
stroke: NOS,
|
||||
strokeWidth: 0,
|
||||
fill: o.fill || NOS,
|
||||
};
|
||||
break;
|
||||
case 'fillSketch':
|
||||
path = this.fillSketch(drawing, o);
|
||||
break;
|
||||
}
|
||||
if (path) {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
fillSketch(drawing, o) {
|
||||
let fweight = o.fillWeight;
|
||||
if (fweight < 0) {
|
||||
fweight = o.strokeWidth / 2;
|
||||
}
|
||||
return {
|
||||
d: this.opsToPath(drawing),
|
||||
stroke: o.fill || NOS,
|
||||
strokeWidth: fweight,
|
||||
fill: NOS,
|
||||
};
|
||||
}
|
||||
_mergedShape(input) {
|
||||
return input.filter((d, i) => {
|
||||
if (i === 0) {
|
||||
return true;
|
||||
}
|
||||
if (d.op === 'move') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export type Point = [number, number];
|
||||
export type Line = [Point, Point];
|
||||
export interface Rectangle {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
export declare function lineLength(line: Line): number;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export function lineLength(line) {
|
||||
const p1 = line[0];
|
||||
const p2 = line[1];
|
||||
return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export declare function randomSeed(): number;
|
||||
export declare class Random {
|
||||
private seed;
|
||||
constructor(seed: number);
|
||||
next(): number;
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
export function randomSeed() {
|
||||
return Math.floor(Math.random() * 2 ** 31);
|
||||
}
|
||||
export class Random {
|
||||
constructor(seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
next() {
|
||||
if (this.seed) {
|
||||
return ((2 ** 31 - 1) & (this.seed = Math.imul(48271, this.seed))) / 2 ** 31;
|
||||
}
|
||||
else {
|
||||
return Math.random();
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { ResolvedOptions, Op, OpSet } from './core.js';
|
||||
import { Point } from './geometry.js';
|
||||
interface EllipseParams {
|
||||
rx: number;
|
||||
ry: number;
|
||||
increment: number;
|
||||
}
|
||||
export declare function line(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): OpSet;
|
||||
export declare function linearPath(points: Point[], close: boolean, o: ResolvedOptions): OpSet;
|
||||
export declare function polygon(points: Point[], o: ResolvedOptions): OpSet;
|
||||
export declare function rectangle(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
|
||||
export declare function curve(inputPoints: Point[] | Point[][], o: ResolvedOptions): OpSet;
|
||||
export interface EllipseResult {
|
||||
opset: OpSet;
|
||||
estimatedPoints: Point[];
|
||||
}
|
||||
export declare function ellipse(x: number, y: number, width: number, height: number, o: ResolvedOptions): OpSet;
|
||||
export declare function generateEllipseParams(width: number, height: number, o: ResolvedOptions): EllipseParams;
|
||||
export declare function ellipseWithParams(x: number, y: number, o: ResolvedOptions, ellipseParams: EllipseParams): EllipseResult;
|
||||
export declare function arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean, roughClosure: boolean, o: ResolvedOptions): OpSet;
|
||||
export declare function svgPath(path: string, o: ResolvedOptions): OpSet;
|
||||
export declare function solidFillPolygon(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
export declare function patternFillPolygons(polygonList: Point[][], o: ResolvedOptions): OpSet;
|
||||
export declare function patternFillArc(x: number, y: number, width: number, height: number, start: number, stop: number, o: ResolvedOptions): OpSet;
|
||||
export declare function randOffset(x: number, o: ResolvedOptions): number;
|
||||
export declare function randOffsetWithRange(min: number, max: number, o: ResolvedOptions): number;
|
||||
export declare function doubleLineFillOps(x1: number, y1: number, x2: number, y2: number, o: ResolvedOptions): Op[];
|
||||
export {};
|
||||
+486
@@ -0,0 +1,486 @@
|
||||
import { getFiller } from './fillers/filler.js';
|
||||
import { Random } from './math.js';
|
||||
import { parsePath, normalize, absolutize } from 'path-data-parser';
|
||||
const helper = {
|
||||
randOffset,
|
||||
randOffsetWithRange,
|
||||
ellipse,
|
||||
doubleLineOps: doubleLineFillOps,
|
||||
};
|
||||
export function line(x1, y1, x2, y2, o) {
|
||||
return { type: 'path', ops: _doubleLine(x1, y1, x2, y2, o) };
|
||||
}
|
||||
export function linearPath(points, close, o) {
|
||||
const len = (points || []).length;
|
||||
if (len > 2) {
|
||||
const ops = [];
|
||||
for (let i = 0; i < (len - 1); i++) {
|
||||
ops.push(..._doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
|
||||
}
|
||||
if (close) {
|
||||
ops.push(..._doubleLine(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o));
|
||||
}
|
||||
return { type: 'path', ops };
|
||||
}
|
||||
else if (len === 2) {
|
||||
return line(points[0][0], points[0][1], points[1][0], points[1][1], o);
|
||||
}
|
||||
return { type: 'path', ops: [] };
|
||||
}
|
||||
export function polygon(points, o) {
|
||||
return linearPath(points, true, o);
|
||||
}
|
||||
export function rectangle(x, y, width, height, o) {
|
||||
const points = [
|
||||
[x, y],
|
||||
[x + width, y],
|
||||
[x + width, y + height],
|
||||
[x, y + height],
|
||||
];
|
||||
return polygon(points, o);
|
||||
}
|
||||
export function curve(inputPoints, o) {
|
||||
if (inputPoints.length) {
|
||||
const p1 = inputPoints[0];
|
||||
const pointsList = (typeof p1[0] === 'number') ? [inputPoints] : inputPoints;
|
||||
const o1 = _curveWithOffset(pointsList[0], 1 * (1 + o.roughness * 0.2), o);
|
||||
const o2 = o.disableMultiStroke ? [] : _curveWithOffset(pointsList[0], 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
|
||||
for (let i = 1; i < pointsList.length; i++) {
|
||||
const points = pointsList[i];
|
||||
if (points.length) {
|
||||
const underlay = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
|
||||
const overlay = o.disableMultiStroke ? [] : _curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
|
||||
for (const item of underlay) {
|
||||
if (item.op !== 'move') {
|
||||
o1.push(item);
|
||||
}
|
||||
}
|
||||
for (const item of overlay) {
|
||||
if (item.op !== 'move') {
|
||||
o2.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { type: 'path', ops: o1.concat(o2) };
|
||||
}
|
||||
return { type: 'path', ops: [] };
|
||||
}
|
||||
export function ellipse(x, y, width, height, o) {
|
||||
const params = generateEllipseParams(width, height, o);
|
||||
return ellipseWithParams(x, y, o, params).opset;
|
||||
}
|
||||
export function generateEllipseParams(width, height, o) {
|
||||
const psq = Math.sqrt(Math.PI * 2 * Math.sqrt((Math.pow(width / 2, 2) + Math.pow(height / 2, 2)) / 2));
|
||||
const stepCount = Math.ceil(Math.max(o.curveStepCount, (o.curveStepCount / Math.sqrt(200)) * psq));
|
||||
const increment = (Math.PI * 2) / stepCount;
|
||||
let rx = Math.abs(width / 2);
|
||||
let ry = Math.abs(height / 2);
|
||||
const curveFitRandomness = 1 - o.curveFitting;
|
||||
rx += _offsetOpt(rx * curveFitRandomness, o);
|
||||
ry += _offsetOpt(ry * curveFitRandomness, o);
|
||||
return { increment, rx, ry };
|
||||
}
|
||||
export function ellipseWithParams(x, y, o, ellipseParams) {
|
||||
const [ap1, cp1] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1, ellipseParams.increment * _offset(0.1, _offset(0.4, 1, o), o), o);
|
||||
let o1 = _curve(ap1, null, o);
|
||||
if ((!o.disableMultiStroke) && (o.roughness !== 0)) {
|
||||
const [ap2] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1.5, 0, o);
|
||||
const o2 = _curve(ap2, null, o);
|
||||
o1 = o1.concat(o2);
|
||||
}
|
||||
return {
|
||||
estimatedPoints: cp1,
|
||||
opset: { type: 'path', ops: o1 },
|
||||
};
|
||||
}
|
||||
export function arc(x, y, width, height, start, stop, closed, roughClosure, o) {
|
||||
const cx = x;
|
||||
const cy = y;
|
||||
let rx = Math.abs(width / 2);
|
||||
let ry = Math.abs(height / 2);
|
||||
rx += _offsetOpt(rx * 0.01, o);
|
||||
ry += _offsetOpt(ry * 0.01, o);
|
||||
let strt = start;
|
||||
let stp = stop;
|
||||
while (strt < 0) {
|
||||
strt += Math.PI * 2;
|
||||
stp += Math.PI * 2;
|
||||
}
|
||||
if ((stp - strt) > (Math.PI * 2)) {
|
||||
strt = 0;
|
||||
stp = Math.PI * 2;
|
||||
}
|
||||
const ellipseInc = (Math.PI * 2) / o.curveStepCount;
|
||||
const arcInc = Math.min(ellipseInc / 2, (stp - strt) / 2);
|
||||
const ops = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
|
||||
if (!o.disableMultiStroke) {
|
||||
const o2 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
|
||||
ops.push(...o2);
|
||||
}
|
||||
if (closed) {
|
||||
if (roughClosure) {
|
||||
ops.push(..._doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o), ..._doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o));
|
||||
}
|
||||
else {
|
||||
ops.push({ op: 'lineTo', data: [cx, cy] }, { op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] });
|
||||
}
|
||||
}
|
||||
return { type: 'path', ops };
|
||||
}
|
||||
export function svgPath(path, o) {
|
||||
const segments = normalize(absolutize(parsePath(path)));
|
||||
const ops = [];
|
||||
let first = [0, 0];
|
||||
let current = [0, 0];
|
||||
for (const { key, data } of segments) {
|
||||
switch (key) {
|
||||
case 'M': {
|
||||
current = [data[0], data[1]];
|
||||
first = [data[0], data[1]];
|
||||
break;
|
||||
}
|
||||
case 'L':
|
||||
ops.push(..._doubleLine(current[0], current[1], data[0], data[1], o));
|
||||
current = [data[0], data[1]];
|
||||
break;
|
||||
case 'C': {
|
||||
const [x1, y1, x2, y2, x, y] = data;
|
||||
ops.push(..._bezierTo(x1, y1, x2, y2, x, y, current, o));
|
||||
current = [x, y];
|
||||
break;
|
||||
}
|
||||
case 'Z':
|
||||
ops.push(..._doubleLine(current[0], current[1], first[0], first[1], o));
|
||||
current = [first[0], first[1]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { type: 'path', ops };
|
||||
}
|
||||
// Fills
|
||||
export function solidFillPolygon(polygonList, o) {
|
||||
const ops = [];
|
||||
for (const points of polygonList) {
|
||||
if (points.length) {
|
||||
const offset = o.maxRandomnessOffset || 0;
|
||||
const len = points.length;
|
||||
if (len > 2) {
|
||||
ops.push({ op: 'move', data: [points[0][0] + _offsetOpt(offset, o), points[0][1] + _offsetOpt(offset, o)] });
|
||||
for (let i = 1; i < len; i++) {
|
||||
ops.push({ op: 'lineTo', data: [points[i][0] + _offsetOpt(offset, o), points[i][1] + _offsetOpt(offset, o)] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { type: 'fillPath', ops };
|
||||
}
|
||||
export function patternFillPolygons(polygonList, o) {
|
||||
return getFiller(o, helper).fillPolygons(polygonList, o);
|
||||
}
|
||||
export function patternFillArc(x, y, width, height, start, stop, o) {
|
||||
const cx = x;
|
||||
const cy = y;
|
||||
let rx = Math.abs(width / 2);
|
||||
let ry = Math.abs(height / 2);
|
||||
rx += _offsetOpt(rx * 0.01, o);
|
||||
ry += _offsetOpt(ry * 0.01, o);
|
||||
let strt = start;
|
||||
let stp = stop;
|
||||
while (strt < 0) {
|
||||
strt += Math.PI * 2;
|
||||
stp += Math.PI * 2;
|
||||
}
|
||||
if ((stp - strt) > (Math.PI * 2)) {
|
||||
strt = 0;
|
||||
stp = Math.PI * 2;
|
||||
}
|
||||
const increment = (stp - strt) / o.curveStepCount;
|
||||
const points = [];
|
||||
for (let angle = strt; angle <= stp; angle = angle + increment) {
|
||||
points.push([cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)]);
|
||||
}
|
||||
points.push([cx + rx * Math.cos(stp), cy + ry * Math.sin(stp)]);
|
||||
points.push([cx, cy]);
|
||||
return patternFillPolygons([points], o);
|
||||
}
|
||||
export function randOffset(x, o) {
|
||||
return _offsetOpt(x, o);
|
||||
}
|
||||
export function randOffsetWithRange(min, max, o) {
|
||||
return _offset(min, max, o);
|
||||
}
|
||||
export function doubleLineFillOps(x1, y1, x2, y2, o) {
|
||||
return _doubleLine(x1, y1, x2, y2, o, true);
|
||||
}
|
||||
// Private helpers
|
||||
function cloneOptionsAlterSeed(ops) {
|
||||
const result = Object.assign({}, ops);
|
||||
result.randomizer = undefined;
|
||||
if (ops.seed) {
|
||||
result.seed = ops.seed + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function random(ops) {
|
||||
if (!ops.randomizer) {
|
||||
ops.randomizer = new Random(ops.seed || 0);
|
||||
}
|
||||
return ops.randomizer.next();
|
||||
}
|
||||
function _offset(min, max, ops, roughnessGain = 1) {
|
||||
return ops.roughness * roughnessGain * ((random(ops) * (max - min)) + min);
|
||||
}
|
||||
function _offsetOpt(x, ops, roughnessGain = 1) {
|
||||
return _offset(-x, x, ops, roughnessGain);
|
||||
}
|
||||
function _doubleLine(x1, y1, x2, y2, o, filling = false) {
|
||||
const singleStroke = filling ? o.disableMultiStrokeFill : o.disableMultiStroke;
|
||||
const o1 = _line(x1, y1, x2, y2, o, true, false);
|
||||
if (singleStroke) {
|
||||
return o1;
|
||||
}
|
||||
const o2 = _line(x1, y1, x2, y2, o, true, true);
|
||||
return o1.concat(o2);
|
||||
}
|
||||
function _line(x1, y1, x2, y2, o, move, overlay) {
|
||||
const lengthSq = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
|
||||
const length = Math.sqrt(lengthSq);
|
||||
let roughnessGain = 1;
|
||||
if (length < 200) {
|
||||
roughnessGain = 1;
|
||||
}
|
||||
else if (length > 500) {
|
||||
roughnessGain = 0.4;
|
||||
}
|
||||
else {
|
||||
roughnessGain = (-0.0016668) * length + 1.233334;
|
||||
}
|
||||
let offset = o.maxRandomnessOffset || 0;
|
||||
if ((offset * offset * 100) > lengthSq) {
|
||||
offset = length / 10;
|
||||
}
|
||||
const halfOffset = offset / 2;
|
||||
const divergePoint = 0.2 + random(o) * 0.2;
|
||||
let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
|
||||
let midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200;
|
||||
midDispX = _offsetOpt(midDispX, o, roughnessGain);
|
||||
midDispY = _offsetOpt(midDispY, o, roughnessGain);
|
||||
const ops = [];
|
||||
const randomHalf = () => _offsetOpt(halfOffset, o, roughnessGain);
|
||||
const randomFull = () => _offsetOpt(offset, o, roughnessGain);
|
||||
const preserveVertices = o.preserveVertices;
|
||||
if (move) {
|
||||
if (overlay) {
|
||||
ops.push({
|
||||
op: 'move', data: [
|
||||
x1 + (preserveVertices ? 0 : randomHalf()),
|
||||
y1 + (preserveVertices ? 0 : randomHalf()),
|
||||
],
|
||||
});
|
||||
}
|
||||
else {
|
||||
ops.push({
|
||||
op: 'move', data: [
|
||||
x1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
|
||||
y1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
if (overlay) {
|
||||
ops.push({
|
||||
op: 'bcurveTo',
|
||||
data: [
|
||||
midDispX + x1 + (x2 - x1) * divergePoint + randomHalf(),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + randomHalf(),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomHalf(),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomHalf(),
|
||||
x2 + (preserveVertices ? 0 : randomHalf()),
|
||||
y2 + (preserveVertices ? 0 : randomHalf()),
|
||||
],
|
||||
});
|
||||
}
|
||||
else {
|
||||
ops.push({
|
||||
op: 'bcurveTo',
|
||||
data: [
|
||||
midDispX + x1 + (x2 - x1) * divergePoint + randomFull(),
|
||||
midDispY + y1 + (y2 - y1) * divergePoint + randomFull(),
|
||||
midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomFull(),
|
||||
midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomFull(),
|
||||
x2 + (preserveVertices ? 0 : randomFull()),
|
||||
y2 + (preserveVertices ? 0 : randomFull()),
|
||||
],
|
||||
});
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
function _curveWithOffset(points, offset, o) {
|
||||
if (!points.length) {
|
||||
return [];
|
||||
}
|
||||
const ps = [];
|
||||
ps.push([
|
||||
points[0][0] + _offsetOpt(offset, o),
|
||||
points[0][1] + _offsetOpt(offset, o),
|
||||
]);
|
||||
ps.push([
|
||||
points[0][0] + _offsetOpt(offset, o),
|
||||
points[0][1] + _offsetOpt(offset, o),
|
||||
]);
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
ps.push([
|
||||
points[i][0] + _offsetOpt(offset, o),
|
||||
points[i][1] + _offsetOpt(offset, o),
|
||||
]);
|
||||
if (i === (points.length - 1)) {
|
||||
ps.push([
|
||||
points[i][0] + _offsetOpt(offset, o),
|
||||
points[i][1] + _offsetOpt(offset, o),
|
||||
]);
|
||||
}
|
||||
}
|
||||
return _curve(ps, null, o);
|
||||
}
|
||||
function _curve(points, closePoint, o) {
|
||||
const len = points.length;
|
||||
const ops = [];
|
||||
if (len > 3) {
|
||||
const b = [];
|
||||
const s = 1 - o.curveTightness;
|
||||
ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
|
||||
for (let i = 1; (i + 2) < len; i++) {
|
||||
const cachedVertArray = points[i];
|
||||
b[0] = [cachedVertArray[0], cachedVertArray[1]];
|
||||
b[1] = [cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6, cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6];
|
||||
b[2] = [points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6, points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6];
|
||||
b[3] = [points[i + 1][0], points[i + 1][1]];
|
||||
ops.push({ op: 'bcurveTo', data: [b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]] });
|
||||
}
|
||||
if (closePoint && closePoint.length === 2) {
|
||||
const ro = o.maxRandomnessOffset;
|
||||
ops.push({ op: 'lineTo', data: [closePoint[0] + _offsetOpt(ro, o), closePoint[1] + _offsetOpt(ro, o)] });
|
||||
}
|
||||
}
|
||||
else if (len === 3) {
|
||||
ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
|
||||
ops.push({
|
||||
op: 'bcurveTo',
|
||||
data: [
|
||||
points[1][0], points[1][1],
|
||||
points[2][0], points[2][1],
|
||||
points[2][0], points[2][1],
|
||||
],
|
||||
});
|
||||
}
|
||||
else if (len === 2) {
|
||||
ops.push(..._line(points[0][0], points[0][1], points[1][0], points[1][1], o, true, true));
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
function _computeEllipsePoints(increment, cx, cy, rx, ry, offset, overlap, o) {
|
||||
const coreOnly = o.roughness === 0;
|
||||
const corePoints = [];
|
||||
const allPoints = [];
|
||||
if (coreOnly) {
|
||||
increment = increment / 4;
|
||||
allPoints.push([
|
||||
cx + rx * Math.cos(-increment),
|
||||
cy + ry * Math.sin(-increment),
|
||||
]);
|
||||
for (let angle = 0; angle <= Math.PI * 2; angle = angle + increment) {
|
||||
const p = [
|
||||
cx + rx * Math.cos(angle),
|
||||
cy + ry * Math.sin(angle),
|
||||
];
|
||||
corePoints.push(p);
|
||||
allPoints.push(p);
|
||||
}
|
||||
allPoints.push([
|
||||
cx + rx * Math.cos(0),
|
||||
cy + ry * Math.sin(0),
|
||||
]);
|
||||
allPoints.push([
|
||||
cx + rx * Math.cos(increment),
|
||||
cy + ry * Math.sin(increment),
|
||||
]);
|
||||
}
|
||||
else {
|
||||
const radOffset = _offsetOpt(0.5, o) - (Math.PI / 2);
|
||||
allPoints.push([
|
||||
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
|
||||
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
|
||||
]);
|
||||
const endAngle = Math.PI * 2 + radOffset - 0.01;
|
||||
for (let angle = radOffset; angle < endAngle; angle = angle + increment) {
|
||||
const p = [
|
||||
_offsetOpt(offset, o) + cx + rx * Math.cos(angle),
|
||||
_offsetOpt(offset, o) + cy + ry * Math.sin(angle),
|
||||
];
|
||||
corePoints.push(p);
|
||||
allPoints.push(p);
|
||||
}
|
||||
allPoints.push([
|
||||
_offsetOpt(offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
|
||||
_offsetOpt(offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5),
|
||||
]);
|
||||
allPoints.push([
|
||||
_offsetOpt(offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
|
||||
_offsetOpt(offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap),
|
||||
]);
|
||||
allPoints.push([
|
||||
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
|
||||
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5),
|
||||
]);
|
||||
}
|
||||
return [allPoints, corePoints];
|
||||
}
|
||||
function _arc(increment, cx, cy, rx, ry, strt, stp, offset, o) {
|
||||
const radOffset = strt + _offsetOpt(0.1, o);
|
||||
const points = [];
|
||||
points.push([
|
||||
_offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
|
||||
_offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
|
||||
]);
|
||||
for (let angle = radOffset; angle <= stp; angle = angle + increment) {
|
||||
points.push([
|
||||
_offsetOpt(offset, o) + cx + rx * Math.cos(angle),
|
||||
_offsetOpt(offset, o) + cy + ry * Math.sin(angle),
|
||||
]);
|
||||
}
|
||||
points.push([
|
||||
cx + rx * Math.cos(stp),
|
||||
cy + ry * Math.sin(stp),
|
||||
]);
|
||||
points.push([
|
||||
cx + rx * Math.cos(stp),
|
||||
cy + ry * Math.sin(stp),
|
||||
]);
|
||||
return _curve(points, null, o);
|
||||
}
|
||||
function _bezierTo(x1, y1, x2, y2, x, y, current, o) {
|
||||
const ops = [];
|
||||
const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.3];
|
||||
let f = [0, 0];
|
||||
const iterations = o.disableMultiStroke ? 1 : 2;
|
||||
const preserveVertices = o.preserveVertices;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
if (i === 0) {
|
||||
ops.push({ op: 'move', data: [current[0], current[1]] });
|
||||
}
|
||||
else {
|
||||
ops.push({ op: 'move', data: [current[0] + (preserveVertices ? 0 : _offsetOpt(ros[0], o)), current[1] + (preserveVertices ? 0 : _offsetOpt(ros[0], o))] });
|
||||
}
|
||||
f = preserveVertices ? [x, y] : [x + _offsetOpt(ros[i], o), y + _offsetOpt(ros[i], o)];
|
||||
ops.push({
|
||||
op: 'bcurveTo',
|
||||
data: [
|
||||
x1 + _offsetOpt(ros[i], o), y1 + _offsetOpt(ros[i], o),
|
||||
x2 + _offsetOpt(ros[i], o), y2 + _offsetOpt(ros[i], o),
|
||||
f[0], f[1],
|
||||
],
|
||||
});
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Config } from './core';
|
||||
import { RoughCanvas } from './canvas';
|
||||
import { RoughGenerator } from './generator';
|
||||
import { RoughSVG } from './svg';
|
||||
declare const _default: {
|
||||
canvas(canvas: HTMLCanvasElement, config?: Config): RoughCanvas;
|
||||
svg(svg: SVGSVGElement, config?: Config): RoughSVG;
|
||||
generator(config?: Config): RoughGenerator;
|
||||
newSeed(): number;
|
||||
};
|
||||
export default _default;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { RoughCanvas } from './canvas';
|
||||
import { RoughGenerator } from './generator';
|
||||
import { RoughSVG } from './svg';
|
||||
export default {
|
||||
canvas(canvas, config) {
|
||||
return new RoughCanvas(canvas, config);
|
||||
},
|
||||
svg(svg, config) {
|
||||
return new RoughSVG(svg, config);
|
||||
},
|
||||
generator(config) {
|
||||
return new RoughGenerator(config);
|
||||
},
|
||||
newSeed() {
|
||||
return RoughGenerator.newSeed();
|
||||
},
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Config, Options, OpSet, ResolvedOptions, Drawable } from './core';
|
||||
import { RoughGenerator } from './generator';
|
||||
import { Point } from './geometry';
|
||||
export declare class RoughSVG {
|
||||
private gen;
|
||||
private svg;
|
||||
constructor(svg: SVGSVGElement, config?: Config);
|
||||
draw(drawable: Drawable): SVGGElement;
|
||||
private fillSketch;
|
||||
get generator(): RoughGenerator;
|
||||
getDefaultOptions(): ResolvedOptions;
|
||||
opsToPath(drawing: OpSet, fixedDecimalPlaceDigits?: number): string;
|
||||
line(x1: number, y1: number, x2: number, y2: number, options?: Options): SVGGElement;
|
||||
rectangle(x: number, y: number, width: number, height: number, options?: Options): SVGGElement;
|
||||
ellipse(x: number, y: number, width: number, height: number, options?: Options): SVGGElement;
|
||||
circle(x: number, y: number, diameter: number, options?: Options): SVGGElement;
|
||||
linearPath(points: Point[], options?: Options): SVGGElement;
|
||||
polygon(points: Point[], options?: Options): SVGGElement;
|
||||
arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed?: boolean, options?: Options): SVGGElement;
|
||||
curve(points: Point[] | Point[][], options?: Options): SVGGElement;
|
||||
path(d: string, options?: Options): SVGGElement;
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
import { SVGNS } from './core';
|
||||
import { RoughGenerator } from './generator';
|
||||
export class RoughSVG {
|
||||
constructor(svg, config) {
|
||||
this.svg = svg;
|
||||
this.gen = new RoughGenerator(config);
|
||||
}
|
||||
draw(drawable) {
|
||||
const sets = drawable.sets || [];
|
||||
const o = drawable.options || this.getDefaultOptions();
|
||||
const doc = this.svg.ownerDocument || window.document;
|
||||
const g = doc.createElementNS(SVGNS, 'g');
|
||||
const precision = drawable.options.fixedDecimalPlaceDigits;
|
||||
for (const drawing of sets) {
|
||||
let path = null;
|
||||
switch (drawing.type) {
|
||||
case 'path': {
|
||||
path = doc.createElementNS(SVGNS, 'path');
|
||||
path.setAttribute('d', this.opsToPath(drawing, precision));
|
||||
path.setAttribute('stroke', o.stroke);
|
||||
path.setAttribute('stroke-width', o.strokeWidth + '');
|
||||
path.setAttribute('fill', 'none');
|
||||
if (o.strokeLineDash) {
|
||||
path.setAttribute('stroke-dasharray', o.strokeLineDash.join(' ').trim());
|
||||
}
|
||||
if (o.strokeLineDashOffset) {
|
||||
path.setAttribute('stroke-dashoffset', `${o.strokeLineDashOffset}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'fillPath': {
|
||||
path = doc.createElementNS(SVGNS, 'path');
|
||||
path.setAttribute('d', this.opsToPath(drawing, precision));
|
||||
path.setAttribute('stroke', 'none');
|
||||
path.setAttribute('stroke-width', '0');
|
||||
path.setAttribute('fill', o.fill || '');
|
||||
if (drawable.shape === 'curve' || drawable.shape === 'polygon') {
|
||||
path.setAttribute('fill-rule', 'evenodd');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'fillSketch': {
|
||||
path = this.fillSketch(doc, drawing, o);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
g.appendChild(path);
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
fillSketch(doc, drawing, o) {
|
||||
let fweight = o.fillWeight;
|
||||
if (fweight < 0) {
|
||||
fweight = o.strokeWidth / 2;
|
||||
}
|
||||
const path = doc.createElementNS(SVGNS, 'path');
|
||||
path.setAttribute('d', this.opsToPath(drawing, o.fixedDecimalPlaceDigits));
|
||||
path.setAttribute('stroke', o.fill || '');
|
||||
path.setAttribute('stroke-width', fweight + '');
|
||||
path.setAttribute('fill', 'none');
|
||||
if (o.fillLineDash) {
|
||||
path.setAttribute('stroke-dasharray', o.fillLineDash.join(' ').trim());
|
||||
}
|
||||
if (o.fillLineDashOffset) {
|
||||
path.setAttribute('stroke-dashoffset', `${o.fillLineDashOffset}`);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
get generator() {
|
||||
return this.gen;
|
||||
}
|
||||
getDefaultOptions() {
|
||||
return this.gen.defaultOptions;
|
||||
}
|
||||
opsToPath(drawing, fixedDecimalPlaceDigits) {
|
||||
return this.gen.opsToPath(drawing, fixedDecimalPlaceDigits);
|
||||
}
|
||||
line(x1, y1, x2, y2, options) {
|
||||
const d = this.gen.line(x1, y1, x2, y2, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
rectangle(x, y, width, height, options) {
|
||||
const d = this.gen.rectangle(x, y, width, height, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
ellipse(x, y, width, height, options) {
|
||||
const d = this.gen.ellipse(x, y, width, height, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
circle(x, y, diameter, options) {
|
||||
const d = this.gen.circle(x, y, diameter, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
linearPath(points, options) {
|
||||
const d = this.gen.linearPath(points, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
polygon(points, options) {
|
||||
const d = this.gen.polygon(points, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
arc(x, y, width, height, start, stop, closed = false, options) {
|
||||
const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
curve(points, options) {
|
||||
const d = this.gen.curve(points, options);
|
||||
return this.draw(d);
|
||||
}
|
||||
path(d, options) {
|
||||
const drawing = this.gen.path(d, options);
|
||||
return this.draw(drawing);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user