var Promise = require('promise'); var dom = require('./dom'); var Ruler = require('./ruler'); /** * @constructor * * @param {string} family * @param [fontface.Descriptors] descriptors */ var Observer = function (family, descriptors) { descriptors = descriptors || {weight: 'normal'}; /** * @type {string} */ this['family'] = family; /** * @type {string} */ this['style'] = descriptors.style || 'normal'; /** * @type {string} */ this['variant'] = descriptors.variant || 'normal'; /** * @type {string} */ this['weight'] = descriptors.weight || 'normal'; /** * @type {string} */ this['stretch'] = descriptors.stretch || 'stretch'; /** * @type {string} */ this['featureSettings'] = descriptors.featureSettings || 'normal'; }; module.exports = Observer; /** * @type {null|boolean} */ Observer.HAS_WEBKIT_FALLBACK_BUG = null; /** * @type {number} */ Observer.DEFAULT_TIMEOUT = 3000; /** * @return {string} */ Observer.getUserAgent = function () { return window.navigator.userAgent; }; /** * Returns true if this browser is WebKit and it has the fallback bug * which is present in WebKit 536.11 and earlier. * * @return {boolean} */ Observer.hasWebKitFallbackBug = function () { if (Observer.HAS_WEBKIT_FALLBACK_BUG === null) { var match = /AppleWeb[kK]it\/([0-9]+)(?:\.([0-9]+))/.exec(Observer.getUserAgent()); Observer.HAS_WEBKIT_FALLBACK_BUG = !!match && (parseInt(match[1], 10) < 536 || (parseInt(match[1], 10) === 536 && parseInt(match[2], 10) <= 11)); } return Observer.HAS_WEBKIT_FALLBACK_BUG; }; /** * @private * @return {string} */ Observer.prototype.getStyle = function () { return 'font-style:' + this['style'] + ';' + 'font-variant:' + this['variant'] + ';' + 'font-weight:' + this['weight'] + ';' + 'font-stretch:' + this['stretch'] + ';' + 'font-feature-settings:' + this['featureSettings'] + ';' + '-moz-font-feature-settings:' + this['featureSettings'] + ';' + '-webkit-font-feature-settings:' + this['featureSettings'] + ';'; }; /** * @param {string=} text Optional test string to use for detecting if a font is available. * @param {number=} timeout Optional timeout for giving up on font load detection and rejecting the promise (defaults to 3 seconds). * @return {Promise.} */ Observer.prototype.check = function (text, timeout) { var testString = text || 'BESbswy', timeoutValue = timeout || Observer.DEFAULT_TIMEOUT, style = this.getStyle(), container = dom.createElement('div'), rulerA = new Ruler(testString), rulerB = new Ruler(testString), rulerC = new Ruler(testString), widthA = -1, widthB = -1, widthC = -1, fallbackWidthA = -1, fallbackWidthB = -1, fallbackWidthC = -1, that = this; rulerA.setFont('"Times New Roman", sans-serif', style); rulerB.setFont('serif', style); rulerC.setFont('monospace', style); dom.append(container, rulerA.getElement()); dom.append(container, rulerB.getElement()); dom.append(container, rulerC.getElement()); dom.append(document.body, container); fallbackWidthA = rulerA.getWidth(); fallbackWidthB = rulerB.getWidth(); fallbackWidthC = rulerC.getWidth(); return new Promise(function (resolve, reject) { /** * @private */ function removeContainer() { if (container.parentNode !== null) { dom.remove(document.body, container); } } /** * @private * * Cases: * 1) Font loads: both a, b and c are called and have the same value. * 2) Font fails to load: resize callback is never called and timeout happens. * 3) WebKit bug: both a, b and c are called and have the same value, but the * values are equal to one of the last resort fonts, we ignore this and * continue waiting until we get new values (or a timeout). */ function check() { if (widthA !== -1 && widthB !== -1 && widthC !== -1) { // All values are changed from their initial state if (widthA === widthB && widthB === widthC) { // All values are the same, so the browser has most likely loaded the web font if (Observer.hasWebKitFallbackBug()) { // Except if the browser has the WebKit fallback bug, in which case we check to see if all // values are set to one of the last resort fonts. if (!((widthA === fallbackWidthA && widthB === fallbackWidthA && widthC === fallbackWidthA) || (widthA === fallbackWidthB && widthB === fallbackWidthB && widthC === fallbackWidthB) || (widthA === fallbackWidthC && widthB === fallbackWidthC && widthC === fallbackWidthC))) { // The width we got doesn't match any of the known last resort fonts, so let's assume fonts are loaded. removeContainer(); resolve(that); } } else { removeContainer(); resolve(that); } } } } setTimeout(function () { removeContainer(); reject(that); }, timeoutValue); rulerA.onResize(function (width) { widthA = width; check(); }); rulerA.setFont(that['family'] + ',sans-serif', style); rulerB.onResize(function (width) { widthB = width; check(); }); rulerB.setFont(that['family'] + ',serif', style); rulerC.onResize(function (width) { widthC = width; check(); }); rulerC.setFont(that['family'] + ',monospace', style); }); };