Files
AFFiNE-Mirror/blocksuite/affine/block-surface/src/utils/rough/canvas.ts
T
2024-12-20 15:38:06 +08:00

215 lines
4.9 KiB
TypeScript

import type {
Config,
Drawable,
OpSet,
Options,
ResolvedOptions,
} from './core.js';
import { RoughGenerator } from './generator.js';
import type { Point } from './geometry.js';
export class RoughCanvas {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private gen: RoughGenerator;
get generator(): RoughGenerator {
return this.gen;
}
constructor(canvas: HTMLCanvasElement, config?: Config) {
this.canvas = canvas;
this.ctx = this.canvas.getContext('2d')!;
this.gen = new RoughGenerator(config);
}
private _drawToContext(
ctx: CanvasRenderingContext2D,
drawing: OpSet,
fixedDecimals?: number,
rule: CanvasFillRule = 'nonzero'
) {
ctx.beginPath();
for (const item of drawing.ops) {
const data =
typeof fixedDecimals === 'number' && fixedDecimals >= 0
? item.data.map(d => +d.toFixed(fixedDecimals))
: item.data;
switch (item.op) {
case 'move':
ctx.moveTo(data[0], data[1]);
break;
case 'bcurveTo':
ctx.bezierCurveTo(
data[0],
data[1],
data[2],
data[3],
data[4],
data[5]
);
break;
case 'lineTo':
ctx.lineTo(data[0], data[1]);
break;
}
}
if (drawing.type === 'fillPath') {
ctx.fill(rule);
} else {
ctx.stroke();
}
}
private fillSketch(
ctx: CanvasRenderingContext2D,
drawing: OpSet,
o: ResolvedOptions
) {
let fweight = o.fillWeight;
if (fweight < 0) {
fweight = o.strokeWidth / 2;
}
ctx.save();
if (o.fillLineDash) {
ctx.setLineDash(o.fillLineDash);
}
if (o.fillLineDashOffset) {
ctx.lineDashOffset = o.fillLineDashOffset;
}
ctx.strokeStyle = o.fill || '';
ctx.lineWidth = fweight;
this._drawToContext(ctx, drawing, o.fixedDecimalPlaceDigits);
ctx.restore();
}
arc(
x: number,
y: number,
width: number,
height: number,
start: number,
stop: number,
closed = false,
options?: Options
): Drawable {
const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
this.draw(d);
return d;
}
circle(x: number, y: number, diameter: number, options?: Options): Drawable {
const d = this.gen.circle(x, y, diameter, options);
this.draw(d);
return d;
}
curve(points: Point[], options?: Options): Drawable {
const d = this.gen.curve(points, options);
this.draw(d);
return d;
}
draw(drawable: Drawable): void {
const sets = drawable.sets || [];
const o = drawable.options || this.getDefaultOptions();
const ctx = this.ctx;
const precision = drawable.options.fixedDecimalPlaceDigits;
for (const drawing of sets) {
switch (drawing.type) {
case 'path':
ctx.save();
ctx.strokeStyle = o.stroke === 'none' ? 'transparent' : o.stroke;
ctx.lineWidth = o.strokeWidth;
if (o.strokeLineDash) {
ctx.setLineDash(o.strokeLineDash);
}
if (o.strokeLineDashOffset) {
ctx.lineDashOffset = o.strokeLineDashOffset;
}
this._drawToContext(ctx, drawing, precision);
ctx.restore();
break;
case 'fillPath': {
ctx.save();
ctx.fillStyle = o.fill || '';
const fillRule: CanvasFillRule =
drawable.shape === 'curve' ||
drawable.shape === 'polygon' ||
drawable.shape === 'path'
? 'evenodd'
: 'nonzero';
this._drawToContext(ctx, drawing, precision, fillRule);
ctx.restore();
break;
}
case 'fillSketch':
this.fillSketch(ctx, drawing, o);
break;
}
}
}
ellipse(
x: number,
y: number,
width: number,
height: number,
options?: Options
): Drawable {
const d = this.gen.ellipse(x, y, width, height, options);
this.draw(d);
return d;
}
getDefaultOptions(): ResolvedOptions {
return this.gen.defaultOptions;
}
line(
x1: number,
y1: number,
x2: number,
y2: number,
options?: Options
): Drawable {
const d = this.gen.line(x1, y1, x2, y2, options);
this.draw(d);
return d;
}
linearPath(points: Point[], options?: Options): Drawable {
const d = this.gen.linearPath(points, options);
this.draw(d);
return d;
}
path(d: string, options?: Options): Drawable {
const drawing = this.gen.path(d, options);
this.draw(drawing);
return drawing;
}
polygon(points: Point[], options?: Options): Drawable {
const d = this.gen.polygon(points, options);
this.draw(d);
return d;
}
rectangle(
x: number,
y: number,
width: number,
height: number,
options?: Options
): Drawable {
const d = this.gen.rectangle(x, y, width, height, options);
this.draw(d);
return d;
}
}