js-draw
Version: 
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
122 lines (121 loc) • 4.34 kB
JavaScript
import { Rect2, Color4, PathCommandType, Vec2, LineSegment2, } from '@js-draw/math';
import  Stroke  from '../Stroke.mjs';
import  Viewport  from '../../Viewport.mjs';
import  makeShapeFitAutocorrect  from './autocorrect/makeShapeFitAutocorrect.mjs';
/**
 * Creates a freehand line builder that creates strokes from line segments
 * rather than Bézier curves.
 *
 * Example:
 * [[include:doc-pages/inline-examples/changing-pen-types.md]]
 */
export const makePolylineBuilder = makeShapeFitAutocorrect((initialPoint, viewport) => {
    // Fit to a value slightly smaller than the pixel size. A larger value can
    // cause the stroke to appear jagged at some zoom levels.
    const minFit = viewport.getSizeOfPixelOnCanvas() * 0.65;
    return new PolylineBuilder(initialPoint, minFit, viewport);
});
export default class PolylineBuilder {
    constructor(startPoint, minFitAllowed, viewport) {
        this.minFitAllowed = minFitAllowed;
        this.viewport = viewport;
        this.parts = [];
        this.widthAverageNumSamples = 1;
        this.lastLineSegment = null;
        this.averageWidth = startPoint.width;
        this.startPoint = {
            ...startPoint,
            pos: this.roundPoint(startPoint.pos),
        };
        this.lastPoint = this.startPoint.pos;
        this.bbox = new Rect2(this.startPoint.pos.x, this.startPoint.pos.y, 0, 0);
        this.parts = [
            {
                kind: PathCommandType.MoveTo,
                point: this.startPoint.pos,
            },
        ];
    }
    getBBox() {
        return this.bbox.grownBy(this.averageWidth);
    }
    getRenderingStyle() {
        return {
            fill: Color4.transparent,
            stroke: {
                color: this.startPoint.color,
                width: this.roundDistance(this.averageWidth),
            },
        };
    }
    previewCurrentPath() {
        const startPoint = this.startPoint.pos;
        const commands = [...this.parts];
        // TODO: For now, this is necesary for the path to be visible.
        if (commands.length <= 1) {
            commands.push({
                kind: PathCommandType.LineTo,
                point: startPoint.plus(Vec2.of(this.averageWidth / 4, 0)),
            });
        }
        return {
            startPoint,
            commands,
            style: this.getRenderingStyle(),
        };
    }
    previewFullPath() {
        return [this.previewCurrentPath()];
    }
    preview(renderer) {
        const paths = this.previewFullPath();
        if (paths) {
            const approxBBox = this.viewport.visibleRect;
            renderer.startObject(approxBBox);
            for (const path of paths) {
                renderer.drawPath(path);
            }
            renderer.endObject();
        }
    }
    build() {
        return new Stroke(this.previewFullPath());
    }
    getMinFit() {
        let minFit = Math.min(this.minFitAllowed, this.averageWidth / 4);
        if (minFit < 1e-10) {
            minFit = this.minFitAllowed;
        }
        return minFit;
    }
    roundPoint(point) {
        const minFit = this.getMinFit();
        return Viewport.roundPoint(point, minFit);
    }
    roundDistance(dist) {
        const minFit = this.getMinFit();
        return Viewport.roundPoint(dist, minFit);
    }
    addPoint(newPoint) {
        this.widthAverageNumSamples++;
        this.averageWidth =
            (this.averageWidth * (this.widthAverageNumSamples - 1)) / this.widthAverageNumSamples +
                newPoint.width / this.widthAverageNumSamples;
        const roundedPoint = this.roundPoint(newPoint.pos);
        if (!roundedPoint.eq(this.lastPoint)) {
            // If almost exactly in the same line as the previous
            if (this.lastLineSegment &&
                this.lastLineSegment.direction.dot(roundedPoint.minus(this.lastPoint).normalized()) > 0.997) {
                this.parts.pop();
                this.lastPoint = this.lastLineSegment.p1;
            }
            this.parts.push({
                kind: PathCommandType.LineTo,
                point: this.roundPoint(newPoint.pos),
            });
            this.bbox = this.bbox.grownToPoint(roundedPoint);
            this.lastLineSegment = new LineSegment2(this.lastPoint, roundedPoint);
            this.lastPoint = roundedPoint;
        }
    }
}