Test/node_modules/@excalidraw/mermaid-to-excalidraw/dist/utils.js
2026-04-09 22:54:00 +07:00

121 lines
5 KiB
JavaScript

// Convert mermaid entity codes to text e.g. "#9829;" to "♥"
export const entityCodesToText = (input) => {
input = decodeEntities(input);
// Append & before the pattern #(\d+); or #([a-z]+); to convert to decimal code
// so it can be rendered as html character
// eg #9829; => ♥
const inputWithDecimalCode = input
.replace(/#(\d+);/g, "&#$1;")
.replace(/#([a-z]+);/g, "&$1;");
// Render the decimal code as html character, eg ♥ => ♥
const element = document.createElement("textarea");
element.innerHTML = inputWithDecimalCode;
return element.value;
};
export const getTransformAttr = (el) => {
const transformAttr = el.getAttribute("transform");
const translateMatch = transformAttr?.match(/translate\(([ \d.-]+),\s*([\d.-]+)\)/);
let transformX = 0;
let transformY = 0;
if (translateMatch) {
transformX = Number(translateMatch[1]);
transformY = Number(translateMatch[2]);
}
return { transformX, transformY };
};
//TODO Once fixed in mermaid this will be removed
export const encodeEntities = (text) => {
let txt = text;
txt = txt.replace(/style.*:\S*#.*;/g, (s) => {
return s.substring(0, s.length - 1);
});
txt = txt.replace(/classDef.*:\S*#.*;/g, (s) => {
return s.substring(0, s.length - 1);
});
txt = txt.replace(/#\w+;/g, (s) => {
const innerTxt = s.substring(1, s.length - 1);
const isInt = /^\+?\d+$/.test(innerTxt);
if (isInt) {
return `fl°°${innerTxt}¶ß`;
}
return `fl°${innerTxt}¶ß`;
});
return txt;
};
export const decodeEntities = function (text) {
return text.replace(/fl°°/g, "#").replace(/fl°/g, "&").replace(/¶ß/g, ";");
};
// Compute edge postion start, end and points (reflection points)
export const computeEdgePositions = (pathElement, offset = { x: 0, y: 0 }) => {
// Check if the element is a path else throw an error
if (pathElement.tagName.toLowerCase() !== "path") {
throw new Error(`Invalid input: Expected an HTMLElement of tag "path", got ${pathElement.tagName}`);
}
// Get the d attribute from the path element else throw an error
const dAttr = pathElement.getAttribute("d");
if (!dAttr) {
throw new Error('Path element does not contain a "d" attribute');
}
// Split the d attribute based on M (Move To) and L (Line To) commands
// eg "M29.383,38.5L29.383,63.5L29.383,83.2" => ["M29.383,38.5", "L29.383,63.5", "L29.383,83.2"]
const commands = dAttr.split(/(?=[LM])/);
// Get the start position from the first commands element => [29.383,38.5]
const startPosition = commands[0]
.substring(1)
.split(",")
.map((coord) => parseFloat(coord));
// Get the last position from the last commands element => [29.383,83.2]
const endPosition = commands[commands.length - 1]
.substring(1)
.split(",")
.map((coord) => parseFloat(coord));
// compute the reflection points -> [ {x: 29.383, y: 38.5}, {x: 29.383, y: 83.2} ]
// These includes the start and end points and also points which are not the same as the previous points
const reflectionPoints = commands
.map((command) => {
const coords = command
.substring(1)
.split(",")
.map((coord) => parseFloat(coord));
return { x: coords[0], y: coords[1] };
})
.filter((point, index, array) => {
// Always include the last point
if (index === 0 || index === array.length - 1) {
return true;
}
// Exclude the points which are the same as the previous point
if (point.x === array[index - 1].x && point.y === array[index - 1].y) {
return false;
}
// The below check is exclusively for second last point
if (index === array.length - 2 &&
(array[index - 1].x === point.x || array[index - 1].y === point.y)) {
const lastPoint = array[array.length - 1];
// Get the distance between the last point and second last point using Euclidean distance formula
const distance = Math.hypot(lastPoint.x - point.x, lastPoint.y - point.y);
// Include the second last point if the distance between the
// last point and second last point is > 20.
// This is to ensure we have a distance for render the edge.
// 20 seems to be a good enough distance to render the edge
return distance > 20;
}
// Always include if the current point is not the same as the previous point
return point.x !== array[index - 1].x || point.y !== array[index - 1].y;
})
.map((p) => {
// Offset the point by the provided offset
return {
x: p.x + offset.x,
y: p.y + offset.y,
};
});
// Return the edge positions
return {
startX: startPosition[0] + offset.x,
startY: startPosition[1] + offset.y,
endX: endPosition[0] + offset.x,
endY: endPosition[1] + offset.y,
reflectionPoints,
};
};