blob: 4f749fb9f224bc1cbd80b1a70c4f746dd6f7f60f [file] [log] [blame]
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;