Home Reference Source

scripts/utils/font.js

/* globals document */
'use strict'

import { html } from './html.js'
import { loop } from './loop.js'

/**
 * Contains the Adobe Blank font code to be used in testing
 * @external {Adobe Blank} https://github.com/adobe-fonts/adobe-blank
 */
import AdobeBlankCss from './../../styles/adobe-blank.inline.scss'

/**
 * Creates a font test container
 * @param {String} name The name of the container
 * @return {HTMLElement} The html tag containing the test text
 */
let createContainer = ( name ) => {
  return html( `
    <span id="modux-font-` + name + `" style="position: absolute; top: -500px">
      abcdefghijklmnopqrstuvwxyz
      1234567890
      ABCDEFGHIGKLMNOPQRSTUVWXYZ
      !? -_
    </span>
  ` )
}

/**
 * A class used to load fonts
 */
export class Font {
  /**
   * Creates a new instance of Font
   * @param {String} font Holds the font name
   * @param {Object} styles Holds the font styles as object properties
   */
  constructor ( font, styles ) {
    /**
     * Holds the font name
     * @type {String}
     * @private
     */
    this.__font = font
    /**
     * Holds the font styles as object properties
     * @type {Object}
     * @private
     */
    this.__styles = styles
  }

  /**
   * Returns a new instance of Font
   * @param {String} font Holds the font name
   * @param {Object} styles Holds the font styles as object properties
   * @return {Font} The newly created Font instance
   */
  static create ( font, styles ) {
    return new Font( font, styles )
  }

  /**
   * Loads the font into the html document
   * @param {Array} srcs An array of sources pointing to the font file ( ttf, otf, etc )
   * @return {Font} Returns the current instance of the class
   */
  get ( srcs ) {
    /**
     * Holds the fontfile css source
     * @type {HTMLElement}
     * @private
     */
    this.__fontfile = html( '<style></style>' )
    document.head.appendChild( this.__fontfile )
    let data = 'src: ' + srcs.join( ', ' ) + ';\n'
    loop( this.__styles, ( value, key ) => {
      data += key + ': ' + value + ';\n'
    } )
    this.__fontfile.innerHTML = `
    @font-face {
      font-family: '` + this.__font + `';
      ` + data + `
    }
    `
    return this
  }

  /**
   * Destroy font related tags
   */
  destroy () {
    if ( this.__fontfile ) {
      this.__fontfile.remove()
    }
  }

  /**
   * A method to check if a font has been loaded
   * @param {Number} [attempts=-1] The number of attempts before the promise is rejected. Default is infinite.
   * @param {Number} [interval=100] The time in milliseconds it takes to retry the load
   * @return {Promise} A promise that is resolved when the font is loaded
  */
  load ( attempts, interval ) {
    attempts = attempts || -1

    let blankFontStyle = html( '<style></style>' )
    document.head.appendChild( blankFontStyle )
    blankFontStyle.innerHTML = AdobeBlankCss

    let container = createContainer( this.__font )
    document.body.appendChild( container )

    container.style.fontFamily = '"' + this.__font + '", "AdobeBlank"'

    loop( this.__styles, ( value, key ) => {
      container.style[ key ] = value
    } )

    let count = 0

    return new Promise( ( resolve, reject ) => {
      let cleanUp = () => {
        container.parentNode.removeChild( container )
        blankFontStyle.remove()
      }
      let timer = () => {
        if ( container.offsetWidth > 0 ) {
          cleanUp()
          resolve()
        } else {
          count++
          if ( count === attempts ) {
            cleanUp()
            reject()
          } else {
            setTimeout( timer, interval || 100 )
          }
        }
      }

      setTimeout( () => {
        timer()
      }, interval || 100 )
    } )
  }
}