UNPKG

tldraw

Version:

A tiny little drawing editor.

243 lines (242 loc) • 10.2 kB
"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