<commit_before>'use strict';

/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * Incremental font loader object
 * @param {string} fontname Name of the font which will be used as id for font
 * @param {boolean} isTTF True if it is TrueType font, else should be False
 * @constructor
 */
function IncrementalFontLoader(fontname, isTTF) {
  this.fontname = fontname;
  this.isTTF = isTTF;

}

/**
 * Enum for flags in the coming glyph bundle
 * @enum {number}
 */
IncrementalFontLoader.FLAGS = {
    HAS_HMTX: 1,
    HAS_VMTX: 2,
    HAS_CFF: 4
};

/**
 * Segment size in the loca table
 * @const {number}
 */
IncrementalFontLoader.LOCA_BLOCK_SIZE = 64;

/**
 * Find new codepoints
 * @param {string} str Input text
 * @param {Object.<number, number>} codes Codepoints to exclude from the result
 * @return {Array.<number>} New codepoints in the given text
 * @private
 */
IncrementalFontLoader.prototype.strToCodeArrayExceptCodes_ = function(str,
  codes) {
  var len = str.length;
  var arr = [];
  var code;
  for (var i = 0; i < len; i++) {
    code = str.charCodeAt(i);
    if (!codes.hasOwnProperty(code)) {
      arr.push(code);
      codes[code] = 0;
    }
  }
  return arr;
};

/**
 * Read previously requested codepoints from filesystem
 * @param {string} idx_file Filename of the index file of the font
 * @param {FilesystemHelper} fs Filesystem To write object
 * @return {Object.<number, number>} Codepoints from the file
 * @private
 */
IncrementalFontLoader.prototype.readPersistedCharacters_ = function(idx_file,
  fs) {
  return fs.getFileAs(idx_file, FilesystemHelper.TYPES.TEXT).
          then(function(idx_text) {
            if (idx_text) {
              return JSON.parse(idx_text);
            } else {
              return {0: 0};// always request .notdef
            }
          });
};

/**
 * Determine new codepoints
 * @param {Object.<number, number>} codes Existing codepoints
 * @param {string} text New text
 * @return {Promise} Promise to return new codepoints array
 * @private
 */
IncrementalFontLoader.prototype.determineCharacters_ = function(codes, text) {
  var that = this;
  return new Promise(function(resolve) {
    resolve(that.strToCodeArrayExceptCodes_(text, codes));
  });
};

/**
 * Async XMLHttpRequest to given url using given method, data and header
 * @param {string} url Destination url
 * @param {string} method Request method
 * @param {type} data Request data
 * @param {Object} headerParams Request headers
 * @param {string} responseType Response type
 * @return {Promise} Promise to return response
 */
IncrementalFontLoader.requestURL = function(url, method, data, headerParams,
responseType) {
  // time_start('fetch ' + url)
  return new Promise(function(resolve, reject) {
    var oReq = new XMLHttpRequest();
    oReq.open(method, url, true);
    for (var param in headerParams)
      oReq.setRequestHeader(param, headerParams[param]);
    oReq.responseType = responseType;
    oReq.onload = function(oEvent) {
      if (oReq.status == 200) {
        // time_end('fetch ' + url)
        resolve(oReq.response);
      } else
        reject(oReq.status + ' ' + oReq.statusText);
    };
    oReq.onerror = function() {
      reject(Error('Network Error'));
    };
    oReq.send(data);
  });
};

/**
 * Request codepoints from server
 * @param {Array.<number>} chars Codepoints to be requested
 * @return {Promise} Promise to return ArrayBuffer for the response bundle
 * @private
 */
IncrementalFontLoader.prototype.requestCharacters_ = function(chars) {

  return IncrementalFontLoader.requestURL('/incremental_fonts/request', 'POST',
  JSON.stringify({
      'font': this.fontname,
      'arr': chars
  }), {
    'Content-Type': 'application/json'
  }, 'arraybuffer');
};

/**
 * Add and load the font
 * @param {string} font_src Data url of the font
 * @param {function()} callback Action to take when font is loaded
 * @private
 */
IncrementalFontLoader.prototype.setTheFont_ = function(font_src, callback) {
  font_src += ('?t=' + Date.now());
  console.log(font_src);
  var font = new FontFace(this.fontname, 'url(' + font_src + ')', {});
  document.fonts.add(font);
  font.load().then(callback);
};

/**
 * Request base font from the server
 * @return {Promise} Promise to return ArrayBuffer for the base font
 * @private
 */
IncrementalFontLoader.prototype.requestBaseFont_ = function() {
  return IncrementalFontLoader.requestURL('/fonts/' + this.fontname + '/base',
  'GET', null, {},
    'arraybuffer');
};

/**
 * Write font to the filesystem
 * @param {boolean} inFS True if it is in filesystem
 * @param {FilesystemHelper} fs FilesystemHelper to write font
 * @param {string} filename Filename for the font while writing
 * @return {Promise} Promise to write font to the given filesystem
 * @private
 */
IncrementalFontLoader.prototype.getBaseFont_ = function(inFS, fs, filename) {
  if (inFS) {
    return Promise.resolve();
  } else {
    var that = this;
    return this.requestBaseFont_().
            then(RLEDecoder.rleDecode).
            then(that.sanitizeBaseFont_.bind(that)).
            then(function(sanitized_base) {
              return fs.writeToTheFile(filename, sanitized_base,
                'application/octet-stream');
            });
  }

};

/**
 * Sanitize base font to pass OTS
 * @param {ArrayBuffer} baseFont Base font as ArrayBuffer
 * @return {ArrayBuffer} Sanitized base font
 * @private
 */
IncrementalFontLoader.prototype.sanitizeBaseFont_ = function(baseFont) {

  if (this.isTTF) {

    // time_start('sanitize');
    var fontObj = parseFont(baseFont);
    var fontParser = new Parser(new DataView(baseFont), 0);
    var glyphOffset = fontObj.glyfOffset;
    var glyphCount = fontObj.numGlyphs;
    var glyphSize;
    for (var i = (IncrementalFontLoader.LOCA_BLOCK_SIZE - 1); i < glyphCount;
    i += IncrementalFontLoader.LOCA_BLOCK_SIZE) {
      glyphSize = fontObj.loca[i + 1] - fontObj.loca[i];
      if (glyphSize)
        fontParser.writeShortByOffset(glyphOffset + fontObj.loca[i], -1);
    }
    // time_end('sanitize');
  }
  return baseFont;
};

/**
 * Inject glyphs in the glyphData to the baseFont
 * @param {ArrayBuffer} baseFont Current base font
 * @param {ArrayBuffer} glyphData New glyph data
 * @return {ArrayBuffer} Updated base font
 * @private
 */
IncrementalFontLoader.prototype.injectCharacters_ = function(baseFont,
  glyphData) {
  // time_start('inject')
  var glyphParser = new Parser(new DataView(glyphData), 0);
  var fontParser = new Parser(new DataView(baseFont), 0);
  var fontObj = parseFont(baseFont);
  var count = glyphParser.parseUShort();
  var flags = glyphParser.parseByte();

  var glyphOffset, locaOffset;
  if (flags & IncrementalFontLoader.FLAGS.HAS_CFF)
    glyphOffset = fontObj.cffOffset;
  else {
    glyphOffset = fontObj.glyfOffset;
    locaOffset = fontObj.locaOffset;
  }
  console.log('count ' + count);
  for (var i = 0; i < count; i += 1) {
    var id = glyphParser.parseUShort();
    var hmtx, vmtx;
    if (flags & IncrementalFontLoader.FLAGS.HAS_HMTX) {
      hmtx = glyphParser.parseUShort();
      fontObj.metrics[id][1] = hmtx;
    }
    if (flags & IncrementalFontLoader.FLAGS.HAS_VMTX) {
      vmtx = glyphParser.parseUShort();
    }
    var offset = glyphParser.parseULong();
    var length = glyphParser.parseUShort();

    if (!(flags & IncrementalFontLoader.FLAGS.HAS_CFF)) {
      fontObj.loca[id] = offset;
      var isChanged = (fontObj.loca[id + 1] != offset + length);
      fontObj.loca[id + 1] = offset + length;
      var prev_id = id - 1;
      while (prev_id >= 0 && fontObj.loca[prev_id] > offset) {
        fontObj.loca[prev_id] = offset;
        prev_id--;
      }
      /*
       * if value is changed and length is nonzero we should write -1
       */
      if (length > 0 && isChanged)
        fontParser.writeShortByOffset(glyphOffset + fontObj.loca[id + 1], -1);
    }

    var bytes = glyphParser.parseBytes(length);
    fontParser.setBytes(glyphOffset + offset, bytes);
  }
  if (!(flags & IncrementalFontLoader.FLAGS.HAS_CFF))
    fontParser.writeLoca(fontObj);
  if (flags & IncrementalFontLoader.FLAGS.HAS_HMTX)
    fontParser.writeHmtx(fontObj);
  // time_end('inject')

  return baseFont;
};

