Skip to content

Commit 1c1d7d0

Browse files
authored
Merge pull request #6282 from mermaid-js/saurabh/refactor-fontawesome-icon-usage
Refactor fontawesome icon usage.
2 parents e8ee4bd + 4077088 commit 1c1d7d0

File tree

23 files changed

+228
-59
lines changed

23 files changed

+228
-59
lines changed

.changeset/proud-seahorses-wash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'mermaid': patch
3+
---
4+
5+
FontAwesome icons can now be embedded as SVGs in flowcharts if they are registered via `mermaid.registerIconPacks`.

.cspell/libraries.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ dompurify
2626
elkjs
2727
fcose
2828
fontawesome
29+
Fonticons
2930
Forgejo
3031
Foswiki
3132
Gitea
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { imgSnapshotTest } from '../../helpers/util.ts';
2+
3+
const themes = ['default', 'forest', 'dark', 'base', 'neutral'];
4+
5+
describe('when rendering flowchart with icons', () => {
6+
for (const theme of themes) {
7+
it(`should render icons from fontawesome library on theme ${theme}`, () => {
8+
imgSnapshotTest(
9+
`flowchart TD
10+
A("fab:fa-twitter Twitter") --> B("fab:fa-facebook Facebook")
11+
B --> C("fa:fa-coffee Coffee")
12+
C --> D("fa:fa-car Car")
13+
D --> E("fab:fa-github GitHub")
14+
`,
15+
{ theme }
16+
);
17+
});
18+
19+
it(`should render registered icons on theme ${theme}`, () => {
20+
imgSnapshotTest(
21+
`flowchart TD
22+
A("fa:fa-bell Bell")
23+
`,
24+
{ theme }
25+
);
26+
});
27+
}
28+
});

cypress/integration/rendering/flowchart-v2.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
9999
const style = svg.attr('style');
100100
expect(style).to.match(/^max-width: [\d.]+px;$/);
101101
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
102-
expect(maxWidthValue).to.be.within(417 * 0.95, 417 * 1.05);
102+
expect(maxWidthValue).to.be.within(440 * 0.95, 440 * 1.05);
103103
});
104104
});
105105
it('8: should render a flowchart when useMaxWidth is false', () => {
@@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
118118
const width = parseFloat(svg.attr('width'));
119119
// use within because the absolute value can be slightly different depending on the environment ±5%
120120
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
121-
expect(width).to.be.within(417 * 0.95, 417 * 1.05);
121+
expect(width).to.be.within(440 * 0.95, 440 * 1.05);
122122
expect(svg).to.not.have.attr('style');
123123
});
124124
});

cypress/platform/e2e.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
rel="stylesheet"
88
/>
99
<style>
10-
svg {
10+
svg:not(svg svg) {
1111
border: 2px solid darkred;
1212
}
1313
.exClass2 > rect,

cypress/platform/viewer.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,28 @@ function markRendered() {
1414
}
1515
}
1616

