1 | import { l as log, M as decodeEntities } from "./mermaid-6dc72991.js";
|
2 | import { fromMarkdown } from "mdast-util-from-markdown";
|
3 | import { dedent } from "ts-dedent";
|
4 | function preprocessMarkdown(markdown) {
|
5 | const withoutMultipleNewlines = markdown.replace(/\n{2,}/g, "\n");
|
6 | const withoutExtraSpaces = dedent(withoutMultipleNewlines);
|
7 | return withoutExtraSpaces;
|
8 | }
|
9 | function markdownToLines(markdown) {
|
10 | const preprocessedMarkdown = preprocessMarkdown(markdown);
|
11 | const { children } = fromMarkdown(preprocessedMarkdown);
|
12 | const lines = [[]];
|
13 | let currentLine = 0;
|
14 | function processNode(node, parentType = "normal") {
|
15 | if (node.type === "text") {
|
16 | const textLines = node.value.split("\n");
|
17 | textLines.forEach((textLine, index) => {
|
18 | if (index !== 0) {
|
19 | currentLine++;
|
20 | lines.push([]);
|
21 | }
|
22 | textLine.split(" ").forEach((word) => {
|
23 | if (word) {
|
24 | lines[currentLine].push({ content: word, type: parentType });
|
25 | }
|
26 | });
|
27 | });
|
28 | } else if (node.type === "strong" || node.type === "emphasis") {
|
29 | node.children.forEach((contentNode) => {
|
30 | processNode(contentNode, node.type);
|
31 | });
|
32 | }
|
33 | }
|
34 | children.forEach((treeNode) => {
|
35 | if (treeNode.type === "paragraph") {
|
36 | treeNode.children.forEach((contentNode) => {
|
37 | processNode(contentNode);
|
38 | });
|
39 | }
|
40 | });
|
41 | return lines;
|
42 | }
|
43 | function markdownToHTML(markdown) {
|
44 | const { children } = fromMarkdown(markdown);
|
45 | function output(node) {
|
46 | if (node.type === "text") {
|
47 | return node.value.replace(/\n/g, "<br/>");
|
48 | } else if (node.type === "strong") {
|
49 | return `<strong>${node.children.map(output).join("")}</strong>`;
|
50 | } else if (node.type === "emphasis") {
|
51 | return `<em>${node.children.map(output).join("")}</em>`;
|
52 | } else if (node.type === "paragraph") {
|
53 | return `<p>${node.children.map(output).join("")}</p>`;
|
54 | }
|
55 | return `Unsupported markdown: ${node.type}`;
|
56 | }
|
57 | return children.map(output).join("");
|
58 | }
|
59 | function splitTextToChars(text) {
|
60 | if (Intl.Segmenter) {
|
61 | return [...new Intl.Segmenter().segment(text)].map((s) => s.segment);
|
62 | }
|
63 | return [...text];
|
64 | }
|
65 | function splitWordToFitWidth(checkFit, word) {
|
66 | const characters = splitTextToChars(word.content);
|
67 | return splitWordToFitWidthRecursion(checkFit, [], characters, word.type);
|
68 | }
|
69 | function splitWordToFitWidthRecursion(checkFit, usedChars, remainingChars, type) {
|
70 | if (remainingChars.length === 0) {
|
71 | return [
|
72 | { content: usedChars.join(""), type },
|
73 | { content: "", type }
|
74 | ];
|
75 | }
|
76 | const [nextChar, ...rest] = remainingChars;
|
77 | const newWord = [...usedChars, nextChar];
|
78 | if (checkFit([{ content: newWord.join(""), type }])) {
|
79 | return splitWordToFitWidthRecursion(checkFit, newWord, rest, type);
|
80 | }
|
81 | if (usedChars.length === 0 && nextChar) {
|
82 | usedChars.push(nextChar);
|
83 | remainingChars.shift();
|
84 | }
|
85 | return [
|
86 | { content: usedChars.join(""), type },
|
87 | { content: remainingChars.join(""), type }
|
88 | ];
|
89 | }
|
90 | function splitLineToFitWidth(line, checkFit) {
|
91 | if (line.some(({ content }) => content.includes("\n"))) {
|
92 | throw new Error("splitLineToFitWidth does not support newlines in the line");
|
93 | }
|
94 | return splitLineToFitWidthRecursion(line, checkFit);
|
95 | }
|
96 | function splitLineToFitWidthRecursion(words, checkFit, lines = [], newLine = []) {
|
97 | if (words.length === 0) {
|
98 | if (newLine.length > 0) {
|
99 | lines.push(newLine);
|
100 | }
|
101 | return lines.length > 0 ? lines : [];
|
102 | }
|
103 | let joiner = "";
|
104 | if (words[0].content === " ") {
|
105 | joiner = " ";
|
106 | words.shift();
|
107 | }
|
108 | const nextWord = words.shift() ?? { content: " ", type: "normal" };
|
109 | const lineWithNextWord = [...newLine];
|
110 | if (joiner !== "") {
|
111 | lineWithNextWord.push({ content: joiner, type: "normal" });
|
112 | }
|
113 | lineWithNextWord.push(nextWord);
|
114 | if (checkFit(lineWithNextWord)) {
|
115 | return splitLineToFitWidthRecursion(words, checkFit, lines, lineWithNextWord);
|
116 | }
|
117 | if (newLine.length > 0) {
|
118 | lines.push(newLine);
|
119 | words.unshift(nextWord);
|
120 | } else if (nextWord.content) {
|
121 | const [line, rest] = splitWordToFitWidth(checkFit, nextWord);
|
122 | lines.push([line]);
|
123 | if (rest.content) {
|
124 | words.unshift(rest);
|
125 | }
|
126 | }
|
127 | return splitLineToFitWidthRecursion(words, checkFit, lines);
|
128 | }
|
129 | function applyStyle(dom, styleFn) {
|
130 | if (styleFn) {
|
131 | dom.attr("style", styleFn);
|
132 | }
|
133 | }
|
134 | function addHtmlSpan(element, node, width, classes, addBackground = false) {
|
135 | const fo = element.append("foreignObject");
|
136 | const div = fo.append("xhtml:div");
|
137 | const label = node.label;
|
138 | const labelClass = node.isNode ? "nodeLabel" : "edgeLabel";
|
139 | div.html(
|
140 | `
|
141 | <span class="${labelClass} ${classes}" ` + (node.labelStyle ? 'style="' + node.labelStyle + '"' : "") + ">" + label + "</span>"
|
142 | );
|
143 | applyStyle(div, node.labelStyle);
|
144 | div.style("display", "table-cell");
|
145 | div.style("white-space", "nowrap");
|
146 | div.style("max-width", width + "px");
|
147 | div.attr("xmlns", "http://www.w3.org/1999/xhtml");
|
148 | if (addBackground) {
|
149 | div.attr("class", "labelBkg");
|
150 | }
|
151 | let bbox = div.node().getBoundingClientRect();
|
152 | if (bbox.width === width) {
|
153 | div.style("display", "table");
|
154 | div.style("white-space", "break-spaces");
|
155 | div.style("width", width + "px");
|
156 | bbox = div.node().getBoundingClientRect();
|
157 | }
|
158 | fo.style("width", bbox.width);
|
159 | fo.style("height", bbox.height);
|
160 | return fo.node();
|
161 | }
|
162 | function createTspan(textElement, lineIndex, lineHeight) {
|
163 | return textElement.append("tspan").attr("class", "text-outer-tspan").attr("x", 0).attr("y", lineIndex * lineHeight - 0.1 + "em").attr("dy", lineHeight + "em");
|
164 | }
|
165 | function computeWidthOfText(parentNode, lineHeight, line) {
|
166 | const testElement = parentNode.append("text");
|
167 | const testSpan = createTspan(testElement, 1, lineHeight);
|
168 | updateTextContentAndStyles(testSpan, line);
|
169 | const textLength = testSpan.node().getComputedTextLength();
|
170 | testElement.remove();
|
171 | return textLength;
|
172 | }
|
173 | function computeDimensionOfText(parentNode, lineHeight, text) {
|
174 | var _a;
|
175 | const testElement = parentNode.append("text");
|
176 | const testSpan = createTspan(testElement, 1, lineHeight);
|
177 | updateTextContentAndStyles(testSpan, [{ content: text, type: "normal" }]);
|
178 | const textDimension = (_a = testSpan.node()) == null ? void 0 : _a.getBoundingClientRect();
|
179 | if (textDimension) {
|
180 | testElement.remove();
|
181 | }
|
182 | return textDimension;
|
183 | }
|
184 | function createFormattedText(width, g, structuredText, addBackground = false) {
|
185 | const lineHeight = 1.1;
|
186 | const labelGroup = g.append("g");
|
187 | const bkg = labelGroup.insert("rect").attr("class", "background");
|
188 | const textElement = labelGroup.append("text").attr("y", "-10.1");
|
189 | let lineIndex = 0;
|
190 | for (const line of structuredText) {
|
191 | const checkWidth = (line2) => computeWidthOfText(labelGroup, lineHeight, line2) <= width;
|
192 | const linesUnderWidth = checkWidth(line) ? [line] : splitLineToFitWidth(line, checkWidth);
|
193 | for (const preparedLine of linesUnderWidth) {
|
194 | const tspan = createTspan(textElement, lineIndex, lineHeight);
|
195 | updateTextContentAndStyles(tspan, preparedLine);
|
196 | lineIndex++;
|
197 | }
|
198 | }
|
199 | if (addBackground) {
|
200 | const bbox = textElement.node().getBBox();
|
201 | const padding = 2;
|
202 | bkg.attr("x", -padding).attr("y", -padding).attr("width", bbox.width + 2 * padding).attr("height", bbox.height + 2 * padding);
|
203 | return labelGroup.node();
|
204 | } else {
|
205 | return textElement.node();
|
206 | }
|
207 | }
|
208 | function updateTextContentAndStyles(tspan, wrappedLine) {
|
209 | tspan.text("");
|
210 | wrappedLine.forEach((word, index) => {
|
211 | const innerTspan = tspan.append("tspan").attr("font-style", word.type === "emphasis" ? "italic" : "normal").attr("class", "text-inner-tspan").attr("font-weight", word.type === "strong" ? "bold" : "normal");
|
212 | if (index === 0) {
|
213 | innerTspan.text(word.content);
|
214 | } else {
|
215 | innerTspan.text(" " + word.content);
|
216 | }
|
217 | });
|
218 | }
|
219 | const createText = (el, text = "", {
|
220 | style = "",
|
221 | isTitle = false,
|
222 | classes = "",
|
223 | useHtmlLabels = true,
|
224 | isNode = true,
|
225 | width = 200,
|
226 | addSvgBackground = false
|
227 | } = {}) => {
|
228 | log.info("createText", text, style, isTitle, classes, useHtmlLabels, isNode, addSvgBackground);
|
229 | if (useHtmlLabels) {
|
230 | const htmlText = markdownToHTML(text);
|
231 | const node = {
|
232 | isNode,
|
233 | label: decodeEntities(htmlText).replace(
|
234 | /fa[blrs]?:fa-[\w-]+/g,
|
235 |
|
236 | (s) => `<i class='${s.replace(":", " ")}'></i>`
|
237 | ),
|
238 | labelStyle: style.replace("fill:", "color:")
|
239 | };
|
240 | const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);
|
241 | return vertexNode;
|
242 | } else {
|
243 | const structuredText = markdownToLines(text);
|
244 | const svgLabel = createFormattedText(width, el, structuredText, addSvgBackground);
|
245 | return svgLabel;
|
246 | }
|
247 | };
|
248 | export {
|
249 | createText as a,
|
250 | computeDimensionOfText as c
|
251 | };
|