/**
 * Write the base font to the filesystem and load it
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {function()} callback Action to take after font load
 * @return {Promise} Promise to load base font
 */
IncrementalFontLoader.prototype.getBaseToFileSystem = function(fs, callback) {
  // time_start('getBaseToFileSystem');
  var filename = this.fontname + '.ttf';
  var that = this;
  var doesBaseExist = fs.checkIfFileExists(filename);
  var baseFontPersisted = doesBaseExist.then(function(doesExist) {
                                                return that.getBaseFont_(
                                                        doesExist, fs,
                                                filename);
                                            });

  var fileURLReady = baseFontPersisted.
                       then(function() {
                         return fs.getFileURL(filename);
                       });

  return fileURLReady.
          then(function(fileURL) {
            that.setTheFont_(fileURL, callback);
            // time_end('getBaseToFileSystem');
          });

};

/**
 * Request glyph data for this text and write to the filesystem
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {string} text New text
 * @return {Promise} Promise to get glyph data
 */
IncrementalFontLoader.prototype.requestGlyphs = function(fs, text) {
  // time_start('request glyphs')

  var INDEXFILENAME = this.fontname + '.idx';
  var that = this;
  var doesIdxExist = fs.checkIfFileExists(INDEXFILENAME);

  var injectedChars = doesIdxExist.then(function(doesExist) {
                                        if (doesExist)
                                          return that.readPersistedCharacters_(
                                              INDEXFILENAME, fs);
                                        else
                                          return {};
                                        });

  var charsDetermined = injectedChars.then(function(chars) {
    return that.determineCharacters_(chars, text);
  });

  var indexUpdated = Promise.all([charsDetermined, injectedChars]).
                        then(function(results) {
                          if (results[0].length) {
                            return fs.writeToTheFile(INDEXFILENAME,
                              JSON.stringify(results[1]), 'text/plain');
                          }
                        });

  var bundleReady = Promise.all([charsDetermined, indexUpdated]).
                        then(function(arr) {
                          // time_end('request glyphs')
                          if (arr[0].length) {
                            return that.requestCharacters_(arr[0]);
                          } else {
                            return null;
                          }
                        });

  return bundleReady;
};

/**
 * Inject the bundle to the base font and load updated font
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {type} bundle New glyph data
 * @param {function()} callback Action to take after font load
 * @return {Promise} Promise to inject bundle and load the new font
 */
IncrementalFontLoader.prototype.injectBundle = function(fs, bundle, callback) {
  // time_start('inject bundle')
  var filename = this.fontname + '.ttf';
  var that = this;
  var charsInjected, fileUpdated;
  if (bundle != null) {
    charsInjected = fs.getFileAs(filename,
    FilesystemHelper.TYPES.ARRAYBUFFER).then(function(baseFont) {
                        return that.injectCharacters_(baseFont, bundle);
                      });

    fileUpdated = charsInjected.then(function(newBase) {
        return fs.writeToTheFile(filename, newBase, 'application/octet-stream');
    });
  } else {
    charsInjected = fileUpdated = Promise.resolve();
  }

  var fileURLReady = fileUpdated.then(function() {
      return fs.getFileURL(filename);
  });

  return fileURLReady.then(function(fileURL) {
      // time_end('inject bundle')
      that.setTheFont_(fileURL, callback);
  });

};

/**
 * Update the base font using new glyphs in the text
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {string} text New text
 * @param {function()} callback Action to take after font load
 * @return {Promise} Promise to update base font using new text and load it
 */
IncrementalFontLoader.prototype.incrUpdate = function(fs, text, callback) {

  // time_start('incrUpdate')
  var FILENAME = this.fontname + '.ttf';
  var that = this;
  var bundleReady = that.requestGlyphs(fs, text);

  return bundleReady.
          then(function(bundle) {
            that.injectBundle(fs, bundle, callback);
            // time_end('incrUpdate')
          });
};
<commit_msg>Move persist code out of getBase to getBaseToFs. Add commented out timing code.<commit_after>'use strict';

/*
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * Incremental font loader object
 * @param {string} fontname Name of the font which will be used as id for font
 * @param {boolean} isTTF True if it is TrueType font, else should be False
 * @constructor
 */
function IncrementalFontLoader(fontname, isTTF) {
  this.fontname = fontname;
  this.isTTF = isTTF;

}

/**
 * Enum for flags in the coming glyph bundle
 * @enum {number}
 */
IncrementalFontLoader.FLAGS = {
    HAS_HMTX: 1,
    HAS_VMTX: 2,
    HAS_CFF: 4
};

/**
 * Segment size in the loca table
 * @const {number}
 */
IncrementalFontLoader.LOCA_BLOCK_SIZE = 64;

/**
 * Find new codepoints
 * @param {string} str Input text
 * @param {Object.<number, number>} codes Codepoints to exclude from the result
 * @return {Array.<number>} New codepoints in the given text
 * @private
 */
IncrementalFontLoader.prototype.strToCodeArrayExceptCodes_ = function(str,
  codes) {
  var len = str.length;
  var arr = [];
  var code;
  for (var i = 0; i < len; i++) {
    code = str.charCodeAt(i);
    if (!codes.hasOwnProperty(code)) {
      arr.push(code);
      codes[code] = 0;
    }
  }
  return arr;
};

/**
 * Read previously requested codepoints from filesystem
 * @param {string} idx_file Filename of the index file of the font
 * @param {FilesystemHelper} fs Filesystem To write object
 * @return {Object.<number, number>} Codepoints from the file
 * @private
 */
IncrementalFontLoader.prototype.readPersistedCharacters_ = function(idx_file,
  fs) {
  return fs.getFileAs(idx_file, FilesystemHelper.TYPES.TEXT).
          then(function(idx_text) {
            if (idx_text) {
              return JSON.parse(idx_text);
            } else {
              return {0: 0};// always request .notdef
            }
          });
};

/**
 * Determine new codepoints
 * @param {Object.<number, number>} codes Existing codepoints
 * @param {string} text New text
 * @return {Promise} Promise to return new codepoints array
 * @private
 */
IncrementalFontLoader.prototype.determineCharacters_ = function(codes, text) {
  var that = this;
  return new Promise(function(resolve) {
    resolve(that.strToCodeArrayExceptCodes_(text, codes));
  });
};

//var fetchCnt = 0;
/**
 * Async XMLHttpRequest to given url using given method, data and header
 * @param {string} url Destination url
 * @param {string} method Request method
 * @param {type} data Request data
 * @param {Object} headerParams Request headers
 * @param {string} responseType Response type
 * @return {Promise} Promise to return response
 */
IncrementalFontLoader.requestURL = function(url, method, data, headerParams,
responseType) {
  //var cnt = fetchCnt++;
  //timer.start('fetch ' + cnt + ' ' + url);
  return new Promise(function(resolve, reject) {
    var oReq = new XMLHttpRequest();
    oReq.open(method, url, true);
    for (var param in headerParams)
      oReq.setRequestHeader(param, headerParams[param]);
    oReq.responseType = responseType;
    oReq.onload = function(oEvent) {
      if (oReq.status == 200) {
        //timer.end('fetch ' + cnt + ' ' + url);
        resolve(oReq.response);
      } else
        reject(oReq.status + ' ' + oReq.statusText);
    };
    oReq.onerror = function() {
      reject(Error('Network Error'));
    };
    oReq.send(data);
  });
};

/**
 * Request codepoints from server
 * @param {Array.<number>} chars Codepoints to be requested
 * @return {Promise} Promise to return ArrayBuffer for the response bundle
 * @private
 */
IncrementalFontLoader.prototype.requestCharacters_ = function(chars) {

  return IncrementalFontLoader.requestURL('/incremental_fonts/request', 'POST',
  JSON.stringify({
      'font': this.fontname,
      'arr': chars
  }), {
    'Content-Type': 'application/json'
  }, 'arraybuffer');
};

/**
 * Add and load the font
 * @param {string} font_src Data url of the font
 * @param {function()} callback Action to take when font is loaded
 * @private
 */
IncrementalFontLoader.prototype.setTheFont_ = function(font_src, callback) {
  font_src += ('?t=' + Date.now()); // REMOVE THE TIMESTAMP FOR OBJECT/BLOB URLS
  console.log(font_src);
  var font = new FontFace(this.fontname, 'url(' + font_src + ')', {});
  document.fonts.add(font);
  font.load().then(callback);
};

/**
 * Request base font from the server
 * @return {Promise} Promise to return ArrayBuffer for the base font
 * @private
 */
IncrementalFontLoader.prototype.requestBaseFont_ = function() {
  return IncrementalFontLoader.requestURL('/fonts/' + this.fontname + '/base',
  'GET', null, {},
    'arraybuffer');
};

/**
 * Write font to the filesystem
 * @param {boolean} inFS True if it is in filesystem
 * @param {FilesystemHelper} fs FilesystemHelper to write font
 * @param {string} filename Filename for the font while writing
 * @return {Promise} Promise to write font to the given filesystem
 * @private
 */
