tldraw
Version:
A tiny little drawing editor.
276 lines (275 loc) • 11.4 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var DrawShapeUtil_exports = {};
__export(DrawShapeUtil_exports, {
DrawShapeUtil: () => DrawShapeUtil
});
module.exports = __toCommonJS(DrawShapeUtil_exports);
var import_jsx_runtime = require("react/jsx-runtime");
var import_editor = require("@tldraw/editor");
var import_ShapeFill = require("../shared/ShapeFill");
var import_default_shape_constants = require("../shared/default-shape-constants");
var import_defaultStyleDefs = require("../shared/defaultStyleDefs");
var import_getStrokePoints = require("../shared/freehand/getStrokePoints");
var import_svg = require("../shared/freehand/svg");
var import_svgInk = require("../shared/freehand/svgInk");
var import_interpolate_props = require("../shared/interpolate-props");
var import_useDefaultColorTheme = require("../shared/useDefaultColorTheme");
var import_getPath = require("./getPath");
class DrawShapeUtil extends import_editor.ShapeUtil {
static type = "draw";
static props = import_editor.drawShapeProps;
static migrations = import_editor.drawShapeMigrations;
options = {
maxPointsPerShape: 600
};
hideResizeHandles(shape) {
return getIsDot(shape);
}
hideRotateHandle(shape) {
return getIsDot(shape);
}
hideSelectionBoundsFg(shape) {
return getIsDot(shape);
}
getDefaultProps() {
return {
segments: [],
color: "black",
fill: "none",
dash: "draw",
size: "m",
isComplete: false,
isClosed: false,
isPen: false,
scale: 1,
scaleX: 1,
scaleY: 1
};
}
getGeometry(shape) {
const points = (0, import_getPath.getPointsFromDrawSegments)(
shape.props.segments,
shape.props.scaleX,
shape.props.scaleY
);
const sw = (import_default_shape_constants.STROKE_SIZES[shape.props.size] + 1) * shape.props.scale;
if (shape.props.segments.length === 1) {
const box = import_editor.Box.FromPoints(points);
if (box.width < sw * 2 && box.height < sw * 2) {
return new import_editor.Circle2d({
x: -sw,
y: -sw,
radius: sw,
isFilled: true
});
}
}
const strokePoints = (0, import_getStrokePoints.getStrokePoints)(
points,
(0, import_getPath.getFreehandOptions)(shape.props, sw, shape.props.isPen, true)
).map((p) => p.point);
if (shape.props.isClosed && strokePoints.length > 2) {
return new import_editor.Polygon2d({
points: strokePoints,
isFilled: shape.props.fill !== "none"
});
}
if (strokePoints.length === 1) {
return new import_editor.Circle2d({
x: -sw,
y: -sw,
radius: sw,
isFilled: true
});
}
return new import_editor.Polyline2d({
points: strokePoints
});
}
component(shape) {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_editor.SVGContainer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawShapeSvg, { shape }) });
}
indicator(shape) {
const allPointsFromSegments = (0, import_getPath.getPointsFromDrawSegments)(
shape.props.segments,
shape.props.scaleX,
shape.props.scaleY
);
let sw = (import_default_shape_constants.STROKE_SIZES[shape.props.size] + 1) * shape.props.scale;
const forceSolid = (0, import_editor.useValue)(
"force solid",
() => {
const zoomLevel = this.editor.getEfficientZoomLevel();
return zoomLevel < 0.5 && zoomLevel < 1.5 / sw;
},
[this.editor, sw]
);
if (!forceSolid && !shape.props.isPen && shape.props.dash === "draw" && allPointsFromSegments.length === 1) {
sw += (0, import_editor.rng)(shape.id)() * (sw / 6);
}
const showAsComplete = shape.props.isComplete || (0, import_editor.last)(shape.props.segments)?.type === "straight";
const options = (0, import_getPath.getFreehandOptions)(shape.props, sw, showAsComplete, true);
const strokePoints = (0, import_getStrokePoints.getStrokePoints)(allPointsFromSegments, options);
const solidStrokePath = strokePoints.length > 1 ? (0, import_svg.getSvgPathFromStrokePoints)(strokePoints, shape.props.isClosed) : getDot(allPointsFromSegments[0], sw);
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: solidStrokePath });
}
useLegacyIndicator() {
return false;
}
getIndicatorPath(shape) {
const allPointsFromSegments = (0, import_getPath.getPointsFromDrawSegments)(
shape.props.segments,
shape.props.scaleX,
shape.props.scaleY
);
let sw = (import_default_shape_constants.STROKE_SIZES[shape.props.size] + 1) * shape.props.scale;
const zoomLevel = this.editor.getEfficientZoomLevel();
const forceSolid = zoomLevel < 0.5 && zoomLevel < 1.5 / sw;
if (!forceSolid && !shape.props.isPen && shape.props.dash === "draw" && allPointsFromSegments.length === 1) {
sw += (0, import_editor.rng)(shape.id)() * (sw / 6);
}
const showAsComplete = shape.props.isComplete || (0, import_editor.last)(shape.props.segments)?.type === "straight";
const options = (0, import_getPath.getFreehandOptions)(shape.props, sw, showAsComplete, true);
const strokePoints = (0, import_getStrokePoints.getStrokePoints)(allPointsFromSegments, options);
const solidStrokePath = strokePoints.length > 1 ? (0, import_svg.getSvgPathFromStrokePoints)(strokePoints, shape.props.isClosed) : getDot(allPointsFromSegments[0], sw);
return new Path2D(solidStrokePath);
}
toSvg(shape, ctx) {
ctx.addExportDef((0, import_defaultStyleDefs.getFillDefForExport)(shape.props.fill));
const scaleFactor = 1 / shape.props.scale;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { transform: `scale(${scaleFactor})`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DrawShapeSvg, { shape, zoomOverride: 1 }) });
}
getCanvasSvgDefs() {
return [(0, import_defaultStyleDefs.getFillDefForCanvas)()];
}
onResize(shape, info) {
const newScaleX = info.scaleX * shape.props.scaleX;
const newScaleY = info.scaleY * shape.props.scaleY;
if (newScaleX === 0 || newScaleY === 0) return;
return {
props: {
scaleX: newScaleX,
scaleY: newScaleY
}
};
}
expandSelectionOutlinePx(shape) {
const multiplier = shape.props.dash === "draw" ? 1.6 : 1;
return import_default_shape_constants.STROKE_SIZES[shape.props.size] * multiplier / 2 * shape.props.scale;
}
getInterpolatedProps(startShape, endShape, t) {
return {
...t > 0.5 ? endShape.props : startShape.props,
segments: (0, import_interpolate_props.interpolateSegments)(startShape.props.segments, endShape.props.segments, t),
scale: (0, import_editor.lerp)(startShape.props.scale, endShape.props.scale, t)
};
}
}
function getDot(point, sw) {
const r = (sw + 1) * 0.5;
return `M ${point.x} ${point.y} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${r * 2},0`;
}
function getIsDot(shape) {
return shape.props.segments.length === 1 && shape.props.segments[0].path.length < 24;
}
function DrawShapeSvg({ shape, zoomOverride }) {
const theme = (0, import_useDefaultColorTheme.useDefaultColorTheme)();
const editor = (0, import_editor.useEditor)();
const allPointsFromSegments = (0, import_getPath.getPointsFromDrawSegments)(
shape.props.segments,
shape.props.scaleX,
shape.props.scaleY
);
const showAsComplete = shape.props.isComplete || (0, import_editor.last)(shape.props.segments)?.type === "straight";
let sw = (import_default_shape_constants.STROKE_SIZES[shape.props.size] + 1) * shape.props.scale;
const forceSolid = (0, import_editor.useValue)(
"force solid",
() => {
const zoomLevel = zoomOverride ?? editor.getEfficientZoomLevel();
return zoomLevel < 0.5 && zoomLevel < 1.5 / sw;
},
[editor, sw, zoomOverride]
);
const dotAdjustment = (0, import_editor.useValue)(
"dot adjustment",
() => {
const zoomLevel = zoomOverride ?? editor.getEfficientZoomLevel();
return zoomLevel < 0.2 ? 9 : 0.1;
},
[editor, zoomOverride]
);
if (!forceSolid && !shape.props.isPen && shape.props.dash === "draw" && allPointsFromSegments.length === 1) {
sw += (0, import_editor.rng)(shape.id)() * (sw / 6);
}
const options = (0, import_getPath.getFreehandOptions)(shape.props, sw, showAsComplete, forceSolid);
if (!forceSolid && shape.props.dash === "draw") {
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
shape.props.isClosed && shape.props.fill && allPointsFromSegments.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_ShapeFill.ShapeFill,
{
d: (0, import_svg.getSvgPathFromStrokePoints)(
(0, import_getStrokePoints.getStrokePoints)(allPointsFromSegments, options),
shape.props.isClosed
),
theme,
color: shape.props.color,
fill: shape.props.isClosed ? shape.props.fill : "none",
scale: shape.props.scale
}
) : null,
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"path",
{
d: (0, import_svgInk.svgInk)(allPointsFromSegments, options),
strokeLinecap: "round",
fill: (0, import_editor.getColorValue)(theme, shape.props.color, "solid")
}
)
] });
}
const strokePoints = (0, import_getStrokePoints.getStrokePoints)(allPointsFromSegments, options);
const isDot = strokePoints.length < 2;
const solidStrokePath = isDot ? getDot(allPointsFromSegments[0], 0) : (0, import_svg.getSvgPathFromStrokePoints)(strokePoints, shape.props.isClosed);
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_ShapeFill.ShapeFill,
{
d: solidStrokePath,
theme,
color: shape.props.color,
fill: isDot || shape.props.isClosed ? shape.props.fill : "none",
scale: shape.props.scale
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"path",
{
d: solidStrokePath,
strokeLinecap: "round",
fill: isDot ? (0, import_editor.getColorValue)(theme, shape.props.color, "solid") : "none",
stroke: (0, import_editor.getColorValue)(theme, shape.props.color, "solid"),
strokeWidth: sw,
strokeDasharray: isDot ? "none" : (0, import_getPath.getDrawShapeStrokeDashArray)(shape, sw, dotAdjustment),
strokeDashoffset: "0"
}
)
] });
}
//# sourceMappingURL=DrawShapeUtil.js.map