diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index ce1caea69..220db2209 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -1,5 +1,6 @@ import common from '../common/common'; import { addFunction } from '../../interactionDb'; +import { parseFontSize } from '../../utils'; import { sanitizeUrl } from '@braintree/sanitize-url'; export const drawRect = function (elem, rectData) { @@ -156,6 +157,8 @@ export const drawText = function (elem, textData) { textHeight = 0; const lines = textData.text.split(common.lineBreakRegex); + const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize); + let textElems = []; let dy = 0; let yfunc = () => textData.y; @@ -218,9 +221,9 @@ export const drawText = function (elem, textData) { if ( textData.textMargin !== undefined && textData.textMargin === 0 && - textData.fontSize !== undefined + _textFontSize !== undefined ) { - dy = i * textData.fontSize; + dy = i * _textFontSize; } const textElem = elem.append('text'); @@ -235,8 +238,8 @@ export const drawText = function (elem, textData) { if (textData.fontFamily !== undefined) { textElem.style('font-family', textData.fontFamily); } - if (textData.fontSize !== undefined) { - textElem.style('font-size', textData.fontSize); + if (_textFontSizePx !== undefined) { + textElem.style('font-size', _textFontSizePx); } if (textData.fontWeight !== undefined) { textElem.style('font-weight', textData.fontWeight); @@ -840,8 +843,7 @@ const _drawTextCandidateFunc = (function () { function byTspan(content, g, x, y, width, height, textAttrs, conf) { const { actorFontSize, actorFontFamily, actorFontWeight } = conf; - let _actorFontSize = - actorFontSize && actorFontSize.replace ? actorFontSize.replace('px', '') : actorFontSize; + const [_actorFontSize, _actorFontSizePx] = parseFontSize(actorFontSize); const lines = content.split(common.lineBreakRegex); for (let i = 0; i < lines.length; i++) { @@ -851,7 +853,7 @@ const _drawTextCandidateFunc = (function () { .attr('x', x + width / 2) .attr('y', y) .style('text-anchor', 'middle') - .style('font-size', actorFontSize) + .style('font-size', _actorFontSizePx) .style('font-weight', actorFontWeight) .style('font-family', actorFontFamily); text diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index 8e5f5f32b..ed60285ed 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js @@ -125,6 +125,30 @@ describe('svgDraw', function () { expect(text3.attr).toHaveBeenCalledWith('y', 10); expect(text3.text).toHaveBeenCalledWith('fine lines'); }); + it('should work with numeral font sizes', function () { + const svg = MockD3('svg'); + svgDraw.drawText(svg, { + x: 10, + y: 10, + dy: '1em', + text: 'One fine text message', + class: 'noteText', + fontFamily: 'courier', + fontSize: 10, + fontWeight: '500', + }); + expect(svg.__children.length).toBe(1); + const text = svg.__children[0]; + expect(text.__name).toBe('text'); + expect(text.attr).toHaveBeenCalledWith('x', 10); + expect(text.attr).toHaveBeenCalledWith('y', 10); + expect(text.attr).toHaveBeenCalledWith('dy', '1em'); + expect(text.attr).toHaveBeenCalledWith('class', 'noteText'); + expect(text.text).toHaveBeenCalledWith('One fine text message'); + expect(text.style).toHaveBeenCalledWith('font-family', 'courier'); + expect(text.style).toHaveBeenCalledWith('font-size', '10px'); + expect(text.style).toHaveBeenCalledWith('font-weight', '500'); + }); }); describe('drawBackgroundRect', function () { it('should append a rect before the previous element within a given bound', function () { diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index 7ee6aa000..0f0bc1e92 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -402,3 +402,29 @@ describe('when inserting titles', function () { expect(titleAttrSpy).toHaveBeenCalledWith('class', 'testClass'); }); }); + +describe('when parsing font sizes', function () { + it('parses number inputs', function () { + expect(utils.parseFontSize(14)).toEqual([14, '14px']); + }); + + it('parses string em inputs', function () { + expect(utils.parseFontSize('14em')).toEqual([14, '14em']); + }); + + it('parses string px inputs', function () { + expect(utils.parseFontSize('14px')).toEqual([14, '14px']); + }); + + it('parses string inputs without units', function () { + expect(utils.parseFontSize('14')).toEqual([14, '14px']); + }); + + it('handles undefined input', function () { + expect(utils.parseFontSize(undefined)).toEqual([undefined, undefined]); + }); + + it('handles unparseable input', function () { + expect(utils.parseFontSize({ fontSize: 14 })).toEqual([undefined, undefined]); + }); +}); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 4c2f844e2..876c81543 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -543,12 +543,14 @@ export const drawSimpleText = function ( // Remove and ignore br:s const nText = textData.text.replace(common.lineBreakRegex, ' '); + const [, _fontSizePx] = parseFontSize(textData.fontSize); + const textElem = elem.append('text'); textElem.attr('x', textData.x); textElem.attr('y', textData.y); textElem.style('text-anchor', textData.anchor); textElem.style('font-family', textData.fontFamily); - textElem.style('font-size', textData.fontSize); + textElem.style('font-size', _fontSizePx); textElem.style('font-weight', textData.fontWeight); textElem.attr('fill', textData.fill); if (textData.class !== undefined) { @@ -722,6 +724,8 @@ export const calculateTextDimensions: ( return { width: 0, height: 0 }; } + const [, _fontSizePx] = parseFontSize(fontSize); + // We can't really know if the user supplied font family will render on the user agent; // thus, we'll take the max width between the user supplied font family, and a default // of sans-serif. @@ -745,7 +749,7 @@ export const calculateTextDimensions: ( const textObj = getTextObj(); textObj.text = line; const textElem = drawSimpleText(g, textObj) - .style('font-size', fontSize) + .style('font-size', _fontSizePx) .style('font-weight', fontWeight) .style('font-family', fontFamily); @@ -941,6 +945,32 @@ export const insertTitle = ( .attr('class', cssClass); }; +/** + * Parses a raw fontSize configuration value into a number and string value. + * + * @param fontSize - a string or number font size configuration value + * + * @returns parsed number and string style font size values, or nulls if a number value can't + * be parsed from an input string. + */ +export const parseFontSize = (fontSize: string | number | undefined): [number?, string?] => { + // if the font size is a number, assume a px string representation + if (typeof fontSize === 'number') { + return [fontSize, fontSize + 'px']; + } + + const fontSizeNumber = parseInt(fontSize, 10); + if (Number.isNaN(fontSizeNumber)) { + // if a number value can't be parsed, return null for both values + return [undefined, undefined]; + } else if (fontSize === String(fontSizeNumber)) { + // if a string input doesn't contain any units, assume px units + return [fontSizeNumber, fontSize + 'px']; + } else { + return [fontSizeNumber, fontSize]; + } +}; + export default { assignWithDepth, wrapLabel, @@ -964,4 +994,5 @@ export default { directiveSanitizer, sanitizeCss, insertTitle, + parseFontSize, };