IncrementalFontLoader.prototype.getBaseFont_ = function(inFS, fs, filename) {
  if (inFS) {
    return Promise.resolve();
  } else {
    var that = this;
    return this.requestBaseFont_().
    //then(function(data) {
    //  timer.start('rleDecode');
    //  return data;
    //}).
    then(RLEDecoder.rleDecode).
    //then(function(data) {
    //  timer.end('rleDecode');
    //  timer.start('sanitizeBase');
    //  return data;
    //}).
    then(that.sanitizeBaseFont_.bind(that)).
    then(function(sanitized_base) {
      //timer.end('sanitizeBase');
      return sanitized_base;
    });
  }

};

/**
 * Sanitize base font to pass OTS
 * @param {ArrayBuffer} baseFont Base font as ArrayBuffer
 * @return {ArrayBuffer} Sanitized base font
 * @private
 */
IncrementalFontLoader.prototype.sanitizeBaseFont_ = function(baseFont) {

  if (this.isTTF) {

    // time_start('sanitize');
    var fontObj = parseFont(baseFont);
    var fontParser = new Parser(new DataView(baseFont), 0);
    var glyphOffset = fontObj.glyfOffset;
    var glyphCount = fontObj.numGlyphs;
    var glyphSize;
    for (var i = (IncrementalFontLoader.LOCA_BLOCK_SIZE - 1); i < glyphCount;
    i += IncrementalFontLoader.LOCA_BLOCK_SIZE) {
      glyphSize = fontObj.loca[i + 1] - fontObj.loca[i];
      if (glyphSize)
        fontParser.writeShortByOffset(glyphOffset + fontObj.loca[i], -1);
    }
    // time_end('sanitize');
  }
  return baseFont;
};

/**
 * Inject glyphs in the glyphData to the baseFont
 * @param {ArrayBuffer} baseFont Current base font
 * @param {ArrayBuffer} glyphData New glyph data
 * @return {ArrayBuffer} Updated base font
 * @private
 */
IncrementalFontLoader.prototype.injectCharacters_ = function(baseFont,
  glyphData) {
  // time_start('inject')
  var glyphParser = new Parser(new DataView(glyphData), 0);
  var fontParser = new Parser(new DataView(baseFont), 0);
  var fontObj = parseFont(baseFont);
  var count = glyphParser.parseUShort();
  var flags = glyphParser.parseByte();

  var glyphOffset, locaOffset;
  if (flags & IncrementalFontLoader.FLAGS.HAS_CFF)
    glyphOffset = fontObj.cffOffset;
  else {
    glyphOffset = fontObj.glyfOffset;
    locaOffset = fontObj.locaOffset;
  }
  console.log('count ' + count);
  for (var i = 0; i < count; i += 1) {
    var id = glyphParser.parseUShort();
    var hmtx, vmtx;
    if (flags & IncrementalFontLoader.FLAGS.HAS_HMTX) {
      hmtx = glyphParser.parseUShort();
      fontObj.metrics[id][1] = hmtx;
    }
    if (flags & IncrementalFontLoader.FLAGS.HAS_VMTX) {
      vmtx = glyphParser.parseUShort();
    }
    var offset = glyphParser.parseULong();
    var length = glyphParser.parseUShort();

    if (!(flags & IncrementalFontLoader.FLAGS.HAS_CFF)) {
      fontObj.loca[id] = offset;
      var isChanged = (fontObj.loca[id + 1] != offset + length);
      fontObj.loca[id + 1] = offset + length;
      var prev_id = id - 1;
      while (prev_id >= 0 && fontObj.loca[prev_id] > offset) {
        fontObj.loca[prev_id] = offset;
        prev_id--;
      }
      /*
       * if value is changed and length is nonzero we should write -1
       */
      if (length > 0 && isChanged)
        fontParser.writeShortByOffset(glyphOffset + fontObj.loca[id + 1], -1);
    }

    var bytes = glyphParser.parseBytes(length);
    fontParser.setBytes(glyphOffset + offset, bytes);
  }
  if (!(flags & IncrementalFontLoader.FLAGS.HAS_CFF))
    fontParser.writeLoca(fontObj);
  if (flags & IncrementalFontLoader.FLAGS.HAS_HMTX)
    fontParser.writeHmtx(fontObj);
  // time_end('inject')

  return baseFont;
};

/**
 * Write the base font to the filesystem and load it
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {function()} callback Action to take after font load
 * @return {Promise} Promise to load base font
 */
IncrementalFontLoader.prototype.getBaseToFileSystem = function(fs, callback) {
  var filename = this.fontname + '.ttf';
  var that = this;
  var doesBaseExist = fs.checkIfFileExists(filename);
  var baseFontPersisted = doesBaseExist.then(
    function(doesExist) {
      return that.getBaseFont_(doesExist, fs, filename).
      //then(function(sanitized_base) {
      //  // Create a blob and blob URL and set the font.
      //  return sanitized_base;
      //}).
      then(function(sanitized_base) {
        //timer.start('write base to filesystem');
        return fs.writeToTheFile(filename, sanitized_base,
          'application/octet-stream');
      }).
      then(function(data) {
        //timer.end('write base to filesystem');
        return data;
      });
    });

  var fileURLReady = baseFontPersisted.
                       then(function() {
                         return fs.getFileURL(filename);
                       });

  return fileURLReady.
          then(function(fileURL) {
            that.setTheFont_(fileURL, callback);
          });

};

/**
 * Request glyph data for this text and write to the filesystem
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {string} text New text
 * @return {Promise} Promise to get glyph data
 */
IncrementalFontLoader.prototype.requestGlyphs = function(fs, text) {
  // time_start('request glyphs')

  var INDEXFILENAME = this.fontname + '.idx';
  var that = this;
  var doesIdxExist = fs.checkIfFileExists(INDEXFILENAME);

  var injectedChars = doesIdxExist.then(function(doesExist) {
                                        if (doesExist)
                                          return that.readPersistedCharacters_(
                                              INDEXFILENAME, fs);
                                        else
                                          return {};
                                        });

  var charsDetermined = injectedChars.then(function(chars) {
    return that.determineCharacters_(chars, text);
  });

  var indexUpdated = Promise.all([charsDetermined, injectedChars]).
                        then(function(results) {
                          if (results[0].length) {
                            return fs.writeToTheFile(INDEXFILENAME,
                              JSON.stringify(results[1]), 'text/plain');
                          }
                        });

  var bundleReady = Promise.all([charsDetermined, indexUpdated]).
                        then(function(arr) {
                          // time_end('request glyphs')
                          if (arr[0].length) {
                            return that.requestCharacters_(arr[0]);
                          } else {
                            return null;
                          }
                        });

  return bundleReady;
};

/**
 * Inject the bundle to the base font and load updated font
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {type} bundle New glyph data
 * @param {function()} callback Action to take after font load
 * @return {Promise} Promise to inject bundle and load the new font
 */
IncrementalFontLoader.prototype.injectBundle = function(fs, bundle, callback) {
  // time_start('inject bundle')
  var filename = this.fontname + '.ttf';
  var that = this;
  var charsInjected, fileUpdated;
  if (bundle != null) {
    charsInjected = fs.getFileAs(filename,
    FilesystemHelper.TYPES.ARRAYBUFFER).then(function(baseFont) {
                        return that.injectCharacters_(baseFont, bundle);
                      });

    fileUpdated = charsInjected.then(function(newBase) {
        return fs.writeToTheFile(filename, newBase, 'application/octet-stream');
    });
  } else {
    charsInjected = fileUpdated = Promise.resolve();
  }

  var fileURLReady = fileUpdated.then(function() {
      return fs.getFileURL(filename);
  });

  return fileURLReady.then(function(fileURL) {
      // time_end('inject bundle')
      that.setTheFont_(fileURL, callback);
  });

};

/**
 * Update the base font using new glyphs in the text
 * @param {FilesystemHelper} fs Filesystem to be written
 * @param {string} text New text
 * @param {function()} callback Action to take after font load
 * @return {Promise} Promise to update base font using new text and load it
 */
IncrementalFontLoader.prototype.incrUpdate = function(fs, text, callback) {

  // time_start('incrUpdate')
  var FILENAME = this.fontname + '.ttf';
  var that = this;
  var bundleReady = that.requestGlyphs(fs, text);

  return bundleReady.
          then(function(bundle) {
            that.injectBundle(fs, bundle, callback);
            // time_end('incrUpdate')
          });
};
<|endoftext|>


<commit_before>var http = require('http');
var https = require('https');
var qs = require('qs');

function append_params(path, params) {
    if (path && params && Object.keys(params).length > 0) {
        if (!path.match(/\?/)) {
            path += "?";
        } else {
            path += "&";
        }
        path += qs.stringify(params);
    }
    return path;
}

//
// TODO: handle timeouts.
//

