diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json
index 71abdfdb4..325985c7c 100644
--- a/packages/mermaid/package.json
+++ b/packages/mermaid/package.json
@@ -68,6 +68,10 @@
},
"dependencies": {
"@braintree/sanitize-url": "^7.0.1",
+ "@fortawesome/fontawesome-svg-core": "^6.7.2",
+ "@fortawesome/free-brands-svg-icons": "^6.7.2",
+ "@fortawesome/free-regular-svg-icons": "^6.7.2",
+ "@fortawesome/free-solid-svg-icons": "^6.7.2",
"@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3",
diff --git a/packages/mermaid/src/rendering-util/createText.spec.ts b/packages/mermaid/src/rendering-util/createText.spec.ts
index da0505ad8..7229072ce 100644
--- a/packages/mermaid/src/rendering-util/createText.spec.ts
+++ b/packages/mermaid/src/rendering-util/createText.spec.ts
@@ -1,12 +1,14 @@
import { describe, it, expect } from 'vitest';
import { replaceIconSubstring } from './createText.js';
+import { icon } from '@fortawesome/fontawesome-svg-core';
+import { faUser, faArrowRight, faHome } from '@fortawesome/free-solid-svg-icons';
+import { faGithub } from '@fortawesome/free-brands-svg-icons';
describe('replaceIconSubstring', () => {
it('converts FontAwesome icon notations to HTML tags', () => {
const input = 'This is an icon: fa:fa-user and fab:fa-github';
const output = replaceIconSubstring(input);
- const expected =
- "This is an icon: and ";
+ const expected = `This is an icon: ${icon(faUser).html.join('')} and ${icon(faGithub).html.join('')}`;
expect(output).toEqual(expected);
});
@@ -19,8 +21,7 @@ describe('replaceIconSubstring', () => {
it('correctly processes multiple FontAwesome icon notations in one string', () => {
const input = 'Icons galore: fa:fa-arrow-right, fak:fa-truck, fas:fa-home';
const output = replaceIconSubstring(input);
- const expected =
- "Icons galore: , , ";
+ const expected = `Icons galore: ${icon(faArrowRight).html.join()}, , ${icon(faHome).html.join()}`;
expect(output).toEqual(expected);
});
diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts
index cc189e46e..3fa01a777 100644
--- a/packages/mermaid/src/rendering-util/createText.ts
+++ b/packages/mermaid/src/rendering-util/createText.ts
@@ -11,6 +11,22 @@ import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdo
import { decodeEntities } from '../utils.js';
import { splitLineToFitWidth } from './splitText.js';
import type { MarkdownLine, MarkdownWord } from './types.js';
+import { library, icon } from '@fortawesome/fontawesome-svg-core';
+import * as fab from '@fortawesome/free-brands-svg-icons';
+import * as fas from '@fortawesome/free-solid-svg-icons';
+import * as far from '@fortawesome/free-regular-svg-icons';
+
+const iconListFab = Object.keys(fab)
+ .filter((key) => key !== 'fab' && key !== 'prefix')
+ .map((icon) => fab[icon]);
+const iconListFas = Object.keys(fas)
+ .filter((key) => key !== 'fas' && key !== 'prefix')
+ .map((icon) => fas[icon]);
+const iconListFar = Object.keys(far)
+ .filter((key) => key !== 'far' && key !== 'prefix')
+ .map((icon) => far[icon]);
+
+library.add(...iconListFab, ...iconListFas, ...iconListFar);
function applyStyle(dom, styleFn) {
if (styleFn) {
@@ -180,14 +196,36 @@ function updateTextContentAndStyles(tspan: any, wrappedLine: MarkdownWord[]) {
/**
* Convert fontawesome labels into fontawesome icons by using a regex pattern
* @param text - The raw string to convert
- * @returns string with fontawesome icons as i tags
+ * @returns string with fontawesome icons as i tags if they are from pro pack and as svg if they are from free pack
*/
-export function replaceIconSubstring(text: string) {
- // The letters 'bklrs' stand for possible endings of the fontawesome prefix (e.g. 'fab' for brands, 'fak' for fa-kit) // cspell: disable-line
- return text.replace(
- /fa[bklrs]?:fa-[\w-]+/g, // cspell: disable-line
- (s) => ``
- );
+export function replaceIconSubstring(text) {
+ const iconRegex = /(fas|fab|far|fa|fal|fak|fad):fa-([a-z-]+)/g;
+ const classNameMap = {
+ fas: 'fa-solid',
+ fab: 'fa-brands',
+ far: 'fa-regular',
+ fa: 'fa',
+ fal: 'fa-light',
+ fad: 'fa-duotone',
+ fak: 'fak',
+ } as const;
+ const freeIconPack = ['fas', 'fab', 'far', 'fa'];
+
+ return text.replace(iconRegex, (match, prefix, iconName) => {
+ const isFreeIcon = freeIconPack.includes(prefix);
+ const className = classNameMap[prefix];
+ if (!isFreeIcon) {
+ log.warn(`Icon ${prefix}:fa-${iconName} is pro icon.`);
+ return ``;
+ }
+ const faIcon = icon({ prefix: prefix, iconName: iconName });
+ if (!faIcon) {
+ log.warn(`Icon ${prefix}:fa-${iconName} not found.`);
+ return match;
+ }
+
+ return faIcon.html.join('');
+ });
}
// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to sett classes to'nodeLabel' when isNode=true otherwise 'edgeLabel'
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index df09304fa..178fb27ec 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -217,6 +217,18 @@ importers:
'@braintree/sanitize-url':
specifier: ^7.0.1
version: 7.1.0
+ '@fortawesome/fontawesome-svg-core':
+ specifier: ^6.7.2
+ version: 6.7.2
+ '@fortawesome/free-brands-svg-icons':
+ specifier: ^6.7.2
+ version: 6.7.2
+ '@fortawesome/free-regular-svg-icons':
+ specifier: ^6.7.2
+ version: 6.7.2
+ '@fortawesome/free-solid-svg-icons':
+ specifier: ^6.7.2
+ version: 6.7.2
'@iconify/utils':
specifier: ^2.1.32
version: 2.1.33
@@ -2225,6 +2237,26 @@ packages:
'@floating-ui/vue@1.1.5':
resolution: {integrity: sha512-ynL1p5Z+woPVSwgMGqeDrx6HrJfGIDzFyESFkyqJKilGW1+h/8yVY29Khn0LaU6wHBRwZ13ntG6reiHWK6jyzw==}
+ '@fortawesome/fontawesome-common-types@6.7.2':
+ resolution: {integrity: sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==}
+ engines: {node: '>=6'}
+
+ '@fortawesome/fontawesome-svg-core@6.7.2':
+ resolution: {integrity: sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==}
+ engines: {node: '>=6'}
+
+ '@fortawesome/free-brands-svg-icons@6.7.2':
+ resolution: {integrity: sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==}
+ engines: {node: '>=6'}
+
+ '@fortawesome/free-regular-svg-icons@6.7.2':
+ resolution: {integrity: sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==}
+ engines: {node: '>=6'}
+
+ '@fortawesome/free-solid-svg-icons@6.7.2':
+ resolution: {integrity: sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==}
+ engines: {node: '>=6'}
+
'@hapi/hoek@9.3.0':
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
@@ -12533,6 +12565,24 @@ snapshots:
- '@vue/composition-api'
- vue
+ '@fortawesome/fontawesome-common-types@6.7.2': {}
+
+ '@fortawesome/fontawesome-svg-core@6.7.2':
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.7.2
+
+ '@fortawesome/free-brands-svg-icons@6.7.2':
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.7.2
+
+ '@fortawesome/free-regular-svg-icons@6.7.2':
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.7.2
+
+ '@fortawesome/free-solid-svg-icons@6.7.2':
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.7.2
+
'@hapi/hoek@9.3.0': {}
'@hapi/topo@5.1.0':