You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

444 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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 BLACK_FLAG_CODE_POINT = 127988;
var CANCEL_TAG_CODE_POINT = 917631;
var A_TAG_CODE_POINT = 917601;
var Z_TAG_CODE_POINT = 917626;
var VARIATION_SELECTOR_16_CODE_POINT = 65039;
var ZERO_WIDTH_JOINER_CODE_POINT = 8205;
var REGIONAL_CHARACTER_A_CODE_POINT = 127462;
var REGIONAL_CHARACTER_Z_CODE_POINT = 127487;
var surrogateModifiers = [
'd83cdffb',
'd83cdffc',
'd83cdffd',
'd83cdffe',
'd83cdfff',
];
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);
// Using the char instead of char.charCodeAt(0)
// to avoid collisions between equal chars
var index = char;
if (!fontData.cache[index]) {
var tHelper = fontData.helper;
if (char === ' ') {
var doubleSize = tHelper.measureText('|' + char + '|');
var singleSize = tHelper.measureText('||');
fontData.cache[index] = (doubleSize - singleSize) / 100;
} else {
fontData.cache[index] = tHelper.measureText(char) / 100;
}
}
return fontData.cache[index] * 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 getCodePoint(string) {
var codePoint = 0;
var first = string.charCodeAt(0);
if (first >= 0xD800 && first <= 0xDBFF) {
var second = string.charCodeAt(1);
if (second >= 0xDC00 && second <= 0xDFFF) {
codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
}
}
return codePoint;
}
// Skin tone modifiers
function isModifier(firstCharCode, secondCharCode) {
var sum = firstCharCode.toString(16) + secondCharCode.toString(16);
return surrogateModifiers.indexOf(sum) !== -1;
}
function isZeroWidthJoiner(charCode) {
return charCode === ZERO_WIDTH_JOINER_CODE_POINT;
}
// This codepoint may change the appearance of the preceding character.
// If that is a symbol, dingbat or emoji, U+FE0F forces it to be rendered
// as a colorful image as compared to a monochrome text variant.
function isVariationSelector(charCode) {
return charCode === VARIATION_SELECTOR_16_CODE_POINT;
}
// The regional indicator symbols are a set of 26 alphabetic Unicode
/// characters (AZ) intended to be used to encode ISO 3166-1 alpha-2
// two-letter country codes in a way that allows optional special treatment.
function isRegionalCode(string) {
var codePoint = getCodePoint(string);
if (codePoint >= REGIONAL_CHARACTER_A_CODE_POINT && codePoint <= REGIONAL_CHARACTER_Z_CODE_POINT) {
return true;
}
return false;
}
// Some Emoji implementations represent combinations of
// two “regional indicator” letters as a single flag symbol.
function isFlagEmoji(string) {
return isRegionalCode(string.substr(0, 2)) && isRegionalCode(string.substr(2, 2));
}
function isCombinedCharacter(char) {
return combinedCharacters.indexOf(char) !== -1;
}
// Regional flags start with a BLACK_FLAG_CODE_POINT
// folowed by 5 chars in the TAG range
// and end with a CANCEL_TAG_CODE_POINT
function isRegionalFlag(text, index) {
var codePoint = getCodePoint(text.substr(index, 2));
if (codePoint !== BLACK_FLAG_CODE_POINT) {
return false;
}
var count = 0;
index += 2;
while (count < 5) {
codePoint = getCodePoint(text.substr(index, 2));
if (codePoint < A_TAG_CODE_POINT || codePoint > Z_TAG_CODE_POINT) {
return false;
}
count += 1;
index += 2;
}
return getCodePoint(text.substr(index, 2)) === CANCEL_TAG_CODE_POINT;
}
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.isFlagEmoji = isFlagEmoji;
Font.isRegionalCode = isRegionalCode;
Font.isCombinedCharacter = isCombinedCharacter;
Font.isRegionalFlag = isRegionalFlag;
Font.isVariationSelector = isVariationSelector;
Font.BLACK_FLAG_CODE_POINT = BLACK_FLAG_CODE_POINT;
var fontPrototype = {
addChars: addChars,
addFonts: addFonts,
getCharData: getCharData,
getFontByName: getFontByName,
measureText: measureText,
checkLoadedFonts: checkLoadedFonts,
setIsLoaded: setIsLoaded,
};
Font.prototype = fontPrototype;
return Font;
}());
export default FontManager;