var http_client = function (args) {
    var self = {};
    args = args || {};
    self.host = args.host || 'localhost';
    self.port = args.port || 80;
    self.protocol = args.protocol || 'http';
    self.auth = args.auth;

    var client = null;
    if (self.protocol === 'http') {
        client = http;
    } else {
        client = https;
    }

    self.request = function (args, cb) {
        var called_back = false;
        if (!args) {
            return cb(new Error('Elasticsearch error: args required in http client'));
        }

        var req_options = {
            path: append_params(args.path, args.params),
            method: args.method || 'GET',
            host: self.host,
            port: self.port
        };

        var request = client.request(req_options);

        request.on('error', function (error) {
            if (!called_back) {
                called_back = true;
                return cb(error);
            }
        });

        request.on('response', function (response) {
            var body = "";

            response.on('data', function (chunk) {
                body += chunk;
            });

            response.on('end', function () {
                if (!called_back) {
                    called_back = true;
                    return cb(null, body);
                }
            });

            response.on('error', function (error) {
                if (!called_back) {
                    called_back = true;
                    return cb(error);
                }
            });
        });

        // TODO: need to test that this works
        if (self.auth) {
            request.setHeader("Authorization", "Basic " + new Buffer(self.auth.username + ":" + self.auth.password).toString('base64'));
        }

        if (args.body) {
            if (typeof args.body !== 'string') {
                args.body = JSON.stringify(args.body);
            }
            request.setHeader('Content-Type', 'application/json');
            request.setHeader('Content-Length', Buffer.byteLength(args.body, 'utf8'));
            request.end(args.body);
        } else {
            request.end('');
        }
    };

    self.get = function (args, cb) {
        args.method = 'GET';
        self.request(args, cb);
    };

    self.post = function (args, cb) {
        args.method = 'POST';
        self.request(args, cb);
    };

    self.put = function (args, cb) {
        args.method = 'PUT';
        self.request(args, cb);
    };

    self.del = function (args, cb) {
        args.method = 'DELETE';
        self.request(args, cb);
    };

    return self;
};

module.exports = http_client;
<commit_msg>refactoring double-callback protection<commit_after>var http = require('http');
var https = require('https');
var qs = require('qs');

function append_params(path, params) {
    if (path && params && Object.keys(params).length > 0) {
        if (!path.match(/\?/)) {
            path += "?";
        } else {
            path += "&";
        }
        path += qs.stringify(params);
    }
    return path;
}

//
// TODO: handle timeouts.
//

var http_client = function (args) {
    var self = {};
    args = args || {};
    self.host = args.host || 'localhost';
    self.port = args.port || 80;
    self.protocol = args.protocol || 'http';
    self.auth = args.auth;

    var client = null;
    if (self.protocol === 'http') {
        client = http;
    } else {
        client = https;
    }

    self.request = function (args, cb) {
        // To avoid multiple callbacks, wrap the original callback in a new one
        // that tracks whether the callback has already been called.
        var _cb;
        var called_back = false;

        if (!_cb) {
            _cb = cb;
            cb = function () {
                if (!called_back) {
                    called_back = true;
                    _cb.apply(this, arguments);
                }
            };
        }

        if (!args) {
            return cb(new Error('Elasticsearch error: args required in http client'));
        }

        var req_options = {
            path: append_params(args.path, args.params),
            method: args.method || 'GET',
            host: self.host,
            port: self.port
        };

        var request = client.request(req_options);

        request.on('error', function (error) {
            return cb(error);
        });

        request.on('response', function (response) {
            var body = "";

            response.on('data', function (chunk) {
                body += chunk;
            });

            response.on('end', function () {
                return cb(null, body);
            });

            response.on('error', function (error) {
                return cb(error);
            });
        });

        // TODO: need to test that this works
        if (self.auth) {
            request.setHeader("Authorization", "Basic " + new Buffer(self.auth.username + ":" + self.auth.password).toString('base64'));
        }

        if (args.body) {
            if (typeof args.body !== 'string') {
                args.body = JSON.stringify(args.body);
            }
            request.setHeader('Content-Type', 'application/json');
            request.setHeader('Content-Length', Buffer.byteLength(args.body, 'utf8'));
            request.end(args.body);
        } else {
            request.end('');
        }
    };

    self.get = function (args, cb) {
        args.method = 'GET';
        self.request(args, cb);
    };

    self.post = function (args, cb) {
        args.method = 'POST';
        self.request(args, cb);
    };

    self.put = function (args, cb) {
        args.method = 'PUT';
        self.request(args, cb);
    };

    self.del = function (args, cb) {
        args.method = 'DELETE';
        self.request(args, cb);
    };

    return self;
};

module.exports = http_client;
<|endoftext|>


<commit_before>'use strict';

var React = require('react-native');

var {
  TouchableOpacity,
  View,
  StatusBarIOS,
} = React;

var AppList = require("../Components/AppList");
var NavigationBar = require('../Components/NavigationBar');

var MyApps = React.createClass({
  render() {
    StatusBarIOS.setStyle(StatusBarIOS.Style.lightContent);

    return (
      <View style={{flex: 1}}>
        <NavigationBar title={'My Apps'} />
        <AppList url="/apps.json" hideCreator={true} />
      </View>
    )
  }
});

module.exports = MyApps;
<commit_msg>add option for sign out<commit_after>'use strict';

var React = require('react-native');
var Api = require('../Api/Core');
var AppActions = require('../Actions/AppActions');

var {
  TouchableOpacity,
  View,
  StatusBarIOS,
} = React;

var AppList = require("../Components/AppList");
var NavigationBar = require('../Components/NavigationBar');

var MyApps = React.createClass({

  _signOut() {
    Api.delete('/users/sign_out');
    AppActions.deleteProfile();
    this.props.navigator.replace({ id: 'login'});
  },

  render() {
    StatusBarIOS.setStyle(StatusBarIOS.Style.lightContent);

    return (
      <View style={{flex: 1}}>
        <NavigationBar
          title={'My Apps'}
          nextTitle={'Sign Out'}
          onNext={this._signOut}/>
        <AppList url="/apps.json" hideCreator={true} />
      </View>
    )
  }
});

module.exports = MyApps;
<|endoftext|>


<commit_before>var HttpRequest  = require('./httprequest.js'),
    HttpResponse = require('./httpresponse.js'),
    JavascriptBuilder = require('./javascriptbuilder.js'),
    _            = require('lodash');

/**
* HttpBackend
*/
var HttpBackend = function(browser, options) {
    this.browser = browser;
    this.browserGet = this.browser.get;
    this.options = options;
    this.flow = this.browser.driver.controlFlow;
    if (!this.options || this.options == 'undefined') {
        this.options = {};
    }

    if (!_.isBoolean(this.options.autoSync)) {
        this.options.autoSync = true;
    }

    this.reset();
};

HttpBackend.prototype.reset = function() {
    this.isInitialized = false;
    this.clear();
    this._init();
};

HttpBackend.prototype.clear = function() {
    this.responses = [];
    this.unSyncResponses = [];
    this.browser.get = this.browserGet;
    if (this.isInitialized) {
        this.browser.removeMockModule('httpBackendMock');
    }
};

HttpBackend.prototype.when = function(url) {
    return this._when('', url);
};

var generateWhenHelpers = function(methods) {
    methods.forEach(function(method) {
        HttpBackend.prototype['when' + method] = function(url) {
            return this._when(method, url);
        };
    })
};

generateWhenHelpers([
    'GET',
    'POST',
    'PUT',
    'DELETE',
    'HEAD',
    'PATCH',
    'JSONP'
]);

HttpBackend.prototype.init = function() {

    this.isInitialized = true;
    var jsBuilder = new JavascriptBuilder();
    jsBuilder.responses = this.responses;
    this.unSyncResponses = [];

    return this.browser.addMockModule('httpBackendMock', jsBuilder.toString(true));
};

HttpBackend.prototype.sync = function() {
    var jsBuilder = new JavascriptBuilder();
    jsBuilder.responses = this.unSyncResponses;
    this.browser.driver.executeScript(jsBuilder.toString());
    this.unSyncResponses = [];
};

HttpBackend.prototype._init = function() {
    var _this = this;
    this.browser.get = function(destination, opt_timeout) {
        if (!_this.isInitialized) {
            _this.init();
        }

        _this.browserGet.call(browser, destination, opt_timeout);
    };
};

HttpBackend.prototype._when = function(method, url) {
    var request = new HttpRequest(method, url);
    var response = new HttpResponse(request);

    var responseIndex = _.findIndex(this.responses, function(response) {
        return response.request.uid == request.uid;
    });

    if (responseIndex) {
        this.responses.splice(responseIndex, 1, response);
    }

    if (this.options.autoSync && this.isInitialized) {
        var _this = this;
        response.onResponse = function() {
            _this.sync();
        };
    }

    this.responses.push(response);
    this.unSyncResponses.push(response);

    return response;
};

