tldraw
Version:
A tiny little drawing editor.
243 lines (242 loc) • 10.2 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, {
getArrowBodyGeometry: () => getArrowBodyGeometry,
getArrowLabelDefaultPosition: () => getArrowLabelDefaultPosition,
getArrowLabelFontSize: () => getArrowLabelFontSize,
getArrowLabelPosition: () => getArrowLabelPosition,
isOverArrowLabel: () => isOverArrowLabel
});
module.exports = __toCommonJS(arrowLabel_exports);
var import_editor = require("@tldraw/editor");
var import_richText = require("../../utils/text/richText");
var import_default_shape_constants = require("../shared/default-shape-constants");
var import_getArrowInfo = require("./getArrowInfo");
function getArrowBodyGeometry(editor, shape) {
const info = (0, import_getArrowInfo.getArrowInfo)(editor, shape);
switch (info.type) {
case "straight":
return new import_editor.Edge2d({
start: import_editor.Vec.From(info.start.point),
end: import_editor.Vec.From(info.end.point)
});
case "arc":
return 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
});
case "elbow":
return new import_editor.Polyline2d({ points: info.route.points });
default:
(0, import_editor.exhaustiveSwitchError)(info, "type");
}
}
const labelSizeCache = (0, import_editor.createComputedCache)(
"arrow label size",
(editor, shape) => {
editor.fonts.trackFontsForShape(shape);
let width = 0;
let height = 0;
const bodyGeom = getArrowBodyGeometry(editor, shape);
const isEmpty = (0, import_richText.isEmptyRichText)(shape.props.richText);
const html = (0, import_richText.renderHtmlFromRichTextForMeasurement)(
editor,
isEmpty ? (0, import_editor.toRichText)("i") : shape.props.richText
);
const bodyBounds = bodyGeom.bounds;
const fontSize = getArrowLabelFontSize(shape);
const { w, h } = editor.textMeasure.measureHtml(html, {
...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;
const info = (0, import_getArrowInfo.getArrowInfo)(editor, shape);
const labelToArrowPadding = getLabelToArrowPadding(shape);
const margin = info.type === "elbow" ? Math.max(info.elbow.A.arrowheadOffset + labelToArrowPadding, 32) + Math.max(info.elbow.B.arrowheadOffset + labelToArrowPadding, 32) : 64;
if (bodyBounds.width > bodyBounds.height) {
width = Math.max(Math.min(w, margin), Math.min(bodyBounds.width - margin, w));
shouldSquish = true;
} else if (width > 16 * fontSize) {
width = 16 * fontSize;
shouldSquish = true;
}
if (shouldSquish) {
const { w: squishedWidth, h: squishedHeight } = editor.textMeasure.measureHtml(html, {
...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);
},
{
areRecordsEqual: (a, b) => {
if (a.props === b.props) return true;
const changedKeys = (0, import_editor.getChangedKeys)(a.props, b.props);
return changedKeys.length === 1 && changedKeys[0] === "labelPosition";
}
}
);
function getArrowLabelSize(editor, shape) {
return labelSizeCache.get(editor, 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 getArrowLabelRange(editor, shape, info) {
const bodyGeom = getArrowBodyGeometry(editor, shape);
const dbgPoints = [];
const dbg = [new import_editor.Group2d({ children: [bodyGeom], debugColor: "lime" })];
const labelSize = getArrowLabelSize(editor, shape);
const labelToArrowPadding = getLabelToArrowPadding(shape);
const paddingRelative = labelToArrowPadding / bodyGeom.length;
let startBox, endBox;
if (info.type === "elbow") {
dbgPoints.push(info.start.point, info.end.point);
startBox = import_editor.Box.FromCenter(info.start.point, labelSize).expandBy(labelToArrowPadding);
endBox = import_editor.Box.FromCenter(info.end.point, labelSize).expandBy(labelToArrowPadding);
} else {
const startPoint = bodyGeom.interpolateAlongEdge(paddingRelative);
const endPoint = bodyGeom.interpolateAlongEdge(1 - paddingRelative);
dbgPoints.push(startPoint, endPoint);
startBox = import_editor.Box.FromCenter(startPoint, labelSize);
endBox = import_editor.Box.FromCenter(endPoint, labelSize);
}
const startIntersections = bodyGeom.intersectPolygon(startBox.corners);
const endIntersections = bodyGeom.intersectPolygon(endBox.corners);
const startConstrained = furthest(info.start.point, startIntersections);
const endConstrained = furthest(info.end.point, endIntersections);
let startRelative = startConstrained ? bodyGeom.uninterpolateAlongEdge(startConstrained) : 0.5;
let endRelative = endConstrained ? bodyGeom.uninterpolateAlongEdge(endConstrained) : 0.5;
if (startRelative > endRelative) {
startRelative = 0.5;
endRelative = 0.5;
}
for (const pt of [...startIntersections, ...endIntersections, ...dbgPoints]) {
dbg.push(
new import_editor.Circle2d({
x: pt.x - 3,
y: pt.y - 3,
radius: 3,
isFilled: false,
debugColor: "magenta",
ignore: true
})
);
}
dbg.push(
new import_editor.Polygon2d({
points: startBox.corners,
debugColor: "lime",
isFilled: false,
ignore: true
}),
new import_editor.Polygon2d({
points: endBox.corners,
debugColor: "lime",
isFilled: false,
ignore: true
})
);
return { start: startRelative, end: endRelative, dbg };
}
function getArrowLabelPosition(editor, shape, isEditing) {
if (!isEditing && (0, import_richText.isEmptyRichText)(shape.props.richText)) {
const bodyGeom2 = getArrowBodyGeometry(editor, shape);
const labelCenter2 = bodyGeom2.interpolateAlongEdge(0.5);
return { box: import_editor.Box.FromCenter(labelCenter2, new import_editor.Vec(0, 0)), debugGeom: [] };
}
const debugGeom = [];
const info = (0, import_getArrowInfo.getArrowInfo)(editor, shape);
const arrowheadInfo = {
hasStartBinding: !!info.bindings.start,
hasEndBinding: !!info.bindings.end,
hasStartArrowhead: info.start.arrowhead !== "none",
hasEndArrowhead: info.end.arrowhead !== "none"
};
const range = getArrowLabelRange(editor, shape, info);
if (range.dbg) debugGeom.push(...range.dbg);
const clampedPosition = getClampedPosition(shape, range, arrowheadInfo);
const bodyGeom = getArrowBodyGeometry(editor, shape);
const labelCenter = bodyGeom.interpolateAlongEdge(clampedPosition);
const labelSize = getArrowLabelSize(editor, shape);
return { box: import_editor.Box.FromCenter(labelCenter, labelSize), debugGeom };
}
function getClampedPosition(shape, range, arrowheadInfo) {
const { hasEndArrowhead, hasEndBinding, hasStartBinding, hasStartArrowhead } = arrowheadInfo;
const clampedPosition = (0, import_editor.clamp)(
shape.props.labelPosition,
hasStartArrowhead || hasStartBinding ? range.start : 0,
hasEndArrowhead || hasEndBinding ? range.end : 1
);
return clampedPosition;
}
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 getArrowLabelFontSize(shape) {
return import_default_shape_constants.ARROW_LABEL_FONT_SIZES[shape.props.size] * shape.props.scale;
}
function getArrowLabelDefaultPosition(editor, shape) {
const info = (0, import_getArrowInfo.getArrowInfo)(editor, shape);
switch (info.type) {
case "straight":
case "arc":
return 0.5;
case "elbow": {
const midpointHandle = info.route.midpointHandle;
const bodyGeom = getArrowBodyGeometry(editor, shape);
if (midpointHandle && bodyGeom) {
return bodyGeom.uninterpolateAlongEdge(midpointHandle.point);
}
return 0.5;
}
default:
(0, import_editor.exhaustiveSwitchError)(info, "type");
}
}
function isOverArrowLabel(editor, shape) {
if (!editor.isShapeOfType(shape, "arrow")) return false;
const pointInShapeSpace = editor.getPointInShapeSpace(shape, editor.inputs.getCurrentPagePoint());
const labelGeometry = editor.getShapeGeometry(shape).children[1];
return labelGeometry && (0, import_editor.pointInPolygon)(pointInShapeSpace, labelGeometry.vertices);
}
//# sourceMappingURL=arrowLabel.js.map