UNPKG

8.78 kBJavaScriptView Raw
1import { l as log, M as decodeEntities } from "./mermaid-6dc72991.js";
2import { fromMarkdown } from "mdast-util-from-markdown";
3import { dedent } from "ts-dedent";
4function preprocessMarkdown(markdown) {
5 const withoutMultipleNewlines = markdown.replace(/\n{2,}/g, "\n");
6 const withoutExtraSpaces = dedent(withoutMultipleNewlines);
7 return withoutExtraSpaces;
8}
9function 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}
43function 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}
59function splitTextToChars(text) {
60 if (Intl.Segmenter) {
61 return [...new Intl.Segmenter().segment(text)].map((s) => s.segment);
62 }
63 return [...text];
64}
65function splitWordToFitWidth(checkFit, word) {
66 const characters = splitTextToChars(word.content);
67 return splitWordToFitWidthRecursion(checkFit, [], characters, word.type);
68}
69function 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}
90function 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}
96function 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}
129function applyStyle(dom, styleFn) {
130 if (styleFn) {
131 dom.attr("style", styleFn);
132 }
133}
134function 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}
162function 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}
165function 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}
173function 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}
184function 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}
208function 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}
219const 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 // cspell: disable-line
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};
248export {
249 createText as a,
250 computeDimensionOfText as c
251};