module.exports = HttpBackend;
<commit_msg>Should return promise<commit_after>var HttpRequest  = require('./httprequest.js'),
    HttpResponse = require('./httpresponse.js'),
    JavascriptBuilder = require('./javascriptbuilder.js'),
    _            = require('lodash');

/**
* HttpBackend
*/
var HttpBackend = function(browser, options) {
    this.browser = browser;
    this.browserGet = this.browser.get;
    this.options = options;
    this.flow = this.browser.driver.controlFlow;
    if (!this.options || this.options == 'undefined') {
        this.options = {};
    }

    if (!_.isBoolean(this.options.autoSync)) {
        this.options.autoSync = true;
    }

    this.reset();
};

HttpBackend.prototype.reset = function() {
    this.isInitialized = false;
    this.clear();
    this._init();
};

HttpBackend.prototype.clear = function() {
    this.responses = [];
    this.unSyncResponses = [];
    this.browser.get = this.browserGet;
    if (this.isInitialized) {
        this.browser.removeMockModule('httpBackendMock');
    }
};

HttpBackend.prototype.when = function(url) {
    return this._when('', url);
};

var generateWhenHelpers = function(methods) {
    methods.forEach(function(method) {
        HttpBackend.prototype['when' + method] = function(url) {
            return this._when(method, url);
        };
    })
};

generateWhenHelpers([
    'GET',
    'POST',
    'PUT',
    'DELETE',
    'HEAD',
    'PATCH',
    'JSONP'
]);

HttpBackend.prototype.init = function() {

    this.isInitialized = true;
    var jsBuilder = new JavascriptBuilder();
    jsBuilder.responses = this.responses;
    this.unSyncResponses = [];

    return this.browser.addMockModule('httpBackendMock', jsBuilder.toString(true));
};

HttpBackend.prototype.sync = function() {
    var jsBuilder = new JavascriptBuilder();
    jsBuilder.responses = this.unSyncResponses;
    this.browser.driver.executeScript(jsBuilder.toString());
    this.unSyncResponses = [];
};

HttpBackend.prototype._init = function() {
    var _this = this;
    this.browser.get = function(destination, opt_timeout) {
        if (!_this.isInitialized) {
            _this.init();
        }

        return _this.browserGet.call(browser, destination, opt_timeout);
    };
};

HttpBackend.prototype._when = function(method, url) {
    var request = new HttpRequest(method, url);
    var response = new HttpResponse(request);

    var responseIndex = _.findIndex(this.responses, function(response) {
        return response.request.uid == request.uid;
    });

    if (responseIndex) {
        this.responses.splice(responseIndex, 1, response);
    }

    if (this.options.autoSync && this.isInitialized) {
        var _this = this;
        response.onResponse = function() {
            _this.sync();
        };
    }

    this.responses.push(response);
    this.unSyncResponses.push(response);

    return response;
};

module.exports = HttpBackend;
<|endoftext|>


<commit_before>import * as Lab from "lab";
import { expect } from "code";
import createMapper from "../lib";

const { describe, it, before } = exports.lab = Lab.script();

let source;
let expected;
let mapper;
let actual;

describe("Default transformations functionality of the mapper", () => {

  describe("removing", () => {

    before(done => {
      mapper = createMapper();

      mapper
        .map("foo").removing(["id", "foo1"]).to("bar")
        .map(["foo", "h"]).removing(["id", "foo1"])
        .to("barMulti", (foo, h) => {
          return foo;
        })
        .map("fooArray").removing(["id"]).to("barArray")
        .map("h").or("howdy").removing(["id2"]).to("orTest")
        .map("howdy")
        .map("voila").to("voila", () => "hello");

      source = {
        "foo": {
          "id": "fooID",
          "bar": "tes",
          "foo1": "bar2"
        },
        "fooArray": [{
          "id": "fooID",
          "bar": "tes",
          "foo1": "bar2"
        }],
        "howdy": {
          "id": "allow",
          "id2": "allow2"
        },
        "voila": "a"
      };

      expected = {
        "bar": {
          "bar": "tes"
        },
        "barArray": [
          {
            "bar": "tes",
            "foo1": "bar2"
          }
        ],
        "barMulti": {
          "bar": "tes"
        },
        "howdy": {
          "id": "allow",
          "id2": "allow2"
        },
        "orTest": {
          "id": "allow"
        },
        "voila": "hello"
      };
      done();
    });

    describe("when an object is passed", () => {

      it("should return the desired result", done => {
        actual = mapper.execute(source);
        expect(actual).to.equal(expected);
        done();
      });
    });

    describe("when an array of object is passed", () => {

      it("should return the desired result", done => {
        actual = mapper.each([source]);
        expect(actual).to.equal([expected]);
        done();
      });
    });

    describe("when keys are not passed in as valid string", () => {

      before(done => {
        mapper = createMapper();
        mapper("foo").removing({}).to("bar");
        done();
      });
      it("should throw an error", done => {
        expect(() => mapper.execute(source)).to.throw("The keys should be either of type string or Array of string");
        done();
      });
    });

    describe("when keys are not passed in as valid array of strings", () => {

      before(done => {
        mapper = createMapper();
        mapper("foo").removing([{}]).to("bar");
        done();
      });
      it("should throw an error", done => {
        expect(() => mapper.execute(source)).to.throw("The keys should be either of type string or Array of string");
        done();
      });
    });
  });
});
<commit_msg>[feature/removing][adding test cases for array]<commit_after>import * as Lab from "lab";
import { expect } from "code";
import createMapper from "../lib";

const { describe, it, before } = exports.lab = Lab.script();

let source;
let expected;
let mapper;
let actual;

