UNPKG

js-draw

Version:

Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.

177 lines (176 loc) 6.76 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.makeFreehandLineBuilder = void 0; const math_1 = require("@js-draw/math"); const Stroke_1 = __importDefault(require("../Stroke")); const Viewport_1 = __importDefault(require("../../Viewport")); const StrokeSmoother_1 = require("../util/StrokeSmoother"); const makeShapeFitAutocorrect_1 = __importDefault(require("./autocorrect/makeShapeFitAutocorrect")); /** * Creates a stroke builder that draws freehand lines. * * Example: * [[include:doc-pages/inline-examples/changing-pen-types.md]] */ exports.makeFreehandLineBuilder = (0, makeShapeFitAutocorrect_1.default)((initialPoint, viewport) => { // Don't smooth if input is more than ± 3 pixels from the true curve, do smooth if // less than ±1 px from the curve. const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 3; const minSmoothingDist = viewport.getSizeOfPixelOnCanvas(); return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist, viewport); }); // Handles stroke smoothing and creates Strokes from user/stylus input. class FreehandLineBuilder { constructor(startPoint, minFitAllowed, maxFitAllowed, viewport) { this.startPoint = startPoint; this.minFitAllowed = minFitAllowed; this.viewport = viewport; this.isFirstSegment = true; this.parts = []; this.widthAverageNumSamples = 1; this.curveFitter = new StrokeSmoother_1.StrokeSmoother(startPoint, minFitAllowed, maxFitAllowed, (curve) => this.addCurve(curve)); this.averageWidth = startPoint.width; this.bbox = new math_1.Rect2(this.startPoint.pos.x, this.startPoint.pos.y, 0, 0); } getBBox() { return this.bbox; } getRenderingStyle() { return { fill: math_1.Color4.transparent, stroke: { color: this.startPoint.color, width: this.roundDistance(this.averageWidth), }, }; } previewCurrentPath() { const path = this.parts.slice(); const commands = [...path, ...this.curveToPathCommands(this.curveFitter.preview())]; const startPoint = this.startPoint.pos; return { startPoint, commands, style: this.getRenderingStyle(), }; } previewFullPath() { const preview = this.previewCurrentPath(); if (preview) { return [preview]; } return null; } previewStroke() { const pathPreview = this.previewFullPath(); if (pathPreview) { return new Stroke_1.default(pathPreview); } return null; } 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() { this.curveFitter.finalizeCurrentCurve(); return this.previewStroke(); } getMinFit() { let minFit = Math.min(this.minFitAllowed, this.averageWidth / 3); if (minFit < 1e-10) { minFit = this.minFitAllowed; } return minFit; } roundPoint(point) { const minFit = this.getMinFit(); return Viewport_1.default.roundPoint(point, minFit); } roundDistance(dist) { const minFit = this.getMinFit(); return Viewport_1.default.roundPoint(dist, minFit); } curveToPathCommands(curve) { // Case where no points have been added if (!curve) { // Don't create a circle around the initial point if the stroke has more than one point. if (!this.isFirstSegment) { return []; } // Make the circle small -- because of the stroke style, we'll be drawing a stroke around it. const width = Viewport_1.default.roundPoint(this.averageWidth / 10, Math.min(this.minFitAllowed, this.averageWidth / 10)); const center = this.roundPoint(this.startPoint.pos); // Start on the right, cycle clockwise: // | // ----- ← // | // Draw a circle-ish shape around the start point return [ { kind: math_1.PathCommandType.QuadraticBezierTo, controlPoint: center.plus(math_1.Vec2.of(width, width)), // Bottom of the circle // | // ----- // | // ↑ endPoint: center.plus(math_1.Vec2.of(0, width)), }, { kind: math_1.PathCommandType.QuadraticBezierTo, controlPoint: center.plus(math_1.Vec2.of(-width, width)), endPoint: center.plus(math_1.Vec2.of(-width, 0)), }, { kind: math_1.PathCommandType.QuadraticBezierTo, controlPoint: center.plus(math_1.Vec2.of(-width, -width)), endPoint: center.plus(math_1.Vec2.of(0, -width)), }, { kind: math_1.PathCommandType.QuadraticBezierTo, controlPoint: center.plus(math_1.Vec2.of(width, -width)), endPoint: center.plus(math_1.Vec2.of(width, 0)), }, ]; } const result = []; if (this.isFirstSegment) { result.push({ kind: math_1.PathCommandType.MoveTo, point: this.roundPoint(curve.startPoint), }); } result.push({ kind: math_1.PathCommandType.QuadraticBezierTo, controlPoint: this.roundPoint(curve.controlPoint), endPoint: this.roundPoint(curve.endPoint), }); return result; } addCurve(curve) { const parts = this.curveToPathCommands(curve); this.parts.push(...parts); if (this.isFirstSegment) { this.isFirstSegment = false; } } addPoint(newPoint) { this.curveFitter.addPoint(newPoint); this.widthAverageNumSamples++; this.averageWidth = (this.averageWidth * (this.widthAverageNumSamples - 1)) / this.widthAverageNumSamples + newPoint.width / this.widthAverageNumSamples; } } exports.default = FreehandLineBuilder;