diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/customWidthsMap.js b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/customWidthsMap.js new file mode 100644 index 00000000000..78883b7e639 --- /dev/null +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/customWidthsMap.js @@ -0,0 +1,583 @@ +const widthsMap = { + "lucida sans unicode": { + "0": [ + 63, + 63, + 63, + 63 + ], + "1": [ + 63, + 63, + 63, + 63 + ], + "2": [ + 63, + 63, + 63, + 63 + ], + "3": [ + 63, + 63, + 63, + 63 + ], + "4": [ + 63, + 63, + 63, + 63 + ], + "5": [ + 63, + 63, + 63, + 63 + ], + "6": [ + 63, + 63, + 63, + 63 + ], + "7": [ + 63, + 63, + 63, + 63 + ], + "8": [ + 63, + 63, + 63, + 63 + ], + "9": [ + 63, + 63, + 63, + 63 + ], + " ": [ + 32, + 32, + 32, + 32 + ], + "!": [ + 32, + 32, + 32, + 32 + ], + "\"": [ + 37, + 37, + 37, + 37 + ], + "#": [ + 63, + 63, + 63, + 63 + ], + "$": [ + 63, + 63, + 63, + 63 + ], + "%": [ + 67, + 67, + 67, + 67 + ], + "&": [ + 70, + 70, + 70, + 70 + ], + "'": [ + 23, + 23, + 23, + 23 + ], + "(": [ + 33, + 33, + 33, + 33 + ], + ")": [ + 33, + 33, + 33, + 33 + ], + "*": [ + 48, + 48, + 48, + 48 + ], + "+": [ + 80, + 80, + 80, + 80 + ], + ",": [ + 32, + 32, + 32, + 32 + ], + "-": [ + 58, + 58, + 58, + 58 + ], + ".": [ + 32, + 32, + 32, + 32 + ], + "/": [ + 52, + 52, + 52, + 52 + ], + ":": [ + 32, + 32, + 32, + 32 + ], + ";": [ + 32, + 32, + 32, + 32 + ], + "<": [ + 80, + 80, + 80, + 80 + ], + "=": [ + 80, + 80, + 80, + 80 + ], + ">": [ + 80, + 80, + 80, + 80 + ], + "?": [ + 42, + 42, + 42, + 42 + ], + "@": [ + 86, + 86, + 86, + 86 + ], + "A": [ + 69, + 69, + 69, + 69 + ], + "B": [ + 58, + 58, + 58, + 58 + ], + "C": [ + 69, + 69, + 69, + 69 + ], + "D": [ + 75, + 75, + 75, + 75 + ], + "E": [ + 54, + 54, + 54, + 54 + ], + "F": [ + 54, + 54, + 54, + 54 + ], + "G": [ + 72, + 72, + 72, + 72 + ], + "H": [ + 74, + 74, + 74, + 74 + ], + "I": [ + 29, + 29, + 29, + 29 + ], + "J": [ + 31, + 31, + 31, + 31 + ], + "K": [ + 65, + 65, + 65, + 65 + ], + "L": [ + 53, + 53, + 53, + 53 + ], + "M": [ + 86, + 86, + 86, + 86 + ], + "N": [ + 74, + 74, + 74, + 74 + ], + "O": [ + 78, + 78, + 78, + 78 + ], + "P": [ + 55, + 55, + 55, + 55 + ], + "Q": [ + 78, + 78, + 78, + 78 + ], + "R": [ + 63, + 63, + 63, + 63 + ], + "S": [ + 54, + 54, + 54, + 54 + ], + "T": [ + 63, + 63, + 63, + 63 + ], + "U": [ + 69, + 69, + 69, + 69 + ], + "V": [ + 65, + 65, + 65, + 65 + ], + "W": [ + 86, + 86, + 86, + 86 + ], + "X": [ + 63, + 63, + 63, + 63 + ], + "Y": [ + 62, + 62, + 62, + 62 + ], + "Z": [ + 60, + 60, + 60, + 60 + ], + "[": [ + 33, + 33, + 33, + 33 + ], + "\\": [ + 52, + 52, + 52, + 52 + ], + "]": [ + 33, + 33, + 33, + 33 + ], + "^": [ + 63, + 63, + 63, + 63 + ], + "_": [ + 50, + 50, + 50, + 50 + ], + "`": [ + 61, + 61, + 61, + 61 + ], + "a": [ + 55, + 55, + 55, + 55 + ], + "b": [ + 63, + 63, + 63, + 63 + ], + "c": [ + 51, + 51, + 51, + 51 + ], + "d": [ + 63, + 63, + 63, + 63 + ], + "e": [ + 56, + 56, + 56, + 56 + ], + "f": [ + 37, + 37, + 37, + 37 + ], + "g": [ + 62, + 62, + 62, + 62 + ], + "h": [ + 62, + 62, + 62, + 62 + ], + "i": [ + 29, + 29, + 29, + 29 + ], + "j": [ + 30, + 30, + 30, + 30 + ], + "k": [ + 58, + 58, + 58, + 58 + ], + "l": [ + 29, + 29, + 29, + 29 + ], + "m": [ + 93, + 93, + 93, + 93 + ], + "n": [ + 62, + 62, + 62, + 62 + ], + "o": [ + 61, + 61, + 61, + 61 + ], + "p": [ + 63, + 63, + 63, + 63 + ], + "q": [ + 63, + 63, + 63, + 63 + ], + "r": [ + 41, + 41, + 41, + 41 + ], + "s": [ + 51, + 51, + 51, + 51 + ], + "t": [ + 37, + 37, + 37, + 37 + ], + "u": [ + 62, + 62, + 62, + 62 + ], + "v": [ + 52, + 52, + 52, + 52 + ], + "w": [ + 77, + 77, + 77, + 77 + ], + "x": [ + 61, + 61, + 61, + 61 + ], + "y": [ + 52, + 52, + 52, + 52 + ], + "z": [ + 57, + 57, + 57, + 57 + ], + "{": [ + 33, + 33, + 33, + 33 + ], + "|": [ + 37, + 37, + 37, + 37 + ], + "}": [ + 33, + 33, + 33, + 33 + ], + "~": [ + 63, + 63, + 63, + 63 + ], + "→": [ + 94, + 94, + 94, + 94 + ] + } +}; + +exports.default = widthsMap; +module.exports = exports["default"]; \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/jsdom-exporter.js b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/jsdom-exporter.js index e1dcacbfe2b..9d1088f2584 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/jsdom-exporter.js +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/jsdom-exporter.js @@ -8,6 +8,12 @@ const jsdom = require('jsdom'); const fs = require('fs'); const path = require('path'); +const customWidthsMap = require('./customWidthsMap.js'); +const defaultWidthsMap = require('string-pixel-width/lib/widthsMap.js'); +const pixelWidth = require('string-pixel-width'); + +// Combine the default font widths map from string-pixel-width with our additions +const widthsMap = { ...defaultWidthsMap, ...customWidthsMap }; const { JSDOM } = jsdom; @@ -25,7 +31,7 @@ const doc = win.document; global.Node = win.Node // Require Highcharts with the window shim -const Highcharts = require('highcharts/highstock')(win); +const Highcharts = require('highcharts/highstock.src')(win); require("highcharts/modules/accessibility")(Highcharts); require("highcharts/highcharts-more")(Highcharts); require("highcharts/highcharts-3d")(Highcharts); @@ -45,101 +51,207 @@ require("highcharts/modules/bullet")(Highcharts); win.Date = Date; +function processTextNodes(element, cb) { + for (var childNode of element.childNodes) { + if (childNode.nodeType === Node.ELEMENT_NODE) { + processTextNodes(childNode, cb); + } else if (childNode.nodeType === Node.TEXT_NODE) { + cb(childNode, element); + } + } +} + +/** + * Search up parent chain for an element with the requested attribute set + * + * @param {*} ele + * @param {*} attr + * @returns Attribute, possibly inherited, or undefined if not found + */ +function findStyleAttr(ele, attr) { + while (ele) { + if (ele.style[attr]) { + return ele.style[attr]; + } + ele = ele.parentElement; + } +} + +const sizableFonts = Object.keys(widthsMap); + +/** + * The string-pixel-width library supports a limited set of fonts. Search a font-family + * list for a usable font, or find the next-best fallback + */ +function findSizableFont(fontFamily = "") { + let fonts = fontFamily.split(",") + .map(s => s.trim()) + .map(s => s.replace(/^"(.*)"$/, '$1')) // Un-quote any quoted entries + .map(s => s.toLowerCase()); + + // Search the font list for one in our list of sizable fonts + let usableFont = fonts.find(f => sizableFonts.includes(f)); + + if (!usableFont) { + // None of those are sizable. Go with Highcharts default font + usableFont = 'times new roman'; + } + + return usableFont; +} + +function removeHighchartsTextOutlines(elem) { + let children = [].slice.call( + elem.children.length ? elem.children : [elem] + ); + children.forEach(child => { + if (child.getAttribute('class') === 'highcharts-text-outline') { + child.parentNode.removeChild(child); + } + }); + +} + // Do some modifications to the jsdom document in order to get the SVG bounding // boxes right. -let oldCreateElementNS = doc.createElementNS; -doc.createElementNS = (ns, tagName) => { - let elem = oldCreateElementNS.call(doc, ns, tagName); - if (ns !== 'http://www.w3.org/2000/svg') { - return elem; - } - /** - * Pass Highcharts' test for SVG capabilities - * @returns {undefined} - */ - elem.createSVGRect = () => { }; - /** - * jsdom doesn't compute layout (see - * https://github.com/tmpvar/jsdom/issues/135). This getBBox implementation - * provides just enough information to get Highcharts to render text boxes - * correctly, and is not intended to work like a general getBBox - * implementation. The height of the boxes are computed from the sum of - * tspans and their font sizes. The width is based on an average width for - * each glyph. It could easily be improved to take font-weight into account. - * For a more exact result we could to create a map over glyph widths for - * several fonts and sizes, but it may not be necessary for the purpose. - * If the width for the element is zero, then the height is also - * set to zero, in order to not reserve any vertical space for elements - * without content. - * @returns {Object} The bounding box - */ - elem.getBBox = () => { - let lineWidth = 0, - width = 0, - height = 0; - - let children = [].slice.call( - elem.children.length ? elem.children : [elem] - ); +/** + * Pass Highcharts' test for SVG capabilities + * @returns {undefined} + */ +win.SVGElement.prototype.createSVGRect = function() { }; +/** + * jsdom doesn't compute layout (see + * https://github.com/tmpvar/jsdom/issues/135). This getBBox implementation + * provides just enough information to get Highcharts to render text boxes + * correctly, and is not intended to work like a general getBBox + * implementation. The height of the boxes are computed from the sum of + * tspans and their font sizes. The width is based on an average width for + * each glyph. It could easily be improved to take font-weight into account. + * For a more exact result we could to create a map over glyph widths for + * several fonts and sizes, but it may not be necessary for the purpose. + * If the width for the element is zero, then the height is also + * set to zero, in order to not reserve any vertical space for elements + * without content. + * @returns {Object} The bounding box + */ +win.SVGElement.prototype.getBBox = function() { + let lineWidth = 0, + lineHeight = 0, + width = 0, + height = 0, + newLine = false; - children - .filter(child => { - if (child.getAttribute('class') === 'highcharts-text-outline') { - child.parentNode.removeChild(child); - return false; - } - return true; - }) - .forEach(child => { - let fontSize = child.style.fontSize || elem.style.fontSize, - lineHeight, - textLength; - - // The font size and lineHeight is based on empirical values, - // copied from the SVGRenderer.fontMetrics function in - // Highcharts. - if (/px/.test(fontSize)) { - fontSize = parseInt(fontSize, 10); - } else { - fontSize = /em/.test(fontSize) ? - parseFloat(fontSize) * 12 : - 12; - } - lineHeight = fontSize < 24 ? - fontSize + 3 : - Math.round(fontSize * 1.2); - textLength = child.textContent.length * fontSize * 0.55; - - // Tspans on the same line - if (child.getAttribute('dx') !== '0') { - height += lineHeight; - } + removeHighchartsTextOutlines(this); - // New line - if (child.getAttribute('dy') !== null) { - lineWidth = 0; - } + processTextNodes(this, (textNode, child) => { + if (child.tagName === 'title') { + return; + } + let fontSize = findStyleAttr(child, 'fontSize'), + fontFamily = findStyleAttr(child, 'fontFamily'), + textLength; - lineWidth += textLength; - width = Math.max(width, lineWidth); + // The font size and lineHeight is based on empirical values, + // copied from the SVGRenderer.fontMetrics function in + // Highcharts. + if (/px/.test(fontSize)) { + fontSize = parseInt(fontSize, 10); + } else { + fontSize = /em/.test(fontSize) ? + parseFloat(fontSize) * 12 : + 12; + } + let nodeHeight = fontSize < 24 ? + fontSize + 3 : + Math.round(fontSize * 1.2); + lineHeight = Math.max(lineHeight, nodeHeight); + let fontToUse = findSizableFont(fontFamily); + textLength = pixelWidth(textNode.data, { size: fontSize, font: fontToUse, map: widthsMap }); - } - ); + // In practice, dy is used to trigger a new line + if (child.getAttribute('dy') !== null) { + lineWidth = 0; + newLine = true; + } + + lineWidth += textLength; + width = Math.max(width, lineWidth); + + if (newLine) { + height += lineHeight; + newLine = false; + lineHeight = 0; + } + }); + + // Add the height of the ongoing line + height += lineHeight; - // If the width of the text box is 0, always return a 0 height (since the element indeed consumes no space) - // Returning a non-zero height causes Highcharts to allocate vertical space in the chart for text that doesn't - // exist - let retHeight = width == 0 ? 0 : height; - return { - x: 0, - y: 0, - width: width, - height: retHeight - }; + // If the width of the text box is 0, always return a 0 height (since the element indeed consumes no space) + // Returning a non-zero height causes Highcharts to allocate vertical space in the chart for text that doesn't + // exist + let retHeight = width == 0 ? 0 : height; + return { + x: 0, + y: 0, + width: width, + height: retHeight }; - return elem; }; +/** + * Estimate the rendered length of a substring of text. Uses a similar strategy to getBBox, + * above. + * + * @param {integer} charnum Starting character position + * @param {integer} numchars Number of characters to count + * @returns Rendered length of the substring (estimated) + */ +win.SVGElement.prototype.getSubStringLength = function(charnum, numchars) { + let offset = charnum, + remaining = numchars, + textLength = 0; + + removeHighchartsTextOutlines(this); + + processTextNodes(this, (textNode, child) => { + if (child.tagName === 'title') { + return; + } + + if (remaining <= 0) { + return; + } + let childLength = textNode.length; + + if (childLength <= offset) { + offset -= childLength; + } else { + let usedLength = Math.min(childLength - offset, remaining); + remaining -= usedLength; + + let fontSize = findStyleAttr(child, 'fontSize'), + fontFamily = findStyleAttr(child, 'fontFamily'); + + // The font size is based on empirical values, + // copied from the SVGRenderer.fontMetrics function in + // Highcharts. + if (/px/.test(fontSize)) { + fontSize = parseInt(fontSize, 10); + } else { + fontSize = /em/.test(fontSize) ? + parseFloat(fontSize) * 12 : + 12; + } + let textToSize = textNode.data.substring(offset, offset + usedLength); + let fontToUse = findSizableFont(fontFamily); + let measuredWidth = pixelWidth(textToSize, { size: fontSize, font: fontToUse, map: widthsMap }); + textLength += measuredWidth; + } + }); + + return textLength; +} const inflateFunctions = (jsonConfiguration) => { Object.entries(jsonConfiguration).forEach(([attr, targetProperty]) => { diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/package.json b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/package.json index c534f44234e..b1e89fd433f 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/package.json +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/package.json @@ -5,16 +5,18 @@ "scripts": { "test": "mocha", "test:watch": "mocha --watch", - "build": "webpack --config webpack.config.js" + "build": "webpack --config webpack.config.js" }, "dependencies": { "highcharts": "9.2.2", - "jsdom": "16.5.3" + "jsdom": "16.5.3", + "string-pixel-width": "1.11.0" }, "devDependencies": { "chai": "4.3.4", "mocha": "8.4.0", "mock-fs": "5.0.0", + "rewire": "7.0.0", "webpack": "5.76.0", "webpack-cli": "4.9.2" }, diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/java/com/vaadin/flow/component/charts/export/SVGGeneratorTest.java b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/java/com/vaadin/flow/component/charts/export/SVGGeneratorTest.java index 727f075cfe1..dae4e4f09a3 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/java/com/vaadin/flow/component/charts/export/SVGGeneratorTest.java +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/java/com/vaadin/flow/component/charts/export/SVGGeneratorTest.java @@ -261,8 +261,9 @@ private Configuration createColumnWithoutTitle() { XAxis x = new XAxis(); x.setCategories("January is a long month", "February is rather boring", - "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", - "Dec"); + "Mar", "Apr", "May", "Jun", + "Jul is a month to enjoy really nice weather", "Aug", "Sep", + "Oct", "Nov", "Dec"); configuration.addxAxis(x); YAxis y = new YAxis(); diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/column-without-title.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/column-without-title.svg index 02a0c50e992..4d9ec97de60 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/column-without-title.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/column-without-title.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-height.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-height.svg index 9ba44bd101d..8cc9a23abca 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-height.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-height.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-lang.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-lang.svg index 0f0d1c27947..013f52a3ee2 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-lang.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-lang.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-width.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-width.svg index ee9f080c048..a4999078ca2 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-width.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/custom-width.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/empty.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/empty.svg index 20ada559d65..2eb2dbcd1f9 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/empty.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/empty.svg @@ -1 +1 @@ -Created with Highcharts 9.2.2No data to display \ No newline at end of file +Created with Highcharts 9.2.2No data to display \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/enabled-functions.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/enabled-functions.svg index 2a0f9494181..0bc1cdece79 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/enabled-functions.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/enabled-functions.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/lumo-dark.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/lumo-dark.svg index 32b3f9b865e..298b8b31f65 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/lumo-dark.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/lumo-dark.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/pie.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/pie.svg index fd29933e4f3..1f2d416cd1c 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/pie.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/pie.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/timeline.svg b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/timeline.svg index d88b9e06019..0df3e1b1f6d 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/timeline.svg +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/test/resources/timeline.svg @@ -1 +1 @@ -View 1 month1mView 3 months3mView 6 months6mView year to dateYTDView 1 year1yView allAll \ No newline at end of file +View 1 month1mView 3 months3mView 6 months6mView year to dateYTDView 1 year1yView allAll \ No newline at end of file diff --git a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/test/index.test.js b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/test/index.test.js index e7654e06b58..3c414fd2ce2 100644 --- a/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/test/index.test.js +++ b/vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/test/index.test.js @@ -1,8 +1,13 @@ const { expect } = require('chai') +const rewire = require('rewire'); const { JSDOM } = require('jsdom'); const mock = require('mock-fs') +const pixelWidth = require('string-pixel-width'); -const jsdomExporter = require('../jsdom-exporter.js') +const jsdomExporter = rewire('../jsdom-exporter.js') + +const exporterDom = jsdomExporter.__get__("dom"); +const widthsMap = jsdomExporter.__get__("widthsMap"); /** * @@ -157,3 +162,22 @@ describe('timeline', () => { expect(document.querySelector('.highcharts-navigator')).to.be.not.null; }); }); + +describe('getSubStringLength', () => { + it('should measure strings split across multiple elements', () => { + let window = exporterDom.window; + let document = window.document; + let container = document.getElementById('container'); + container.innerHTML = ` + + 0123456789 + + `; + let text = container.querySelector('text'); + expect(text).to.be.not.null; + expect(text.getSubStringLength(6, 3)).to.equal(pixelWidth("678", { size: 12, font: 'arial', map: widthsMap })) + expect(text.getSubStringLength(1, 2)).to.equal(pixelWidth("12", { size: 12, font: 'arial', map: widthsMap })) + expect(text.getSubStringLength(2, 6)).to.equal(pixelWidth("234567", { size: 12, font: 'arial', map: widthsMap })) + expect(text.getSubStringLength(0, 20)).to.equal(pixelWidth("0123456789", { size: 12, font: 'arial', map: widthsMap })) + }); +}); \ No newline at end of file