describe("Default transformations functionality of the mapper", () => {

  describe("removing", () => {

    before(done => {
      mapper = createMapper();

      mapper
        .map("foo").removing(["id", "foo1"]).to("bar")
        .map(["foo", "h"]).removing(["id", "foo1"])
        .to("barMulti", (foo, h) => {
          return foo;
        })
        .map("fooArray").removing(["id"]).to("barArray")
        .map("h").or("howdy").removing(["id2"]).to("orTest")
        .map("howdy")
        .map("voila").to("voila", () => "hello");

      source = {
        "foo": {
          "id": "fooID",
          "bar": "tes",
          "foo1": "bar2"
        },
        "fooArray": [{
          "id": "fooID",
          "bar": "tes",
          "foo1": "bar2"
        }],
        "howdy": {
          "id": "allow",
          "id2": "allow2"
        },
        "voila": "a"
      };

      expected = {
        "bar": {
          "bar": "tes"
        },
        "barArray": [
          {
            "bar": "tes",
            "foo1": "bar2"
          }
        ],
        "barMulti": {
          "bar": "tes"
        },
        "howdy": {
          "id": "allow",
          "id2": "allow2"
        },
        "orTest": {
          "id": "allow"
        },
        "voila": "hello"
      };
      done();
    });

    describe("when an object is passed", () => {

      it("should return the desired result", done => {
        actual = mapper.execute(source);
        expect(actual).to.equal(expected);
        done();
      });
    });

    describe("when an array of object is passed", () => {

      it("should return the desired result", done => {
        actual = mapper.each([source]);
        expect(actual).to.equal([expected]);
        done();
      });
    });

    describe("when keys are not passed in as valid string", () => {

      before(done => {
        mapper = createMapper();
        mapper("foo").removing({}).to("bar");
        done();
      });
      it("should throw an error", done => {
        expect(() => mapper.execute(source)).to.throw("The keys should be either of type string or Array of string");
        done();
      });
    });

    describe("when keys are not passed in as valid array of strings", () => {

      before(done => {
        mapper = createMapper();
        mapper("foo").removing([{}]).to("bar");
        done();
      });
      it("should throw an error", done => {
        expect(() => mapper.execute(source)).to.throw("The type of items in an array should be string");
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>import { tidyUpSequenceData } from 've-sequence-utils';

function jsonToBed(jsonSequence, options) {
  const cleanedData = tidyUpSequenceData(jsonSequence);
  const { name, features, size, description, circular } = cleanedData;
  options = options || {};

  let sequenceNameToMatchFasta = "";
  sequenceNameToMatchFasta += `${name || "Untitled Sequence"}|`;
  sequenceNameToMatchFasta += "|" + size;
  sequenceNameToMatchFasta += description ? "|" + description : "";
  sequenceNameToMatchFasta += "|" + (circular ?  "circular" : "linear");
  let outString = "";
  outString += `track name="${sequenceNameToMatchFasta}" description="${name} Annotations" itemRgb="On"\n`

  features.forEach(function(feat) {
    const {start, end, type, forward} = feat
    // chromStart is 0-based, chromEnd of the BED file format is not included in the feature
    outString+=`${sequenceNameToMatchFasta}\t${start}\t${end + 1}\t${type}\t1000\t${forward ? "+" : "-"}\t${start}\t${end + 1}\t65,105,225\n`
  })
  return outString;
}

export default jsonToBed;<commit_msg>adding strand to jsonToBed<commit_after>import { tidyUpSequenceData } from 've-sequence-utils';

function jsonToBed(jsonSequence, options) {
  const cleanedData = tidyUpSequenceData(jsonSequence);
  const { name, features, size, description, circular } = cleanedData;
  options = options || {};

  let sequenceNameToMatchFasta = "";
  sequenceNameToMatchFasta += `${name || "Untitled Sequence"}|`;
  sequenceNameToMatchFasta += "|" + size;
  sequenceNameToMatchFasta += description ? "|" + description : "";
  sequenceNameToMatchFasta += "|" + (circular ?  "circular" : "linear");
  let outString = "";
  outString += `track name="${sequenceNameToMatchFasta}" description="${name} Annotations" itemRgb="On"\n`

  features.forEach(function(feat) {
    const {start, end, type, forward, strand} = feat
    // chromStart is 0-based, chromEnd of the BED file format is not included in the feature
    outString+=`${sequenceNameToMatchFasta}\t${start}\t${end + 1}\t${type}\t1000\t${forward || strand === 1 ? "+" : "-"}\t${start}\t${end + 1}\t65,105,225\n`
  })
  return outString;
}

export default jsonToBed;<|endoftext|>


<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod
 * http://www.melonjs.org
 *
 */
(function () {
    // generate a default image for the particles
    var pixel = (function () {
        var canvas = me.video.createCanvas(1, 1);
        var context = canvas.getContext("2d");
        context.fillStyle = "#fff";
        context.fillRect(0, 0, 1, 1);
        return canvas;
    })();

    /**
     * me.ParticleEmitterSettings contains the default settings for me.ParticleEmitter.

     *
     * @protected
     * @class
     * @memberOf me
     * @see me.ParticleEmitter
     */
    me.ParticleEmitterSettings = {
        /**
         * Width of the particle spawn area.

         * @public
         * @type Number
         * @name width
         * @memberOf me.ParticleEmitterSettings
         * @default 0
         */
        width : 0,

        /**
         * Height of the particle spawn area.

         * @public
         * @type Number
         * @name height
         * @memberOf me.ParticleEmitterSettings
         * @default 0
         */
        height : 0,

        /**
         * Image used for particles.

         * @public
         * @type CanvasImageSource
         * @name image
         * @memberOf me.ParticleEmitterSettings
         * @default 1x1 white pixel
         * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvasimagesource
         */
        image : pixel,

        /**
         * Total number of particles in the emitter.

         * @public
         * @type Number
         * @name totalParticles
         * @default 50
         * @memberOf me.ParticleEmitterSettings
         */
        totalParticles : 50,

        /**
         * Start angle for particle launch in Radians.

         * @public
         * @type Number
         * @name angle
         * @default Math.PI / 2
         * @memberOf me.ParticleEmitterSettings
         */
        angle : Math.PI / 2,

        /**
         * Variation in the start angle for particle launch in Radians.

         * @public
         * @type Number
         * @name angleVariation
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        angleVariation : 0,

        /**
         * Minimum time each particle lives once it is emitted in ms.

         * @public
         * @type Number
         * @name minLife
         * @default 1000
         * @memberOf me.ParticleEmitterSettings
         */
        minLife : 1000,

        /**
         * Maximum time each particle lives once it is emitted in ms.

         * @public
         * @type Number
         * @name maxLife
         * @default 3000
         * @memberOf me.ParticleEmitterSettings
         */
        maxLife : 3000,

        /**
         * Start speed of particles.

         * @public
         * @type Number
         * @name speed
         * @default 2
         * @memberOf me.ParticleEmitterSettings
         */
        speed : 2,

        /**
         * Variation in the start speed of particles.

         * @public
         * @type Number
         * @name speedVariation
         * @default 1
         * @memberOf me.ParticleEmitterSettings
         */
        speedVariation : 1,

        /**
         * Minimum start rotation for particles sprites in Radians.

         * @public
         * @type Number
         * @name minRotation
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        minRotation : 0,

        /**
         * Maximum start rotation for particles sprites in Radians.

         * @public
         * @type Number
         * @name maxRotation
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        maxRotation : 0,

        /**
         * Minimum start scale ratio for particles (1 = no scaling).

         * @public
         * @type Number
         * @name minStartScale
         * @default 1
         * @memberOf me.ParticleEmitterSettings
         */
        minStartScale : 1,

        /**
         * Maximum start scale ratio for particles (1 = no scaling).

         * @public
         * @type Number
         * @name maxStartScale
         * @default 1
         * @memberOf me.ParticleEmitterSettings
         */
        maxStartScale : 1,

        /**
         * Minimum end scale ratio for particles.

         * @public
         * @type Number
         * @name minEndScale
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        minEndScale : 0,

        /**
         * Maximum end scale ratio for particles.

         * @public
         * @type Number
         * @name maxEndScale
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        maxEndScale : 0,

        /**
         * Vertical force (Gravity) for each particle.

         * @public
         * @type Number
         * @name gravity
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         * @see me.sys.gravity
         */
        gravity : 0,

        /**
         * Horizontal force (like a Wind) for each particle.

         * @public
         * @type Number
         * @name wind
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        wind : 0,

        /**
         * Update the rotation of particle in accordance the particle trajectory.

         * The particle sprite should aim at zero angle (draw from left to right).

         * Override the particle minRotation and maxRotation.

         * @public
         * @type Boolean
         * @name followTrajectory
         * @default false
         * @memberOf me.ParticleEmitterSettings
         */
        followTrajectory : false,

        /**
         * Enable the Texture Additive by canvas composite operation (lighter).

         * WARNING: Composite Operation may decreases performance!.

         * @public
         * @type Boolean
         * @name textureAdditive
         * @default false
         * @memberOf me.ParticleEmitterSettings
         */
        textureAdditive : false,

        /**
         * Update particles only in the viewport, remove it when out of viewport.

         * @public
         * @type Boolean
         * @name onlyInViewport
         * @default true
         * @memberOf me.ParticleEmitterSettings
         */
        onlyInViewport : true,

        /**
         * Render particles in screen space.

         * @public
         * @type Boolean
         * @name floating
         * @default false
         * @memberOf me.ParticleEmitterSettings
         */
        floating : false,

        /**
         * Maximum number of particles launched each time in this emitter (used only if emitter is Stream).

         * @public
         * @type Number
         * @name maxParticles
         * @default 10
         * @memberOf me.ParticleEmitterSettings
         */
        maxParticles : 10,

        /**
         * How often a particle is emitted in ms (used only if emitter is Stream).

         * Necessary that value is greater than zero.

         * @public
         * @type Number
         * @name frequency
         * @default 100
         * @memberOf me.ParticleEmitterSettings
         */
        frequency : 100,

        /**
         * Duration that the emitter releases particles in ms (used only if emitter is Stream).

         * After this period, the emitter stop the launch of particles.

         * @public
         * @type Number
         * @name duration
         * @default Infinity
         * @memberOf me.ParticleEmitterSettings
         */
        duration : Infinity,

        /**
         * Skip n frames after updating the particle system once.

         * This can be used to reduce the performance impact of emitters with many particles.

         * @public
         * @type Number
         * @name framesToSkip
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        framesToSkip : 0
    };

    /**
     * Particle Emitter Object.
     * @class
     * @extends Rect
     * @memberOf me
     * @constructor
     * @param {Number} x x-position of the particle emitter
     * @param {Number} y y-position of the particle emitter
     * @param {object} settings An object containing the settings for the particle emitter. See {@link me.ParticleEmitterSettings}
     * @example
     *
     * // Create a basic emitter at position 100, 100
     * var emitter = new me.ParticleEmitter(100, 100);
     *
     * // Adjust the emitter properties
     * emitter.totalParticles = 200;
     * emitter.minLife = 1000;
     * emitter.maxLife = 3000;
     * emitter.z = 10;
     *
     * // Add the emitter to the game world
     * me.game.world.addChild(emitter);
     * me.game.world.addChild(emitter.container);
     *
     * // Launch all particles one time and stop, like a explosion
     * emitter.burstParticles();
     *
     * // Launch constantly the particles, like a fountain
     * emitter.streamParticles();
     *
     * // At the end, remove emitter from the game world
     * // call this in onDestroyEvent function
     * me.game.world.removeChild(emitter);
     *
     */
    me.ParticleEmitter = me.Rect.extend(
    /** @scope me.ParticleEmitter.prototype */
    {
        /**
         * @ignore
         */
        init: function (x, y, settings) {
            // Emitter is Stream, launch particles constantly
            /** @ignore */
            this._stream = false;

            // Frequency timer (in ms) for emitter launch new particles
            // used only in stream emitter
            /** @ignore */
            this._frequencyTimer = 0;

            // Time of live (in ms) for emitter launch new particles
            // used only in stream emitter
            /** @ignore */
            this._durationTimer = 0;

            // Emitter is emitting particles
            /** @ignore */
            this._enabled = false;
            // Emitter will always update
            this.isRenderable = false;
            // call the super constructor
            this._super(
                me.Rect,
                "init",
                [x, y,
                Infinity,
                Infinity]
            );

            // don't sort the particles by z-index
            this.autoSort = false;

            this.container = new me.ParticleContainer(this);

            /**
             * @ignore
             */
            Object.defineProperty(this.pos, "z", {
                get : (function () { return this.container.pos.z; }).bind(this),
                set : (function (value) { this.container.pos.z = value; }).bind(this),
                enumerable : true,
                configurable : true
            });

            /**
             * Floating property for particles, value is forwarded to the particle container

             * @type Boolean
             * @name floating
             * @memberOf me.ParticleEmitter
             */
            Object.defineProperty(this, "floating", {
                get : function () { return this.container.floating; },
                set : function (value) { this.container.floating = value; },
                enumerable : true,
                configurable : true
            });

            // Reset the emitter to defaults
            this.reset(settings);
        },

        onActivateEvent: function() {
            // keep a cache reference to the ancestor
            this._ancestor = this.ancestor;
            this.ancestor.addChild(this.container);
            this.container.pos.z = this.pos.z;
            if (!this.ancestor.autoSort) {
                this.ancestor.sort();
            }
        },

        onDeactivateEvent: function() {
            this._ancestor.removeChild(this.container);
            this.ancestor = undefined;
        },

        destroy: function () {
            this.reset();
        },

        /**
         * returns a random point inside the bounds for this emitter
         * @name getRandomPoint
         * @memberOf me.ParticleEmitter
         * @function
         * @return {me.Vector2d} new vector
         */
        getRandomPoint: function () {
            var vector = this.pos.clone();
            vector.x += (0).randomFloat(this.width);
            vector.y += (0).randomFloat(this.height);
            return vector;
        },

        /**
         * Reset the emitter with default values.

         * @function
         * @param {Object} settings [optional] object with emitter settings. See {@link me.ParticleEmitterSettings}
         * @name reset
         * @memberOf me.ParticleEmitter
         */
        reset: function (settings) {
            // check if settings exists and create a dummy object if necessary
            settings = settings || {};
            var defaults = me.ParticleEmitterSettings;

            var width = (typeof settings.width === "number") ? settings.width : defaults.width;
            var height = (typeof settings.height === "number") ? settings.height : defaults.height;
            this.resize(width, height);

            Object.assign(this, defaults, settings);

            // reset particle container values
            this.container.destroy();
        },

        // Add count particles in the game world
        /** @ignore */
        addParticles: function (count) {
            for (var i = 0; i < ~~count; i++) {
                // Add particle to the container
                var particle = me.pool.pull("me.Particle", this);
                this.container.addChild(particle);
            }
        },

        /**
         * Emitter is of type stream and is launching particles

         * @function
         * @returns {Boolean} Emitter is Stream and is launching particles
         * @name isRunning
         * @memberOf me.ParticleEmitter
         */
        isRunning: function () {
            return this._enabled && this._stream;
        },

        /**
         * Launch particles from emitter constantly

         * Particles example: Fountains
         * @param {Number} duration [optional] time that the emitter releases particles in ms
         * @function
         * @name streamParticles
         * @memberOf me.ParticleEmitter
         */
        streamParticles: function (duration) {
            this._enabled = true;
            this._stream = true;
            this.frequency = Math.max(this.frequency, 1);
            this._durationTimer = (typeof duration === "number") ? duration : this.duration;
        },

        /**
         * Stop the emitter from generating new particles (used only if emitter is Stream)

         * @function
         * @name stopStream
         * @memberOf me.ParticleEmitter
         */
        stopStream: function () {
            this._enabled = false;
        },

        /**
         * Launch all particles from emitter and stop

         * Particles example: Explosions

         * @param {Number} total [optional] number of particles to launch
         * @function
         * @name burstParticles
         * @memberOf me.ParticleEmitter
         */
        burstParticles: function (total) {
            this._enabled = true;
            this._stream = false;
            this.addParticles((typeof total === "number") ? total : this.totalParticles);
            this._enabled = false;
        },

        /**
         * @ignore
         */
        update: function (dt) {
            // Launch new particles, if emitter is Stream
            if ((this._enabled) && (this._stream)) {
                // Check if the emitter has duration set
                if (this._durationTimer !== Infinity) {
                    this._durationTimer -= dt;

                    if (this._durationTimer <= 0) {
                        this.stopStream();
                        return false;
                    }
                }

                // Increase the emitter launcher timer
                this._frequencyTimer += dt;

                // Check for new particles launch
                var particlesCount = this.container.children.length;
                if ((particlesCount < this.totalParticles) && (this._frequencyTimer >= this.frequency)) {
                    if ((particlesCount + this.maxParticles) <= this.totalParticles) {
                        this.addParticles(this.maxParticles);
                    }
                    else {
                        this.addParticles(this.totalParticles - particlesCount);
                    }

                    this._frequencyTimer = 0;
                }
            }
            return true;
        }
    });
})();
<commit_msg>Revert "fix a crash/exception when resetting/reloading a level while using particles"<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod
 * http://www.melonjs.org
 *
 */
(function () {
    // generate a default image for the particles
    var pixel = (function () {
        var canvas = me.video.createCanvas(1, 1);
        var context = canvas.getContext("2d");
        context.fillStyle = "#fff";
        context.fillRect(0, 0, 1, 1);
        return canvas;
    })();

    /**
     * me.ParticleEmitterSettings contains the default settings for me.ParticleEmitter.

     *
     * @protected
     * @class
     * @memberOf me
     * @see me.ParticleEmitter
     */
    me.ParticleEmitterSettings = {
        /**
         * Width of the particle spawn area.

         * @public
         * @type Number
         * @name width
         * @memberOf me.ParticleEmitterSettings
         * @default 0
         */
        width : 0,

        /**
         * Height of the particle spawn area.

         * @public
         * @type Number
         * @name height
         * @memberOf me.ParticleEmitterSettings
         * @default 0
         */
        height : 0,

        /**
         * Image used for particles.

         * @public
         * @type CanvasImageSource
         * @name image
         * @memberOf me.ParticleEmitterSettings
         * @default 1x1 white pixel
         * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvasimagesource
         */
        image : pixel,

        /**
         * Total number of particles in the emitter.

         * @public
         * @type Number
         * @name totalParticles
         * @default 50
         * @memberOf me.ParticleEmitterSettings
         */
        totalParticles : 50,

        /**
         * Start angle for particle launch in Radians.

         * @public
         * @type Number
         * @name angle
         * @default Math.PI / 2
         * @memberOf me.ParticleEmitterSettings
         */
        angle : Math.PI / 2,

        /**
         * Variation in the start angle for particle launch in Radians.

         * @public
         * @type Number
         * @name angleVariation
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        angleVariation : 0,

        /**
         * Minimum time each particle lives once it is emitted in ms.

         * @public
         * @type Number
         * @name minLife
         * @default 1000
         * @memberOf me.ParticleEmitterSettings
         */
        minLife : 1000,

        /**
         * Maximum time each particle lives once it is emitted in ms.

         * @public
         * @type Number
         * @name maxLife
         * @default 3000
         * @memberOf me.ParticleEmitterSettings
         */
        maxLife : 3000,

        /**
         * Start speed of particles.

         * @public
         * @type Number
         * @name speed
         * @default 2
         * @memberOf me.ParticleEmitterSettings
         */
        speed : 2,

        /**
         * Variation in the start speed of particles.

         * @public
         * @type Number
         * @name speedVariation
         * @default 1
         * @memberOf me.ParticleEmitterSettings
         */
        speedVariation : 1,

        /**
         * Minimum start rotation for particles sprites in Radians.

         * @public
         * @type Number
         * @name minRotation
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        minRotation : 0,

        /**
         * Maximum start rotation for particles sprites in Radians.

         * @public
         * @type Number
         * @name maxRotation
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        maxRotation : 0,

        /**
         * Minimum start scale ratio for particles (1 = no scaling).

         * @public
         * @type Number
         * @name minStartScale
         * @default 1
         * @memberOf me.ParticleEmitterSettings
         */
        minStartScale : 1,

        /**
         * Maximum start scale ratio for particles (1 = no scaling).

         * @public
         * @type Number
         * @name maxStartScale
         * @default 1
         * @memberOf me.ParticleEmitterSettings
         */
        maxStartScale : 1,

        /**
         * Minimum end scale ratio for particles.

         * @public
         * @type Number
         * @name minEndScale
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        minEndScale : 0,

        /**
         * Maximum end scale ratio for particles.

         * @public
         * @type Number
         * @name maxEndScale
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        maxEndScale : 0,

        /**
         * Vertical force (Gravity) for each particle.

         * @public
         * @type Number
         * @name gravity
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         * @see me.sys.gravity
         */
        gravity : 0,

        /**
         * Horizontal force (like a Wind) for each particle.

         * @public
         * @type Number
         * @name wind
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        wind : 0,

        /**
         * Update the rotation of particle in accordance the particle trajectory.

         * The particle sprite should aim at zero angle (draw from left to right).

         * Override the particle minRotation and maxRotation.

         * @public
         * @type Boolean
         * @name followTrajectory
         * @default false
         * @memberOf me.ParticleEmitterSettings
         */
        followTrajectory : false,

        /**
         * Enable the Texture Additive by canvas composite operation (lighter).

         * WARNING: Composite Operation may decreases performance!.

         * @public
         * @type Boolean
         * @name textureAdditive
         * @default false
         * @memberOf me.ParticleEmitterSettings
         */
        textureAdditive : false,

        /**
         * Update particles only in the viewport, remove it when out of viewport.

         * @public
         * @type Boolean
         * @name onlyInViewport
         * @default true
         * @memberOf me.ParticleEmitterSettings
         */
        onlyInViewport : true,

        /**
         * Render particles in screen space.

         * @public
         * @type Boolean
         * @name floating
         * @default false
         * @memberOf me.ParticleEmitterSettings
         */
        floating : false,

        /**
         * Maximum number of particles launched each time in this emitter (used only if emitter is Stream).

         * @public
         * @type Number
         * @name maxParticles
         * @default 10
         * @memberOf me.ParticleEmitterSettings
         */
        maxParticles : 10,

        /**
         * How often a particle is emitted in ms (used only if emitter is Stream).

         * Necessary that value is greater than zero.

         * @public
         * @type Number
         * @name frequency
         * @default 100
         * @memberOf me.ParticleEmitterSettings
         */
        frequency : 100,

        /**
         * Duration that the emitter releases particles in ms (used only if emitter is Stream).

         * After this period, the emitter stop the launch of particles.

         * @public
         * @type Number
         * @name duration
         * @default Infinity
         * @memberOf me.ParticleEmitterSettings
         */
        duration : Infinity,

        /**
         * Skip n frames after updating the particle system once.

         * This can be used to reduce the performance impact of emitters with many particles.

         * @public
         * @type Number
         * @name framesToSkip
         * @default 0
         * @memberOf me.ParticleEmitterSettings
         */
        framesToSkip : 0
    };

    /**
     * Particle Emitter Object.
     * @class
     * @extends Rect
     * @memberOf me
     * @constructor
     * @param {Number} x x-position of the particle emitter
     * @param {Number} y y-position of the particle emitter
     * @param {object} settings An object containing the settings for the particle emitter. See {@link me.ParticleEmitterSettings}
     * @example
     *
     * // Create a basic emitter at position 100, 100
     * var emitter = new me.ParticleEmitter(100, 100);
     *
     * // Adjust the emitter properties
     * emitter.totalParticles = 200;
     * emitter.minLife = 1000;
     * emitter.maxLife = 3000;
     * emitter.z = 10;
     *
     * // Add the emitter to the game world
     * me.game.world.addChild(emitter);
     * me.game.world.addChild(emitter.container);
     *
     * // Launch all particles one time and stop, like a explosion
     * emitter.burstParticles();
     *
     * // Launch constantly the particles, like a fountain
     * emitter.streamParticles();
     *
     * // At the end, remove emitter from the game world
     * // call this in onDestroyEvent function
     * me.game.world.removeChild(emitter);
     *
     */
    me.ParticleEmitter = me.Rect.extend(
    /** @scope me.ParticleEmitter.prototype */
    {
        /**
         * @ignore
         */
        init: function (x, y, settings) {
            // Emitter is Stream, launch particles constantly
            /** @ignore */
            this._stream = false;

            // Frequency timer (in ms) for emitter launch new particles
            // used only in stream emitter
            /** @ignore */
            this._frequencyTimer = 0;

            // Time of live (in ms) for emitter launch new particles
            // used only in stream emitter
            /** @ignore */
            this._durationTimer = 0;

            // Emitter is emitting particles
            /** @ignore */
            this._enabled = false;
            // Emitter will always update
            this.isRenderable = false;
            // call the super constructor
            this._super(
                me.Rect,
                "init",
                [x, y,
                Infinity,
                Infinity]
            );

            // don't sort the particles by z-index
            this.autoSort = false;

            this.container = new me.ParticleContainer(this);

            /**
             * @ignore
             */
            Object.defineProperty(this.pos, "z", {
                get : (function () { return this.container.pos.z; }).bind(this),
                set : (function (value) { this.container.pos.z = value; }).bind(this),
                enumerable : true,
                configurable : true
            });

            /**
             * Floating property for particles, value is forwarded to the particle container

             * @type Boolean
             * @name floating
             * @memberOf me.ParticleEmitter
             */
            Object.defineProperty(this, "floating", {
                get : function () { return this.container.floating; },
                set : function (value) { this.container.floating = value; },
                enumerable : true,
                configurable : true
            });

            // Reset the emitter to defaults
            this.reset(settings);
        },

        onActivateEvent: function() {
            this.ancestor.addChild(this.container);
            this.container.pos.z = this.pos.z;
            if (!this.ancestor.autoSort) {
                this.ancestor.sort();
            }
        },

        onDeactivateEvent: function() {
            this.container.ancestor.removeChild(this.container);
        },

        destroy: function () {
            this.reset();
        },

        /**
         * returns a random point inside the bounds for this emitter
         * @name getRandomPoint
         * @memberOf me.ParticleEmitter
         * @function
         * @return {me.Vector2d} new vector
         */
        getRandomPoint: function () {
            var vector = this.pos.clone();
            vector.x += (0).randomFloat(this.width);
            vector.y += (0).randomFloat(this.height);
            return vector;
        },

        /**
         * Reset the emitter with default values.

         * @function
         * @param {Object} settings [optional] object with emitter settings. See {@link me.ParticleEmitterSettings}
         * @name reset
         * @memberOf me.ParticleEmitter
         */
        reset: function (settings) {
            // check if settings exists and create a dummy object if necessary
            settings = settings || {};
            var defaults = me.ParticleEmitterSettings;

            var width = (typeof settings.width === "number") ? settings.width : defaults.width;
            var height = (typeof settings.height === "number") ? settings.height : defaults.height;
            this.resize(width, height);

            Object.assign(this, defaults, settings);

            // reset particle container values
            this.container.destroy();
        },

        // Add count particles in the game world
        /** @ignore */
        addParticles: function (count) {
            for (var i = 0; i < ~~count; i++) {
                // Add particle to the container
                var particle = me.pool.pull("me.Particle", this);
                this.container.addChild(particle);
            }
        },

        /**
         * Emitter is of type stream and is launching particles

         * @function
         * @returns {Boolean} Emitter is Stream and is launching particles
         * @name isRunning
         * @memberOf me.ParticleEmitter
         */
        isRunning: function () {
            return this._enabled && this._stream;
        },

        /**
         * Launch particles from emitter constantly

         * Particles example: Fountains
         * @param {Number} duration [optional] time that the emitter releases particles in ms
         * @function
         * @name streamParticles
         * @memberOf me.ParticleEmitter
         */
        streamParticles: function (duration) {
            this._enabled = true;
            this._stream = true;
            this.frequency = Math.max(this.frequency, 1);
            this._durationTimer = (typeof duration === "number") ? duration : this.duration;
        },

        /**
         * Stop the emitter from generating new particles (used only if emitter is Stream)

         * @function
         * @name stopStream
         * @memberOf me.ParticleEmitter
         */
        stopStream: function () {
            this._enabled = false;
        },

        /**
         * Launch all particles from emitter and stop

         * Particles example: Explosions

         * @param {Number} total [optional] number of particles to launch
         * @function
         * @name burstParticles
         * @memberOf me.ParticleEmitter
         */
        burstParticles: function (total) {
            this._enabled = true;
            this._stream = false;
            this.addParticles((typeof total === "number") ? total : this.totalParticles);
            this._enabled = false;
        },

        /**
         * @ignore
         */
        update: function (dt) {
            // Launch new particles, if emitter is Stream
            if ((this._enabled) && (this._stream)) {
                // Check if the emitter has duration set
                if (this._durationTimer !== Infinity) {
                    this._durationTimer -= dt;

                    if (this._durationTimer <= 0) {
                        this.stopStream();
                        return false;
                    }
                }

                // Increase the emitter launcher timer
                this._frequencyTimer += dt;

                // Check for new particles launch
                var particlesCount = this.container.children.length;
                if ((particlesCount < this.totalParticles) && (this._frequencyTimer >= this.frequency)) {
                    if ((particlesCount + this.maxParticles) <= this.totalParticles) {
                        this.addParticles(this.maxParticles);
                    }
                    else {
                        this.addParticles(this.totalParticles - particlesCount);
                    }

                    this._frequencyTimer = 0;
                }
            }
            return true;
        }
    });
})();
<|endoftext|>