Works out what a multileader draws: its line with the landing, the arrowhead and where the text starts.
How the precision is kept
The landing and text position are the last leader point plus the stored sizes, and the arrowhead is built from the unit direction of the first segment. All in 64-bit numbers with nothing rounded.
Calculations under this policy
2 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
end = last + dir·(landing length, 0); text = end + dir·(gap, 0)
Two additions of stored sizes to the last leader point; dir is +1 or −1.
leader.js
lines 20 to 25
CadCraft 0.8.2
/** Where the landing ends and the text starts. */
export function landing(e) {
const st = leaderStyle(e), last = e.pts[e.pts.length - 1], dir = e.dir === -1 ? -1 : 1;
const end = { x: last.x + dir * st.dogleg, y: last.y };
return { start: last, end, text: { x: end.x + dir * st.gap, y: end.y } };
}
Leader line, arrowhead and text
64-bit
The polyline, the solid arrowhead and the text a multileader is drawn and exploded as.
u = (tip − next) ÷ |tip − next|; base = tip − u·a; corners = base ± (−u_y, u_x)·a ÷ 6; text top = landing y + h ÷ 2
The arrow direction is the first segment divided by its Math.hypot length. The leader's own points are copied unchanged into the polyline.
leader.js
lines 27 to 46
CadCraft 0.8.2
export function leaderGeometry(e) {
if (!e.pts || e.pts.length < 2) return [];
const st = leaderStyle(e), dir = e.dir === -1 ? -1 : 1;
const L = landing(e);
const base = { layer: '0', color: 0 };
const out = [{ ...base, type: 'poly', closed: false, pts: [...e.pts, ...(st.dogleg > 0 ? [L.end] : [])].map((p) => ({ x: p.x, y: p.y, b: 0 })) }];
// arrowhead: a solid triangle at the tip, pointing back along the first segment
const tip = e.pts[0], nxt = e.pts[1], dx = tip.x - nxt.x, dy = tip.y - nxt.y, len = Math.hypot(dx, dy);
if (len > 1e-12 && st.asz > 0) {
const ux = dx / len, uy = dy / len, a = st.asz, w = a / 6; // AutoCAD's closed filled arrow: length a, half width a/6
const bx = tip.x - ux * a, by = tip.y - uy * a;
out.push({ ...base, type: 'hatch', solid: true, loops: [[tip.x, tip.y, bx - uy * w, by + ux * w, bx + uy * w, by - ux * w]] });
}
if (String(e.s ?? '').trim()) {
const h = e.h > 0 ? e.h : st.asz;
// the first line's middle on the landing: hang the text from its top, half a capital height above
out.push({ ...base, type: 'text', x: L.text.x, y: L.text.y + h / 2, h, rot: 0, s: String(e.s), ha: dir === 1 ? 'left' : 'right', va: 'top', wf: 1, ...(e.ww > 0 ? { ww: e.ww } : {}), ...(e.ls && e.ls !== 1 ? { ls: e.ls } : {}) });
}
return out;
}
In detail
Method
Landing end = last point ± landing length along x; arrowhead a triangle of length a and half width a ÷ 6.
Number types
64-bit numbers.
Tolerances
A first segment of 1e-12 or shorter gets no arrowhead.
Approach: 64-bit, direct
Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.