tldraw
Version:
A tiny little drawing editor.
260 lines (259 loc) • 11.8 kB
JavaScript
"use strict";
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 arrowLabel_exports = {};
__export(arrowLabel_exports, {
getArrowLabelFontSize: () => getArrowLabelFontSize,
getArrowLabelPosition: () => getArrowLabelPosition
});
module.exports = __toCommonJS(arrowLabel_exports);
var import_editor = require("@tldraw/editor");
var import_default_shape_constants = require("../shared/default-shape-constants");
var import_ArrowShapeUtil = require("./ArrowShapeUtil");
var import_shared = require("./shared");
const labelSizeCacheCache = new import_editor.WeakCache();
function getLabelSizeCache(editor) {
return labelSizeCacheCache.get(editor, () => {
return editor.store.createComputedCache("arrowLabelSize", (shape) => {
const info = (0, import_shared.getArrowInfo)(editor, shape);
let width = 0;
let height = 0;
const bodyGeom = info.isStraight ? new import_editor.Edge2d({
start: import_editor.Vec.From(info.start.point),
end: import_editor.Vec.From(info.end.point)
}) : new import_editor.Arc2d({
center: import_editor.Vec.Cast(info.handleArc.center),
start: import_editor.Vec.Cast(info.start.point),
end: import_editor.Vec.Cast(info.end.point),
sweepFlag: info.bodyArc.sweepFlag,
largeArcFlag: info.bodyArc.largeArcFlag
});
if (shape.props.text.trim()) {
const bodyBounds = bodyGeom.bounds;
const fontSize = getArrowLabelFontSize(shape);
const { w, h } = editor.textMeasure.measureText(shape.props.text, {
...import_default_shape_constants.TEXT_PROPS,
fontFamily: import_default_shape_constants.FONT_FAMILIES[shape.props.font],
fontSize,
maxWidth: null
});
width = w;
height = h;
let shouldSquish = false;
if (bodyBounds.width > bodyBounds.height) {
width = Math.max(Math.min(w, 64), Math.min(bodyBounds.width - 64, w));
shouldSquish = true;
} else if (width > 16 * fontSize) {
width = 16 * fontSize;
shouldSquish = true;
}
if (shouldSquish) {
const { w: squishedWidth, h: squishedHeight } = editor.textMeasure.measureText(
shape.props.text,
{
...import_default_shape_constants.TEXT_PROPS,
fontFamily: import_default_shape_constants.FONT_FAMILIES[shape.props.font],
fontSize,
maxWidth: width
}
);
width = squishedWidth;
height = squishedHeight;
}
}
return new import_editor.Vec(width, height).addScalar(import_default_shape_constants.ARROW_LABEL_PADDING * 2 * shape.props.scale);
});
});
}
function getArrowLabelSize(editor, shape) {
if (shape.props.text.trim() === "") {
return new import_editor.Vec(0, 0).addScalar(import_default_shape_constants.ARROW_LABEL_PADDING * 2 * shape.props.scale);
}
return getLabelSizeCache(editor).get(shape.id) ?? new import_editor.Vec(0, 0);
}
function getLabelToArrowPadding(shape) {
const strokeWidth = import_default_shape_constants.STROKE_SIZES[shape.props.size];
const labelToArrowPadding = (import_default_shape_constants.LABEL_TO_ARROW_PADDING + (strokeWidth - import_default_shape_constants.STROKE_SIZES.s) * 2 + (strokeWidth === import_default_shape_constants.STROKE_SIZES.xl ? 20 : 0)) * shape.props.scale;
return labelToArrowPadding;
}
function getStraightArrowLabelRange(editor, shape, info) {
const labelSize = getArrowLabelSize(editor, shape);
const labelToArrowPadding = getLabelToArrowPadding(shape);
const startOffset = import_editor.Vec.Nudge(info.start.point, info.end.point, labelToArrowPadding);
const endOffset = import_editor.Vec.Nudge(info.end.point, info.start.point, labelToArrowPadding);
const intersectionPoints = (0, import_editor.intersectLineSegmentPolygon)(
startOffset,
endOffset,
import_editor.Box.FromCenter(info.middle, labelSize).corners
);
if (!intersectionPoints || intersectionPoints.length !== 2) {
return { start: 0.5, end: 0.5 };
}
let [startIntersect, endIntersect] = intersectionPoints;
if (import_editor.Vec.Dist2(startIntersect, startOffset) > import_editor.Vec.Dist2(endIntersect, startOffset)) {
;
[endIntersect, startIntersect] = intersectionPoints;
}
const startConstrained = startOffset.add(import_editor.Vec.Sub(info.middle, startIntersect));
const endConstrained = endOffset.add(import_editor.Vec.Sub(info.middle, endIntersect));
const start = import_editor.Vec.Dist(info.start.point, startConstrained) / info.length;
const end = import_editor.Vec.Dist(info.start.point, endConstrained) / info.length;
return { start, end };
}
function getCurvedArrowLabelRange(editor, shape, info) {
const labelSize = getArrowLabelSize(editor, shape);
const labelToArrowPadding = getLabelToArrowPadding(shape);
const direction = Math.sign(shape.props.bend);
const labelToArrowPaddingRad = labelToArrowPadding / info.handleArc.radius * direction;
const startOffsetAngle = import_editor.Vec.Angle(info.bodyArc.center, info.start.point) - labelToArrowPaddingRad;
const endOffsetAngle = import_editor.Vec.Angle(info.bodyArc.center, info.end.point) + labelToArrowPaddingRad;
const startOffset = (0, import_editor.getPointOnCircle)(info.bodyArc.center, info.bodyArc.radius, startOffsetAngle);
const endOffset = (0, import_editor.getPointOnCircle)(info.bodyArc.center, info.bodyArc.radius, endOffsetAngle);
const dbg = [];
const startIntersections = intersectArcPolygon(
info.bodyArc.center,
info.bodyArc.radius,
startOffsetAngle,
endOffsetAngle,
direction,
import_editor.Box.FromCenter(startOffset, labelSize).corners
);
dbg.push(
new import_editor.Polygon2d({
points: import_editor.Box.FromCenter(startOffset, labelSize).corners,
debugColor: "lime",
isFilled: false,
ignore: true
})
);
const endIntersections = intersectArcPolygon(
info.bodyArc.center,
info.bodyArc.radius,
startOffsetAngle,
endOffsetAngle,
direction,
import_editor.Box.FromCenter(endOffset, labelSize).corners
);
dbg.push(
new import_editor.Polygon2d({
points: import_editor.Box.FromCenter(endOffset, labelSize).corners,
debugColor: "lime",
isFilled: false,
ignore: true
})
);
for (const pt of [
...startIntersections ?? [],
...endIntersections ?? [],
startOffset,
endOffset
]) {
dbg.push(
new import_editor.Circle2d({
x: pt.x - 3,
y: pt.y - 3,
radius: 3,
isFilled: false,
debugColor: "magenta",
ignore: true
})
);
}
const startConstrained = (startIntersections && furthest(info.start.point, startIntersections)) ?? info.middle;
const endConstrained = (endIntersections && furthest(info.end.point, endIntersections)) ?? info.middle;
const startAngle = import_editor.Vec.Angle(info.bodyArc.center, info.start.point);
const endAngle = import_editor.Vec.Angle(info.bodyArc.center, info.end.point);
const constrainedStartAngle = import_editor.Vec.Angle(info.bodyArc.center, startConstrained);
const constrainedEndAngle = import_editor.Vec.Angle(info.bodyArc.center, endConstrained);
if ((0, import_editor.angleDistance)(startAngle, constrainedStartAngle, direction) > (0, import_editor.angleDistance)(startAngle, constrainedEndAngle, direction)) {
return { start: 0.5, end: 0.5, dbg };
}
const fullDistance = (0, import_editor.angleDistance)(startAngle, endAngle, direction);
const start = (0, import_editor.angleDistance)(startAngle, constrainedStartAngle, direction) / fullDistance;
const end = (0, import_editor.angleDistance)(startAngle, constrainedEndAngle, direction) / fullDistance;
return { start, end, dbg };
}
function getArrowLabelPosition(editor, shape) {
let labelCenter;
const debugGeom = [];
const info = (0, import_shared.getArrowInfo)(editor, shape);
const arrowheadInfo = {
hasStartBinding: !!info.bindings.start,
hasEndBinding: !!info.bindings.end,
hasStartArrowhead: info.start.arrowhead !== "none",
hasEndArrowhead: info.end.arrowhead !== "none"
};
if (info.isStraight) {
const range = getStraightArrowLabelRange(editor, shape, info);
const clampedPosition = getClampedPosition(editor, shape, range, arrowheadInfo);
labelCenter = import_editor.Vec.Lrp(info.start.point, info.end.point, clampedPosition);
} else {
const range = getCurvedArrowLabelRange(editor, shape, info);
if (range.dbg) debugGeom.push(...range.dbg);
const clampedPosition = getClampedPosition(editor, shape, range, arrowheadInfo);
const labelAngle = interpolateArcAngles(
import_editor.Vec.Angle(info.bodyArc.center, info.start.point),
import_editor.Vec.Angle(info.bodyArc.center, info.end.point),
Math.sign(shape.props.bend),
clampedPosition
);
labelCenter = (0, import_editor.getPointOnCircle)(info.bodyArc.center, info.bodyArc.radius, labelAngle);
}
const labelSize = getArrowLabelSize(editor, shape);
return { box: import_editor.Box.FromCenter(labelCenter, labelSize), debugGeom };
}
function getClampedPosition(editor, shape, range, arrowheadInfo) {
const { hasEndArrowhead, hasEndBinding, hasStartBinding, hasStartArrowhead } = arrowheadInfo;
const arrowLength = (0, import_ArrowShapeUtil.getArrowLength)(editor, shape);
let clampedPosition = (0, import_editor.clamp)(
shape.props.labelPosition,
hasStartArrowhead || hasStartBinding ? range.start : 0,
hasEndArrowhead || hasEndBinding ? range.end : 1
);
const snapDistance = Math.min(0.02, 500 / arrowLength * 0.02);
clampedPosition = clampedPosition >= 0.5 - snapDistance && clampedPosition <= 0.5 + snapDistance ? 0.5 : clampedPosition;
return clampedPosition;
}
function intersectArcPolygon(center, radius, angleStart, angleEnd, direction, polygon) {
const intersections = (0, import_editor.intersectCirclePolygon)(center, radius, polygon);
const fullArcDistance = (0, import_editor.angleDistance)(angleStart, angleEnd, direction);
return intersections?.filter((pt) => {
const pDistance = (0, import_editor.angleDistance)(angleStart, import_editor.Vec.Angle(center, pt), direction);
return pDistance >= 0 && pDistance <= fullArcDistance;
});
}
function furthest(from, candidates) {
let furthest2 = null;
let furthestDist = -Infinity;
for (const candidate of candidates) {
const dist = import_editor.Vec.Dist2(from, candidate);
if (dist > furthestDist) {
furthest2 = candidate;
furthestDist = dist;
}
}
return furthest2;
}
function interpolateArcAngles(angleStart, angleEnd, direction, t) {
const dist = (0, import_editor.angleDistance)(angleStart, angleEnd, direction);
return angleStart + dist * t * direction * -1;
}
function getArrowLabelFontSize(shape) {
return import_default_shape_constants.ARROW_LABEL_FONT_SIZES[shape.props.size] * shape.props.scale;
}
//# sourceMappingURL=arrowLabel.js.map