| import createNS from './helpers/svg_elements'; |
| import createTag from './helpers/html_elements'; |
| import getFontProperties from './getFontProperties'; |
| |
| const FontManager = (function () { |
| var maxWaitingTime = 5000; |
| var emptyChar = { |
| w: 0, |
| size: 0, |
| shapes: [], |
| data: { |
| shapes: [], |
| }, |
| }; |
| var combinedCharacters = []; |
| // Hindi characters |
| combinedCharacters = combinedCharacters.concat([2304, 2305, 2306, 2307, 2362, 2363, 2364, 2364, 2366, |
| 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, 2375, 2376, 2377, 2378, 2379, |
| 2380, 2381, 2382, 2383, 2387, 2388, 2389, 2390, 2391, 2402, 2403]); |
| |
| var surrogateModifiers = [ |
| 'd83cdffb', |
| 'd83cdffc', |
| 'd83cdffd', |
| 'd83cdffe', |
| 'd83cdfff', |
| ]; |
| |
| var zeroWidthJoiner = [65039, 8205]; |
| |
| function trimFontOptions(font) { |
| var familyArray = font.split(','); |
| var i; |
| var len = familyArray.length; |
| var enabledFamilies = []; |
| for (i = 0; i < len; i += 1) { |
| if (familyArray[i] !== 'sans-serif' && familyArray[i] !== 'monospace') { |
| enabledFamilies.push(familyArray[i]); |
| } |
| } |
| return enabledFamilies.join(','); |
| } |
| |
| function setUpNode(font, family) { |
| var parentNode = createTag('span'); |
| // Node is invisible to screen readers. |
| parentNode.setAttribute('aria-hidden', true); |
| parentNode.style.fontFamily = family; |
| var node = createTag('span'); |
| // Characters that vary significantly among different fonts |
| node.innerText = 'giItT1WQy@!-/#'; |
| // Visible - so we can measure it - but not on the screen |
| parentNode.style.position = 'absolute'; |
| parentNode.style.left = '-10000px'; |
| parentNode.style.top = '-10000px'; |
| // Large font size makes even subtle changes obvious |
| parentNode.style.fontSize = '300px'; |
| // Reset any font properties |
| parentNode.style.fontVariant = 'normal'; |
| parentNode.style.fontStyle = 'normal'; |
| parentNode.style.fontWeight = 'normal'; |
| parentNode.style.letterSpacing = '0'; |
| parentNode.appendChild(node); |
| document.body.appendChild(parentNode); |
| |
| // Remember width with no applied web font |
| var width = node.offsetWidth; |
| node.style.fontFamily = trimFontOptions(font) + ', ' + family; |
| return { node: node, w: width, parent: parentNode }; |
| } |
| |
| function checkLoadedFonts() { |
| var i; |
| var len = this.fonts.length; |
| var node; |
| var w; |
| var loadedCount = len; |
| for (i = 0; i < len; i += 1) { |
| if (this.fonts[i].loaded) { |
| loadedCount -= 1; |
| } else if (this.fonts[i].fOrigin === 'n' || this.fonts[i].origin === 0) { |
| this.fonts[i].loaded = true; |
| } else { |
| node = this.fonts[i].monoCase.node; |
| w = this.fonts[i].monoCase.w; |
| if (node.offsetWidth !== w) { |
| loadedCount -= 1; |
| this.fonts[i].loaded = true; |
| } else { |
| node = this.fonts[i].sansCase.node; |
| w = this.fonts[i].sansCase.w; |
| if (node.offsetWidth !== w) { |
| loadedCount -= 1; |
| this.fonts[i].loaded = true; |
| } |
| } |
| if (this.fonts[i].loaded) { |
| this.fonts[i].sansCase.parent.parentNode.removeChild(this.fonts[i].sansCase.parent); |
| this.fonts[i].monoCase.parent.parentNode.removeChild(this.fonts[i].monoCase.parent); |
| } |
| } |
| } |
| |
| if (loadedCount !== 0 && Date.now() - this.initTime < maxWaitingTime) { |
| setTimeout(this.checkLoadedFontsBinded, 20); |
| } else { |
| setTimeout(this.setIsLoadedBinded, 10); |
| } |
| } |
| |
| function createHelper(fontData, def) { |
| var engine = (document.body && def) ? 'svg' : 'canvas'; |
| var helper; |
| var fontProps = getFontProperties(fontData); |
| if (engine === 'svg') { |
| var tHelper = createNS('text'); |
| tHelper.style.fontSize = '100px'; |
| // tHelper.style.fontFamily = fontData.fFamily; |
| tHelper.setAttribute('font-family', fontData.fFamily); |
| tHelper.setAttribute('font-style', fontProps.style); |
| tHelper.setAttribute('font-weight', fontProps.weight); |
| tHelper.textContent = '1'; |
| if (fontData.fClass) { |
| tHelper.style.fontFamily = 'inherit'; |
| tHelper.setAttribute('class', fontData.fClass); |
| } else { |
| tHelper.style.fontFamily = fontData.fFamily; |
| } |
| def.appendChild(tHelper); |
| helper = tHelper; |
| } else { |
| var tCanvasHelper = new OffscreenCanvas(500, 500).getContext('2d'); |
| tCanvasHelper.font = fontProps.style + ' ' + fontProps.weight + ' 100px ' + fontData.fFamily; |
| helper = tCanvasHelper; |
| } |
| function measure(text) { |
| if (engine === 'svg') { |
| helper.textContent = text; |
| return helper.getComputedTextLength(); |
| } |
| return helper.measureText(text).width; |
| } |
| return { |
| measureText: measure, |
| }; |
| } |
| |
| function addFonts(fontData, defs) { |
| if (!fontData) { |
| this.isLoaded = true; |
| return; |
| } |
| if (this.chars) { |
| this.isLoaded = true; |
| this.fonts = fontData.list; |
| return; |
| } |
| if (!document.body) { |
| this.isLoaded = true; |
| fontData.list.forEach((data) => { |
| data.helper = createHelper(data); |
| data.cache = {}; |
| }); |
| this.fonts = fontData.list; |
| return; |
| } |
| |
| var fontArr = fontData.list; |
| var i; |
| var len = fontArr.length; |
| var _pendingFonts = len; |
| for (i = 0; i < len; i += 1) { |
| var shouldLoadFont = true; |
| var loadedSelector; |
| var j; |
| fontArr[i].loaded = false; |
| fontArr[i].monoCase = setUpNode(fontArr[i].fFamily, 'monospace'); |
| fontArr[i].sansCase = setUpNode(fontArr[i].fFamily, 'sans-serif'); |
| if (!fontArr[i].fPath) { |
| fontArr[i].loaded = true; |
| _pendingFonts -= 1; |
| } else if (fontArr[i].fOrigin === 'p' || fontArr[i].origin === 3) { |
| loadedSelector = document.querySelectorAll('style[f-forigin="p"][f-family="' + fontArr[i].fFamily + '"], style[f-origin="3"][f-family="' + fontArr[i].fFamily + '"]'); |
| |
| if (loadedSelector.length > 0) { |
| shouldLoadFont = false; |
| } |
| |
| if (shouldLoadFont) { |
| var s = createTag('style'); |
| s.setAttribute('f-forigin', fontArr[i].fOrigin); |
| s.setAttribute('f-origin', fontArr[i].origin); |
| s.setAttribute('f-family', fontArr[i].fFamily); |
| s.type = 'text/css'; |
| s.innerText = '@font-face {font-family: ' + fontArr[i].fFamily + "; font-style: normal; src: url('" + fontArr[i].fPath + "');}"; |
| defs.appendChild(s); |
| } |
| } else if (fontArr[i].fOrigin === 'g' || fontArr[i].origin === 1) { |
| loadedSelector = document.querySelectorAll('link[f-forigin="g"], link[f-origin="1"]'); |
| |
| for (j = 0; j < loadedSelector.length; j += 1) { |
| if (loadedSelector[j].href.indexOf(fontArr[i].fPath) !== -1) { |
| // Font is already loaded |
| shouldLoadFont = false; |
| } |
| } |
| |
| if (shouldLoadFont) { |
| var l = createTag('link'); |
| l.setAttribute('f-forigin', fontArr[i].fOrigin); |
| l.setAttribute('f-origin', fontArr[i].origin); |
| l.type = 'text/css'; |
| l.rel = 'stylesheet'; |
| l.href = fontArr[i].fPath; |
| document.body.appendChild(l); |
| } |
| } else if (fontArr[i].fOrigin === 't' || fontArr[i].origin === 2) { |
| loadedSelector = document.querySelectorAll('script[f-forigin="t"], script[f-origin="2"]'); |
| |
| for (j = 0; j < loadedSelector.length; j += 1) { |
| if (fontArr[i].fPath === loadedSelector[j].src) { |
| // Font is already loaded |
| shouldLoadFont = false; |
| } |
| } |
| |
| if (shouldLoadFont) { |
| var sc = createTag('link'); |
| sc.setAttribute('f-forigin', fontArr[i].fOrigin); |
| sc.setAttribute('f-origin', fontArr[i].origin); |
| sc.setAttribute('rel', 'stylesheet'); |
| sc.setAttribute('href', fontArr[i].fPath); |
| defs.appendChild(sc); |
| } |
| } |
| fontArr[i].helper = createHelper(fontArr[i], defs); |
| fontArr[i].cache = {}; |
| this.fonts.push(fontArr[i]); |
| } |
| if (_pendingFonts === 0) { |
| this.isLoaded = true; |
| } else { |
| // On some cases even if the font is loaded, it won't load correctly when measuring text on canvas. |
| // Adding this timeout seems to fix it |
| setTimeout(this.checkLoadedFonts.bind(this), 100); |
| } |
| } |
| |
| function addChars(chars) { |
| if (!chars) { |
| return; |
| } |
| if (!this.chars) { |
| this.chars = []; |
| } |
| var i; |
| var len = chars.length; |
| var j; |
| var jLen = this.chars.length; |
| var found; |
| for (i = 0; i < len; i += 1) { |
| j = 0; |
| found = false; |
| while (j < jLen) { |
| if (this.chars[j].style === chars[i].style && this.chars[j].fFamily === chars[i].fFamily && this.chars[j].ch === chars[i].ch) { |
| found = true; |
| } |
| j += 1; |
| } |
| if (!found) { |
| this.chars.push(chars[i]); |
| jLen += 1; |
| } |
| } |
| } |
| |
| function getCharData(char, style, font) { |
| var i = 0; |
| var len = this.chars.length; |
| while (i < len) { |
| if (this.chars[i].ch === char && this.chars[i].style === style && this.chars[i].fFamily === font) { |
| return this.chars[i]; |
| } |
| i += 1; |
| } |
| if (((typeof char === 'string' && char.charCodeAt(0) !== 13) || !char) |
| && console |
| && console.warn // eslint-disable-line no-console |
| && !this._warned |
| ) { |
| this._warned = true; |
| console.warn('Missing character from exported characters list: ', char, style, font); // eslint-disable-line no-console |
| } |
| return emptyChar; |
| } |
| |
| function measureText(char, fontName, size) { |
| var fontData = this.getFontByName(fontName); |
| var index = char.charCodeAt(0); |
| if (!fontData.cache[index + 1]) { |
| var tHelper = fontData.helper; |
| if (char === ' ') { |
| var doubleSize = tHelper.measureText('|' + char + '|'); |
| var singleSize = tHelper.measureText('||'); |
| fontData.cache[index + 1] = (doubleSize - singleSize) / 100; |
| } else { |
| fontData.cache[index + 1] = tHelper.measureText(char) / 100; |
| } |
| } |
| return fontData.cache[index + 1] * size; |
| } |
| |
| function getFontByName(name) { |
| var i = 0; |
| var len = this.fonts.length; |
| while (i < len) { |
| if (this.fonts[i].fName === name) { |
| return this.fonts[i]; |
| } |
| i += 1; |
| } |
| return this.fonts[0]; |
| } |
| |
| function isModifier(firstCharCode, secondCharCode) { |
| var sum = firstCharCode.toString(16) + secondCharCode.toString(16); |
| return surrogateModifiers.indexOf(sum) !== -1; |
| } |
| |
| function isZeroWidthJoiner(firstCharCode, secondCharCode) { |
| if (!secondCharCode) { |
| return firstCharCode === zeroWidthJoiner[1]; |
| } |
| return firstCharCode === zeroWidthJoiner[0] && secondCharCode === zeroWidthJoiner[1]; |
| } |
| |
| function isCombinedCharacter(char) { |
| return combinedCharacters.indexOf(char) !== -1; |
| } |
| |
| function setIsLoaded() { |
| this.isLoaded = true; |
| } |
| |
| var Font = function () { |
| this.fonts = []; |
| this.chars = null; |
| this.typekitLoaded = 0; |
| this.isLoaded = false; |
| this._warned = false; |
| this.initTime = Date.now(); |
| this.setIsLoadedBinded = this.setIsLoaded.bind(this); |
| this.checkLoadedFontsBinded = this.checkLoadedFonts.bind(this); |
| }; |
| Font.isModifier = isModifier; |
| Font.isZeroWidthJoiner = isZeroWidthJoiner; |
| Font.isCombinedCharacter = isCombinedCharacter; |
| |
| var fontPrototype = { |
| addChars: addChars, |
| addFonts: addFonts, |
| getCharData: getCharData, |
| getFontByName: getFontByName, |
| measureText: measureText, |
| checkLoadedFonts: checkLoadedFonts, |
| setIsLoaded: setIsLoaded, |
| }; |
| |
| Font.prototype = fontPrototype; |
| |
| return Font; |
| }()); |
| |
| export default FontManager; |