Shoora
10/3/2018 - 4:49 AM

css-font-loader.js

css-font-loader

'use strict';

var CSSFontLoader = function() {

  var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
  var isFirefox = typeof InstallTrigger !== 'undefined';
  var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || safari.pushNotification);
  var isIE = /*@cc_on!@*/false || !!document.documentMode;
  var isEdge = !isIE && !!window.StyleMedia;
  var isChrome = !!window.chrome && !!window.chrome.webstore;

  var _Promise = null;
  var _url = null;

  if(typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]') !== -1){ // check if there is native promise support.
      _Promise = Promise;
  }

  var api = {};

  api.setPromise = function(promiseLib) { _Promise = promiseLib };

  api.load = function(url, callback) {
    _url = url;

    if(callback){
      api.downloadCSS(callback);
    } else if(_Promise) { 
      return new _Promise(api.downloadCSS) 
    } else {
      api.downloadCSS(function(){});
    }
  }

  api.downloadCSS = function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', _url, true);

    xhr.onreadystatechange = function() {
      if (this.readyState !== 4) return;
      if (this.status !== 200) return;
      
      var cssSource = String(this.responseText).replace(/ *local\([^)]*\), */g, ''); // remove all local references to force remote font file to be downloaded and used
      
      api.loadFromCSS(cssSource, resolve);
    }

    xhr.send();

  };

  api.loadFromCSS = function(cssSource, callback){
    var cssOriginal = cssSource;
    var originalFonts = getCSSFonts(cssSource, true);

    var id = String(new Date().getTime());
    
    var cssNew = api.renderFontCSS(originalFonts);

    var loadedFonts = -1;
    var fontsToLoad = getCSSFonts(cssSource);
    var fontsToReference = getCSSFonts(cssNew, true);

    //console.log('fontsToLoad',fontsToLoad);

    function loadedCallback() {
      loadedFonts++;

      var fontToLoad = fontsToLoad[loadedFonts];
      if(fontToLoad) {

        var fontReferences = fontsToReference.filter(function(fontRef){

          var isSameFont = fontToLoad.family == fontRef.family;
          var isSameStyle = fontToLoad.style == fontRef.style;
          var isSameWeight = fontToLoad.weight == fontRef.weight;

          return isSameFont && isSameStyle && isSameWeight;
        });

        if(isChrome){
          fontToLoad.family = fontToLoad.family;// + fontToLoad.weight + fontToLoad.style + String(new Date().getTime());
        } else {
          fontToLoad.family = fontToLoad.family + fontToLoad.weight + fontToLoad.style + String(new Date().getTime());
        }

        var styleTag = null;

        

        styleTag = document.createElement('style');
        styleTag.innerHTML += api.renderFontCSS(fontReferences);
        document.head.appendChild(styleTag);

        for(var f in fontReferences){
          fontReferences[f].family = fontToLoad.family;
        }

        if(!isChrome){

          styleTag = document.createElement('style');
          styleTag.innerHTML += api.renderFontCSS(fontReferences);
          document.head.appendChild(styleTag);
          
        }

        api.waitForWebfont(fontToLoad, loadedCallback);
      } else {
        //console.log('done----');
        //styleTag.innerHTML = cssOriginal;
        if(callback) callback();
      }
    }

    loadedCallback();
  }

  api.renderFontCSS = function(fonts){
    var cssNew = '';
    for(var i in fonts) { // force css to use font family name in single quotes
      var font = fonts[i];
      //console.log(fonts);
      var uniqueName = font.family;// + font.weight + font.style + String(new Date().getTime());

      cssNew += '@font-face {\n';
      cssNew += ' font-family: \'' + uniqueName + '\'\;\n';
      cssNew += ' font-style: ' + font.style + '\;\n';
      cssNew += ' font-weight: ' + font.weight + '\;\n';
      cssNew += ' src: '+ font.src + '\;\n';
      if(font.unicode) cssNew += ' unicode-range: '+ font.unicode + '\;\n';
      cssNew += '}\n\n';

      var regex = new RegExp('[\'|"]' + font.family + '[\'|"]'   , 'g');
      font.family = uniqueName; // make font family name unique.
    }
    return cssNew;
  }

  api.waitForWebfont = function(font/*s*/, callback) {
    //console.log('waitForWebfont', fonts);
    var loadedFonts = 0;
    var testNodes = [];

    var family = font.family;
    var weight = font.weight;
    var style = font.style;

    var testNode = createFontTestNode(family, weight, style);

    var nullWidth = Number(String(testNode.offsetWidth));
    testNode.style.fontFamily = 'sans-serif';
    var sansWidth = Number(String(testNode.offsetWidth));
    testNode.style.fontFamily = '\'' + String(new Date().getTime()) + '\'';
    var errorWidth = Number(String(testNode.offsetWidth));
    testNode.style.fontFamily = '\'' + String(new Date().getTime()) + '\'';
    var familyWidth = Number(String(testNode.offsetWidth));
    
    testNodes.push({
      family: family,
      weight: weight,
      style: style,
      elem: testNode, 
      nullWidth: nullWidth,
      sansWidth: sansWidth,
      errorWidth: errorWidth,
      changeDetected: 0, 
      loaded: false
    });
      
    //testNodes.forEach(function(e){console.log(e.family, e.weight, e.style, e.nullWidth, e.sansWidth, e.errorWidth, e.elem.offsetWidth)})
    //console.log('====');
    checkFonts(testNodes, callback);
   
  }
  
  function checkFonts(nodes, callback) {
    // Compare current width with original width
    //console.log('====');
    
    nodes.forEach(function(e){
      var newWidth = String(Number(e.elem.offsetWidth));
      e.elem.style.fontFamily = '\'' + e.family + '\', sans-serif';
      
      if(e.nullWidth != newWidth && e.sansWidth != newWidth && e.errorWidth != newWidth) {
        var matchOtherFontWidths = false;
        for(var n in nodes){
          var node = nodes[n];
          matchOtherFontWidths = node.elem.offsetWidth == newWidth;
          if(matchOtherFontWidths) break;
        }
        
        if(matchOtherFontWidths == false) {
          
        }
        e.loaded = true;
        
      }
    });
    var loadedNodes = nodes.filter(function(e){
      return e.loaded;
    });
    //nodes.forEach(function(e){console.log(e.family, e.weight, e.style, e.nullWidth, e.sansWidth, e.errorWidth, e.elem.offsetWidth)})
    if(loadedNodes.length == nodes.length){
      for(var n in nodes){
        var node = nodes[n];
        node.elem.parentNode.removeChild(node.elem);
      }
      nodes = [];
      callback();
    } else {
      setTimeout(function(){
        checkFonts(nodes, callback);
      },50);
    }
  };

  function createFontTestNode(family, weight, style){
    var node = document.createElement('span');
    var testString = '9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#9giItT1WQy@!-/#'; // Characters that vary significantly among different fonts
    node.innerHTML = testString;
    node.style.float = 'left';
    node.style.position      = 'absolute'; // Visible - so we can measure it - but not on the screen
    // node.style.display = 'block'; // for debug
    // node.style.float = 'left'; // for debug
    node.style.left          = '-100000px';
    node.style.top           = '-100000px';
    node.style.fontSize      = '30px'; // Large font size makes even subtle changes obvious
    // Reset any font properties
    //node.style.fontFamily    = 'LoadString45178';
    node.style.fontVariant   = 'normal';
    node.style.fontStyle     = style;
    node.style.fontWeight    = weight;
    node.style.letterSpacing = '0';
    node.style.whiteSpace    = 'nowrap';
    node.style.opacity       = 0;
    document.body.appendChild(node);
    
    return node;
  }
  
  function getCSSFonts(cssSource, includeExtras) {
    var fontCSS = getCSSSelectorContents('@font-face', cssSource);
    var fonts = [];
    for(var f in fontCSS) {
      var css = fontCSS[f];
      var font = {};
      font.weight = getCSSPropertyValues('font-weight', css)[0].replace(/["']+/g, '');
      font.family = getCSSPropertyValues('font-family', css)[0].replace(/["']+/g, '');
      font.style = getCSSPropertyValues('font-style', css)[0].replace(/["']+/g, '');
      if(includeExtras) font.src = getCSSPropertyValues('src', css)[0].replace('format' ,' format');
      var unicode = getCSSPropertyValues('unicode-range', css);
      if(includeExtras && unicode) font.unicode = unicode[0].replace(/["']+/g, '');
      fonts.push(font);
    }
    window.fonts = fonts;
    fonts = removeDuplicateObjects(fonts);
    return fonts;
  }
  
  function getCSSUrls(cssSource){
    var regex = new RegExp('url\\b[^\\(]*\\(([\\s\\S]*?)\\)', 'gm');
    var results = null;
    var match; 
    while (match = regex.exec(cssSource)) {
      if(results) {
        results.push(match[1]);
      } else {
        results = [match[1]];
      }
    }
    
    results = results.map(function(elem) { return elem.replace(/["'\s]+/g, ''); });
    results = results.sort().filter(function(item, pos, ary) { return !pos || item != ary[pos - 1]; });
    
    return results;
  }

  function getCSSPropertyValues(cssProperty, cssSource) {
    var regex = new RegExp(cssProperty+'\\b[^:]*:([\\s\\S]*?);', 'gm');
    var results = null;
    var match; 
    while (match = regex.exec(cssSource)) {
      if(results) {
        results.push(match[1].replace(/[ \t]+/, ''));
      } else {
        results = [match[1].replace(/[ \t]+/, '')];
      }
    }

    return results;
  }
  
  function getCSSSelectorContents(selector, cssSource) {
    var regex = new RegExp(selector+'\\s*{([\\s\\S]*?)}', 'gm'); //section example: @font-face, #container
    
    var results = null;
    var match; 
    while (match = regex.exec(cssSource)) {
      if(results) {
        results.push(match[1]);
      } else {
        results = [match[1]];
      }
    }
    return results;
  }
  
  function removeDuplicates(object) {
    return object.sort().filter(function(item, pos, ary) { return !pos || item != ary[pos - 1]; });
  }
  
  function removeDuplicateObjects(objectsArray) {
      var usedObjects = {};

      for (var i=objectsArray.length - 1;i>=0;i--) {
          var so = JSON.stringify(objectsArray[i]);

          if (usedObjects[so]) {
              objectsArray.splice(i, 1);
          } else {
              usedObjects[so] = true;          
          }
      }
    
      return objectsArray;
  }

  return api;
}

module.exports = CSSFontLoader();