17+
function loadFontAwesomeCSS() {
18+
const link = document.createElement('link');
19+
link.rel = 'stylesheet';
20+
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css';
21+
22+
document.head.appendChild(link);
23+
24+
return new Promise((resolve, reject) => {
25+
link.onload = resolve;
26+
link.onerror = () => reject(new Error('Failed to load FontAwesome'));
27+
});
28+
}
29+
1730
/**
1831
* ##contentLoaded Callback function that is called when page is loaded. This functions fetches
1932
* configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the
2033
* page.
2134
*/
2235
const contentLoaded = async function () {
36+
await loadFontAwesomeCSS();
37+
await Promise.all(Array.from(document.fonts, (font) => font.load()));
38+
2339
let pos = document.location.href.indexOf('?graph=');
2440
if (pos > 0) {
2541
pos = pos + 7;
@@ -50,8 +66,13 @@ const contentLoaded = async function () {
5066

5167
mermaid.registerLayoutLoaders(layouts);
5268
mermaid.initialize(graphObj.mermaid);
69+
/**
70+
* CC-BY-4.0
71+
* Copyright (c) Fonticons, Inc. - https://fontawesome.com/license/free
72+
* https://fontawesome.com/icons/bell?f=classic&s=regular
73+
*/
5374
const staticBellIconPack = {
54-
prefix: 'fa6-regular',
75+
prefix: 'fa',
5576
icons: {
5677
bell: {
5778
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',

docs/syntax/flowchart.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,19 @@ flowchart TD
19501950
B-->E(A fa:fa-camera-retro perhaps?)
19511951
```
19521952

1953+
There are two ways to display these FontAwesome icons:
1954+
1955+
### Register FontAwesome icon packs (v\<MERMAID_RELEASE_VERSION>+)
1956+
1957+
You can register your own FontAwesome icon pack following the ["Registering icon packs" instructions](../config/icons.md).
1958+
1959+
Supported prefixes: `fa`, `fab`, `fas`, `far`, `fal`, `fad`.
1960+
1961+
> **Note**
1962+
> Note that it will fall back to FontAwesome CSS if FontAwesome packs are not registered.
1963+
1964+
### Register FontAwesome CSS
1965+
19531966
Mermaid supports Font Awesome if the CSS is included on the website.
19541967
Mermaid does not have any restriction on the version of Font Awesome that can be used.
19551968

packages/mermaid/src/dagre-wrapper/clusters.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getConfig } from '../diagram-api/diagramAPI.js';
77
import { evaluate } from '../diagrams/common/common.js';
88
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
99

10-
const rect = (parent, node) => {
10+
const rect = async (parent, node) => {
1111
log.info('Creating subgraph rect for ', node.id, node);
1212
const siteConfig = getConfig();
1313

@@ -31,7 +31,9 @@ const rect = (parent, node) => {
3131
const text =
3232
node.labelType === 'markdown'
3333
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels }, siteConfig)
34-
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
34+
: label
35+
.node()
36+
.appendChild(await createLabel(node.labelText, node.labelStyle, undefined, true));
3537

3638
// Get the size of the label
3739
let bbox = text.getBBox();
@@ -129,7 +131,7 @@ const noteGroup = (parent, node) => {
129131

130132
return shapeSvg;
131133
};
132-
const roundedWithTitle = (parent, node) => {
134+
const roundedWithTitle = async (parent, node) => {
133135
const siteConfig = getConfig();
134136

135137
// Add outer g element
@@ -144,7 +146,7 @@ const roundedWithTitle = (parent, node) => {
144146

145147
const text = label
146148
.node()
147-
.appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
149+
.appendChild(await createLabel(node.labelText, node.labelStyle, undefined, true));
148150

149151
// Get the size of the label
150152
let bbox = text.getBBox();
@@ -236,13 +238,13 @@ const shapes = { rect, roundedWithTitle, noteGroup, divider };
236238

237239
let clusterElems = {};
238240

239-
export const insertCluster = (elem, node) => {
241+
export const insertCluster = async (elem, node) => {
240242
log.trace('Inserting cluster');
241243
const shape = node.shape || 'rect';
242-
clusterElems[node.id] = shapes[shape](elem, node);
244+
clusterElems[node.id] = await shapes[shape](elem, node);
243245
};
244-
export const getClusterTitleWidth = (elem, node) => {
245-
const label = createLabel(node.labelText, node.labelStyle, undefined, true);
246+
export const getClusterTitleWidth = async (elem, node) => {
247+
const label = await createLabel(node.labelText, node.labelStyle, undefined, true);
246248
elem.node().appendChild(label);
247249
const width = label.getBBox().width;
248250
elem.node().removeChild(label);

packages/mermaid/src/dagre-wrapper/createLabel.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ function addHtmlLabel(node) {
4444
* @param isNode
4545
* @deprecated svg-util/createText instead
4646
*/
47-
const createLabel = (_vertexText, style, isTitle, isNode) => {
47+
const createLabel = async (_vertexText, style, isTitle, isNode) => {
4848
let vertexText = _vertexText || '';
4949
if (typeof vertexText === 'object') {
5050
vertexText = vertexText[0];
@@ -53,9 +53,10 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
5353
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
5454
vertexText = vertexText.replace(/\\n|\n/g, '<br />');
5555
log.debug('vertexText' + vertexText);
56+
const label = await replaceIconSubstring(decodeEntities(vertexText));
5657
const node = {
5758
isNode,
58-
label: replaceIconSubstring(decodeEntities(vertexText)),
59+
label,
5960
labelStyle: style.replace('fill:', 'color:'),
6061
};
6162
let vertexNode = addHtmlLabel(node);

packages/mermaid/src/dagre-wrapper/edges.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const clear = () => {
1717
terminalLabels = {};
1818
};
1919

20-
export const insertEdgeLabel = (elem, edge) => {
20+
export const insertEdgeLabel = async (elem, edge) => {
2121
const config = getConfig();
2222
const useHtmlLabels = evaluate(config.flowchart.htmlLabels);
2323
// Create the actual text element
@@ -33,7 +33,7 @@ export const insertEdgeLabel = (elem, edge) => {
3333
},
3434
config
3535
)
36-
: createLabel(edge.label, edge.labelStyle);
36+
: await createLabel(edge.label, edge.labelStyle);
3737

3838
// Create outer g, edgeLabel, this will be positioned after graph layout
3939
const edgeLabel = elem.insert('g').attr('class', 'edgeLabel');
@@ -63,7 +63,7 @@ export const insertEdgeLabel = (elem, edge) => {
6363
let fo;
6464
if (edge.startLabelLeft) {
6565
// Create the actual text element
66-
const startLabelElement = createLabel(edge.startLabelLeft, edge.labelStyle);
66+
const startLabelElement = await createLabel(edge.startLabelLeft, edge.labelStyle);
6767
const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
6868
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
6969
fo = inner.node().appendChild(startLabelElement);
@@ -77,7 +77,7 @@ export const insertEdgeLabel = (elem, edge) => {
7777
}
7878
if (edge.startLabelRight) {
7979
// Create the actual text element
80-
const startLabelElement = createLabel(edge.startLabelRight, edge.labelStyle);
80+
const startLabelElement = await createLabel(edge.startLabelRight, edge.labelStyle);
8181
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
8282
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
8383
fo = startEdgeLabelRight.node().appendChild(startLabelElement);
@@ -93,7 +93,7 @@ export const insertEdgeLabel = (elem, edge) => {
9393
}
9494
if (edge.endLabelLeft) {
9595
// Create the actual text element
96-
const endLabelElement = createLabel(edge.endLabelLeft, edge.labelStyle);
96+
const endLabelElement = await createLabel(edge.endLabelLeft, edge.labelStyle);
9797
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
9898
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
9999
fo = inner.node().appendChild(endLabelElement);
@@ -110,7 +110,7 @@ export const insertEdgeLabel = (elem, edge) => {
110110
}
111111
if (edge.endLabelRight) {
112112
// Create the actual text element
113-
const endLabelElement = createLabel(edge.endLabelRight, edge.labelStyle);
113+
const endLabelElement = await createLabel(edge.endLabelRight, edge.labelStyle);
114114
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
115115
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
116116

packages/mermaid/src/dagre-wrapper/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
120120
// Move the nodes to the correct place
121121
let diff = 0;
122122
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
123-
sortNodesByHierarchy(graph).forEach(function (v) {
123+
for (const v of sortNodesByHierarchy(graph)) {
124124
const node = graph.node(v);
125125
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
126126
log.info(
@@ -141,14 +141,14 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
141141
// A cluster in the non-recursive way
142142
// positionCluster(node);
143143
node.height += subGraphTitleTotalMargin;
144-
insertCluster(clusters, node);
144+
await insertCluster(clusters, node);
145145
clusterDb[node.id].node = node;
146146
} else {
147147
node.y += subGraphTitleTotalMargin / 2;
148148
positionNode(node);
149149
}
150150
}
151-
});
151+
}
152152

153153
// Move the edge labels to the correct place after layout
154154
graph.edges().forEach(function (e) {

packages/mermaid/src/dagre-wrapper/nodes.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ function applyNodePropertyBorders(rect, borders, totalWidth, totalHeight) {
553553
rect.attr('stroke-dasharray', strokeDashArray.join(' '));
554554
}
555555

556-
const rectWithTitle = (parent, node) => {
556+
const rectWithTitle = async (parent, node) => {
557557
// const { shapeSvg, bbox, halfPadding } = labelHelper(parent, node, 'node ' + node.classes);
558558

559559
let classes;
@@ -586,7 +586,7 @@ const rectWithTitle = (parent, node) => {
586586
}
587587
log.info('Label text abc79', title, text2, typeof text2 === 'object');
588588

589-
const text = label.node().appendChild(createLabel(title, node.labelStyle, true, true));
589+
const text = label.node().appendChild(await createLabel(title, node.labelStyle, true, true));
590590
let bbox = { width: 0, height: 0 };
591591
if (evaluate(getConfig().flowchart.htmlLabels)) {
592592
const div = text.children[0];
@@ -601,7 +601,12 @@ const rectWithTitle = (parent, node) => {
601601
const descr = label
602602
.node()
603603
.appendChild(
604-
createLabel(textRows.join ? textRows.join('<br/>') : textRows, node.labelStyle, true, true)
604+
await createLabel(
605+
textRows.join ? textRows.join('<br/>') : textRows,
606+
node.labelStyle,
607+
true,
608+
true
609+
)
605610
);
606611

607612
if (evaluate(getConfig().flowchart.htmlLabels)) {
@@ -876,7 +881,7 @@ const end = (parent, node) => {
876881
return shapeSvg;
877882
};
878883

879-
const class_box = (parent, node) => {
884+
const class_box = async (parent, node) => {
880885
const halfPadding = node.padding / 2;
881886
const rowPadding = 4;
882887
const lineHeight = 8;
@@ -910,7 +915,7 @@ const class_box = (parent, node) => {
910915
: '';
911916
const interfaceLabel = labelContainer
912917
.node()
913-
.appendChild(createLabel(interfaceLabelText, node.labelStyle, true, true));
918+
.appendChild(await createLabel(interfaceLabelText, node.labelStyle, true, true));
914919
let interfaceBBox = interfaceLabel.getBBox();
915920
if (evaluate(getConfig().flowchart.htmlLabels)) {
916921
const div = interfaceLabel.children[0];
@@ -935,7 +940,7 @@ const class_box = (parent, node) => {
935940
}
936941
const classTitleLabel = labelContainer
937942
.node()
938-
.appendChild(createLabel(classTitleString, node.labelStyle, true, true));
943+
.appendChild(await createLabel(classTitleString, node.labelStyle, true, true));
939944
select(classTitleLabel).attr('class', 'classTitle');
940945
let classTitleBBox = classTitleLabel.getBBox();
941946
if (evaluate(getConfig().flowchart.htmlLabels)) {
@@ -950,7 +955,7 @@ const class_box = (parent, node) => {
950955
maxWidth = classTitleBBox.width;
951956
}
952957
const classAttributes = [];
953-
node.classData.members.forEach((member) => {
958+
node.classData.members.forEach(async (member) => {
954959
const parsedInfo = member.getDisplayDetails();
955960
let parsedText = parsedInfo.displayText;
956961
if (getConfig().flowchart.htmlLabels) {
@@ -959,7 +964,7 @@ const class_box = (parent, node) => {
959964
const lbl = labelContainer
960965
.node()
961966
.appendChild(
962-
createLabel(
967+
await createLabel(
963968
parsedText,
964969
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
965970
true,
@@ -984,7 +989,7 @@ const class_box = (parent, node) => {
984989
maxHeight += lineHeight;
985990

986991
const classMethods = [];
987-
node.classData.methods.forEach((member) => {
992+
node.classData.methods.forEach(async (member) => {
988993
const parsedInfo = member.getDisplayDetails();
989994
let displayText = parsedInfo.displayText;
990995
if (getConfig().flowchart.htmlLabels) {
@@ -993,7 +998,7 @@ const class_box = (parent, node) => {
993998
const lbl = labelContainer
994999
.node()
9951000
.appendChild(
996-
createLabel(
1001+
await createLabel(
9971002
displayText,
9981003
parsedInfo.cssStyle ? parsedInfo.cssStyle : node.labelStyle,
9991004
true,

packages/mermaid/src/dagre-wrapper/shapes/util.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
4848
);
4949
} else {
5050
text = textNode.appendChild(
51-
createLabel(sanitizeText(decodeEntities(labelText), config), node.labelStyle, false, isNode)
51+
await createLabel(
52+
sanitizeText(decodeEntities(labelText), config),
53+
node.labelStyle,
54+
false,
55+
isNode
56+
)
5257
);
5358
}
5459
// Get the size of the label

0 commit comments

Comments
 (0)