<commit_before>var React = require('react'),
  _ = require('lodash'),
  api = require('./../../steem/api'),
  Loading = require('./../loading'),
  AddPost = require('./../add-post'),
  Post = require('./../post/post');

module.exports = class componentName extends React.Component {
  constructor(props) {
    super(props);
    this.state = { content: [], isLoading: true };
    this._customListener = [];
  }
  componentWillMount() {
    this.getDiscussions();
  }

  getDiscussions = (tag, limit, start_author, start_permlink) => {
    limit = limit || 20;
    let type = {
      "feed": 'getDiscussionsByFeed',
      'trending': 'getDiscussionsByTrending',
      'hot': 'getDiscussionsByHot',
      'cashout': 'getDiscussionsByCashout',
      'created': 'getDiscussionsByCreated',
      'active': 'getDiscussionsByActive'
    }
    let currentType = type[this.props.path] || type['trending'];

    let feed = this.props.path.match(/(@)(\w+)(\/feed)/);
    if (feed && feed.length && feed[2]) {
      currentType = type['feed'];
      tag = feed[2] //username
    }

    api[currentType]({ tag, limit, start_author, start_permlink }, (err, result) => {
      err && console.error('error while ' + currentType, JSON.stringify(err));
      let lastContent = _.last(this.state.content);
      let lastResult = _.last(result);
      let content;
      if (lastResult && lastContent && lastResult.id == lastContent.id) {
        content = this.state.content;
      } else {
        content = _.concat(this.state.content, result);
      }
      this.setState({ content, isLoading: false });
    });
  }

  componentDidMount() {
    this.addScrollListener(this.refs.feedContainer);
  }

  addScrollListener(domNode) {
    this.addCustomListener('scroll', domNode, this.scrollListener);
    this.addCustomListener('resize', domNode, this.scrollListener);
  }

  addCustomListener(listenerType, target, listener) {
    if (target) {
      this._customListener.push({ listenerType, target, listener });
      target.addEventListener(listenerType, listener);
    }
  }

  componentWillUnmount() {
    _.each(this._customListener, ({listenerType, target, listener}) => {
      target.removeEventListener(listenerType, listener);
    })
  }

  loadMore = () => {
    let content = this.state.content;
    if (content && content.length) {
      let lastContent = content[content.length - 1];
      this.getDiscussions("", 10, lastContent.author, lastContent.permlink);
    }
  }

  scrollListener = (event) => {
    var el = event.target;
    let {scrollTop, scrollHeight, offsetHeight} = el;
    let scrollRemain = scrollHeight - scrollTop - offsetHeight;
    let scrollThreshold = window.innerHeight - 200;
    if (scrollHeight && (scrollRemain < scrollThreshold) && !this.state.isLoading) {
      this.setState({ isLoading: true }, () => {
        this.loadMore();
      });
    }
  }

  render() {
    let {content, isLoading} = this.state;
    return (

          <AddPost />
          {content.map(function (entry, key) {
            return <Post key={key} entry={entry} replies={this.props.replies} />;
          }.bind(this)) }
          {isLoading && <Loading />}

    );
  }
}<commit_msg>Fix dup issue<commit_after>var React = require('react'),
  _ = require('lodash'),
  api = require('./../../steem/api'),
  Loading = require('./../loading'),
  AddPost = require('./../add-post'),
  Post = require('./../post/post');

module.exports = class componentName extends React.Component {
  constructor(props) {
    super(props);
    this.state = { content: [], isLoading: true };
    this._customListener = [];
  }
  componentWillMount() {
    this.getDiscussions();
  }

  getDiscussions = (tag, limit, start_author, start_permlink) => {
    limit = limit || 20;
    let type = {
      "feed": 'getDiscussionsByFeed',
      'trending': 'getDiscussionsByTrending',
      'hot': 'getDiscussionsByHot',
      'cashout': 'getDiscussionsByCashout',
      'created': 'getDiscussionsByCreated',
      'active': 'getDiscussionsByActive'
    }
    let currentType = type[this.props.path] || type['trending'];

    let feed = this.props.path.match(/(@)(\w+)(\/feed)/);
    if (feed && feed.length && feed[2]) {
      currentType = type['feed'];
      tag = feed[2] //username
    }

    api[currentType]({ tag, limit, start_author, start_permlink }, (err, result) => {
      err && console.error('error while ' + currentType, JSON.stringify(err));
      let lastContent = _.last(this.state.content);
      let lastResult = _.last(result);
      let content;
      if (lastResult && lastContent && lastResult.id == lastContent.id) {
        content = this.state.content;
      } else {
        content = _.concat(this.state.content, _.slice(result, 1)); //Since first is same as last
      }
      this.setState({ content, isLoading: false });
    });
  }

  componentDidMount() {
    this.addScrollListener(this.refs.feedContainer);
  }

  addScrollListener(domNode) {
    this.addCustomListener('scroll', domNode, this.scrollListener);
    this.addCustomListener('resize', domNode, this.scrollListener);
  }

  addCustomListener(listenerType, target, listener) {
    if (target) {
      this._customListener.push({ listenerType, target, listener });
      target.addEventListener(listenerType, listener);
    }
  }

  componentWillUnmount() {
    _.each(this._customListener, ({listenerType, target, listener}) => {
      target.removeEventListener(listenerType, listener);
    })
  }

  loadMore = () => {
    let content = this.state.content;
    if (content && content.length) {
      let lastContent = content[content.length - 1];
      this.getDiscussions("", 10, lastContent.author, lastContent.permlink);
    }
  }

  scrollListener = (event) => {
    var el = event.target;
    let {scrollTop, scrollHeight, offsetHeight} = el;
    let scrollRemain = scrollHeight - scrollTop - offsetHeight;
    let scrollThreshold = window.innerHeight - 200;
    if (scrollHeight && (scrollRemain < scrollThreshold) && !this.state.isLoading) {
      this.setState({ isLoading: true }, () => {
        this.loadMore();
      });
    }
  }

  render() {
    let {content, isLoading} = this.state;
    return (

          <AddPost />
          {content.map(function (entry, key) {
            return <Post key={key} entry={entry} replies={this.props.replies} />;
          }.bind(this)) }
          {isLoading && <Loading />}

    );
  }
}<|endoftext|>


<commit_before>import {clipboard, nativeImage, remote, shell} from 'electron';
import {truncateString, matchesWord} from './utility';

const {Menu, MenuItem} = remote;

let d = require('debug')('electron-spellchecker:context-menu-builder');

const contextMenuStringTable = {
  copyMail: () => `Copy Email Address`,
  copyLinkUrl: () => `Copy Link`,
  openLinkUrl: () => `Open Link`,
  copyImageUrl: () => `Copy Image URL`,
  copyImage: () => `Copy Image`,
  addToDictionary: () => `Add to Dictionary`,
  lookUpDefinition: ({word}) => `Look Up "${word}"`,
  searchGoogle: () => `Search with Google`,
  cut: () => `Cut`,
  copy: () => `Copy`,
  paste: () => `Paste`,
  inspectElement: () => `Inspect Element`,
};

/**
 * ContextMenuBuilder creates context menus based on the content clicked - this
 * information is derived from
 * https://github.com/electron/electron/blob/master/docs/api/web-contents.md#event-context-menu,
 * which we use to generate the menu. We also use the spell-check information to
 * generate suggestions.
 */
export default class ContextMenuBuilder {
  /**
   * Creates an instance of ContextMenuBuilder
   *
   * @param  {SpellCheckHandler} spellCheckHandler  The spell checker to generate
   *                                                recommendations for.
   * @param  {BrowserWindow|WebView} windowOrWebView  The hosting window/WebView
   * @param  {Boolean} debugMode    If true, display the "Inspect Element" menu item.
   * @param  {function} processMenu If passed, this method will be passed the menu to change
   *                                it prior to display. Signature: (menu, info) => menu
   */
  constructor(spellCheckHandler, windowOrWebView=null, debugMode=false, processMenu=(m) => m) {
    this.spellCheckHandler = spellCheckHandler;
    this.debugMode = debugMode;
    this.processMenu = processMenu;
    this.menu = null;
    this.stringTable = Object.assign({}, contextMenuStringTable);

    windowOrWebView = windowOrWebView || remote.getCurrentWebContents();

    let ctorName = Object.getPrototypeOf(windowOrWebView).constructor.name;
    if (ctorName === 'WebContents') {
      this.getWebContents = () => windowOrWebView;
    } else {
      // NB: We do this because at the time a WebView is created, it doesn't
      // have a WebContents, we need to defer the call to getWebContents
      this.getWebContents = 'webContents' in windowOrWebView ?
        () => windowOrWebView.webContents :
        () => windowOrWebView.getWebContents();
    }
  }

  /**
   * Override the default logger for this class. You probably want to use
   * {{setGlobalLogger}} instead
   *
   * @param {Function} fn   The function which will operate like console.log
   */
  static setLogger(fn) {
    d = fn;
  }

  /**
   * Specify alternate string formatter for each context menu.
   * String table consist of string formatter as function instead per each context menu item,
   * allows to change string in runtime. All formatters are simply typeof () => string, except
   * lookUpDefinition provides word, ({word}) => string.
   *
   * @param {Object} stringTable The object contains string foramtter function for context menu.
   * It is allowed to specify only certain menu string as necessary, which will makes other string
   * fall backs to default.
   *
   */
  setAlternateStringFormatter(stringTable) {
    this.contextMenuStringTable = Object.assign(this.stringTable, stringTable);
  }

  /**
   * Shows a popup menu given the information returned from the context-menu
   * event. This is probably the only method you need to call in this class.
   *
   * @param  {Object} contextInfo   The object returned from the 'context-menu'
   *                                Electron event.
   *
   * @return {Promise}              Completion
   */
  async showPopupMenu(contextInfo) {
    let menu = await this.buildMenuForElement(contextInfo);

    // Opening a menu blocks the renderer process, which is definitely not
    // suitable for running tests
    if (!menu) return;
    menu.popup(remote.getCurrentWindow());
  }

  /**
   * Builds a context menu specific to the given info that _would_ be shown
   * immediately by {{showPopupMenu}}. Use this to add your own menu items to
   * the list but use most of the default behavior.
   *
   * @return {Promise<Menu>}      The newly created `Menu`
   */
  async buildMenuForElement(info) {
    //d(`Got context menu event with args: ${JSON.stringify(info)}`);

    if (info.linkURL && info.linkURL.length > 0) {
      return this.buildMenuForLink(info);
    }

    if (info.hasImageContents && info.srcURL && info.srcURL.length > 1) {
      return this.buildMenuForImage(info);
    }

    if (info.isEditable || (info.inputFieldType && info.inputFieldType !== 'none')) {
      return await this.buildMenuForTextInput(info);
    }

    return this.buildMenuForText(info);
  }

  /**
   * Builds a menu applicable to a text input field.
   *
   * @return {Menu}  The `Menu`
   */
  async buildMenuForTextInput(menuInfo) {
    let menu = new Menu();

    await this.addSpellingItems(menu, menuInfo);
    this.addSearchItems(menu, menuInfo);

    this.addCut(menu, menuInfo);
    this.addCopy(menu, menuInfo);
    this.addPaste(menu, menuInfo);
    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu);

    return menu;
  }

  /**
   * Builds a menu applicable to a link element.
   *
   * @return {Menu}  The `Menu`
   */
  buildMenuForLink(menuInfo) {
    let menu = new Menu();
    let isEmailAddress = menuInfo.linkURL.startsWith('mailto:');

    let copyLink = new MenuItem({
      label: isEmailAddress ? this.stringTable.copyMail() : this.stringTable.copyLinkUrl(),
      click: () => {
        // Omit the mailto: portion of the link; we just want the address
        clipboard.writeText(isEmailAddress ?
          menuInfo.linkText : menuInfo.linkURL);
      }
    });

    let openLink = new MenuItem({
      label: this.stringTable.openLinkUrl(),
      click: () => {
        //d(`Navigating to: ${menuInfo.linkURL}`);
        shell.openExternal(menuInfo.linkURL);
      }
    });

    menu.append(copyLink);
    menu.append(openLink);

    if (this.isSrcUrlValid(menuInfo)) {
      this.addSeparator(menu);
      this.addImageItems(menu, menuInfo);
    }

    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu, menuInfo);

    return menu;
  }

  /**
   * Builds a menu applicable to a text field.
   *
   * @return {Menu}  The `Menu`
   */
  buildMenuForText(menuInfo) {
    let menu = new Menu();

    this.addSearchItems(menu, menuInfo);
    this.addCopy(menu, menuInfo);
    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu, menuInfo);

    return menu;
  }

  /**
   * Builds a menu applicable to an image.
   *
   * @return {Menu}  The `Menu`
   */
  buildMenuForImage(menuInfo) {
    let menu = new Menu();

    if (this.isSrcUrlValid(menuInfo)) {
      this.addImageItems(menu, menuInfo);
    }
    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu, menuInfo);

    return menu;
  }

  /**
   * Checks if the current text selection contains a single misspelled word and
   * if so, adds suggested spellings as individual menu items.
   */
  async addSpellingItems(menu, menuInfo) {
    let target = this.getWebContents();
    if (!menuInfo.misspelledWord || menuInfo.misspelledWord.length < 1) {
      return menu;
    }

    // Ensure that we have a spell-checker for this language
    if (!this.spellCheckHandler.currentSpellchecker) {
      return menu;
    }

    // Ensure that we have valid corrections for that word
    let corrections = await this.spellCheckHandler.getCorrectionsForMisspelling(menuInfo.misspelledWord);
    if (!corrections || !corrections.length) {
      return menu;
    }

    corrections.forEach((correction) => {
      let item = new MenuItem({
        label: correction,
        click: () => target.replaceMisspelling(correction)
      });

      menu.append(item);
    });

    this.addSeparator(menu);

    // Gate learning words based on OS support. At some point we can manage a
    // custom dictionary for Hunspell, but today is not that day
    if (process.platform === 'darwin') {
      let learnWord = new MenuItem({
        label: this.stringTable.addToDictionary(),
        click: async () => {
          // NB: This is a gross fix to invalidate the spelling underline,
          // refer to https://github.com/tinyspeck/slack-winssb/issues/354
          target.replaceMisspelling(menuInfo.selection);

          try {
            await this.spellChecker.add(menuInfo.misspelledWord);
          } catch (e) {
            d(`Failed to add entry to dictionary: ${e.message}`);
          }
        }
      });

      menu.append(learnWord);
    }

    return menu;
  }

  /**
   * Adds search-related menu items.
   */
  addSearchItems(menu, menuInfo) {
    if (!menuInfo.selectionText || menuInfo.selectionText.length < 1) {
      return menu;
    }

    let match = matchesWord(menuInfo.selectionText);
    if (!match || match.length === 0) {
      return menu;
    }

    if (process.platform === 'darwin') {
      let target = this.getWebContents();

      let lookUpDefinition = new MenuItem({
        label: this.stringTable.lookUpDefinition({word: truncateString(menuInfo.selectionText)}),
        click: () => target.showDefinitionForSelection()
      });

      menu.append(lookUpDefinition);
    }

    let search = new MenuItem({
      label: this.stringTable.searchGoogle(),
      click: () => {
        let url = `https://www.google.com/#q=${encodeURIComponent(menuInfo.selectionText)}`;

        //d(`Searching Google using ${url}`);
        shell.openExternal(url);
      }
    });

    menu.append(search);
    this.addSeparator(menu);

    return menu;
  }

  isSrcUrlValid(menuInfo) {
    return menuInfo.srcURL && menuInfo.srcURL.length > 0;
  }

  /**
   * Adds "Copy Image" and "Copy Image URL" items when `src` is valid.
   */
  addImageItems(menu, menuInfo) {
    let copyImage = new MenuItem({
      label: this.stringTable.copyImage(),
      click: () => this.convertImageToBase64(menuInfo.srcURL,
        (dataURL) => clipboard.writeImage(nativeImage.createFromDataURL(dataURL)))
    });

    menu.append(copyImage);

    let copyImageUrl = new MenuItem({
      label: this.stringTable.copyImageUrl(),
      click: () => clipboard.writeText(menuInfo.srcURL)
    });

    menu.append(copyImageUrl);
    return menu;
  }

  /**
   * Adds the Cut menu item
   */
  addCut(menu, menuInfo) {
    let target = this.getWebContents();
    menu.append(new MenuItem({
      label: this.stringTable.cut(),
      accelerator: 'CommandOrControl+X',
      enabled: menuInfo.editFlags.canCut,
      click: () => target.cut()
    }));

    return menu;
  }

  /**
   * Adds the Copy menu item.
   */
  addCopy(menu, menuInfo) {
    let target = this.getWebContents();
    menu.append(new MenuItem({
      label: this.stringTable.copy(),
      accelerator: 'CommandOrControl+C',
      enabled: menuInfo.editFlags.canCopy,
      click: () => target.copy()
    }));

    return menu;
  }

  /**
   * Adds the Paste menu item.
   */
  addPaste(menu, menuInfo) {
    let target = this.getWebContents();
    menu.append(new MenuItem({
      label: this.stringTable.paste(),
      accelerator: 'CommandOrControl+V',
      enabled: menuInfo.editFlags.canPaste,
      click: () => target.paste()
    }));

    return menu;
  }

  /**
   * Adds a separator item.
   */
  addSeparator(menu) {
    menu.append(new MenuItem({type: 'separator'}));
    return menu;
  }

  /**
   * Adds the "Inspect Element" menu item.
   */
  addInspectElement(menu, menuInfo, needsSeparator=true) {
    let target = this.getWebContents();
    if (!this.debugMode) return menu;
    if (needsSeparator) this.addSeparator(menu);

    let inspect = new MenuItem({
      label: this.stringTable.inspectElement(),
      click: () => target.inspectElement(menuInfo.x, menuInfo.y)
    });

    menu.append(inspect);
    return menu;
  }

  /**
   * Converts an image to a base-64 encoded string.
   *
   * @param  {String} url           The image URL
   * @param  {Function} callback    A callback that will be invoked with the result
   * @param  {String} outputFormat  The image format to use, defaults to 'image/png'
   */
  convertImageToBase64(url, callback, outputFormat='image/png') {
    let canvas = document.createElement('CANVAS');
    let ctx = canvas.getContext('2d');
    let img = new Image();
    img.crossOrigin = 'Anonymous';

    img.onload = () => {
      canvas.height = img.height;
      canvas.width = img.width;
      ctx.drawImage(img, 0, 0);

      let dataURL = canvas.toDataURL(outputFormat);
      canvas = null;
      callback(dataURL);
    };

    img.src = url;
  }
}
<commit_msg>Show the menu using the new async option.<commit_after>import {clipboard, nativeImage, remote, shell} from 'electron';
import {truncateString, matchesWord} from './utility';

const {Menu, MenuItem} = remote;

let d = require('debug')('electron-spellchecker:context-menu-builder');

const contextMenuStringTable = {
  copyMail: () => `Copy Email Address`,
  copyLinkUrl: () => `Copy Link`,
  openLinkUrl: () => `Open Link`,
  copyImageUrl: () => `Copy Image URL`,
  copyImage: () => `Copy Image`,
  addToDictionary: () => `Add to Dictionary`,
  lookUpDefinition: ({word}) => `Look Up "${word}"`,
  searchGoogle: () => `Search with Google`,
  cut: () => `Cut`,
  copy: () => `Copy`,
  paste: () => `Paste`,
  inspectElement: () => `Inspect Element`,
};

/**
 * ContextMenuBuilder creates context menus based on the content clicked - this
 * information is derived from
 * https://github.com/electron/electron/blob/master/docs/api/web-contents.md#event-context-menu,
 * which we use to generate the menu. We also use the spell-check information to
 * generate suggestions.
 */
export default class ContextMenuBuilder {
  /**
   * Creates an instance of ContextMenuBuilder
   *
   * @param  {SpellCheckHandler} spellCheckHandler  The spell checker to generate
   *                                                recommendations for.
   * @param  {BrowserWindow|WebView} windowOrWebView  The hosting window/WebView
   * @param  {Boolean} debugMode    If true, display the "Inspect Element" menu item.
   * @param  {function} processMenu If passed, this method will be passed the menu to change
   *                                it prior to display. Signature: (menu, info) => menu
   */
  constructor(spellCheckHandler, windowOrWebView=null, debugMode=false, processMenu=(m) => m) {
    this.spellCheckHandler = spellCheckHandler;
    this.debugMode = debugMode;
    this.processMenu = processMenu;
    this.menu = null;
    this.stringTable = Object.assign({}, contextMenuStringTable);

    windowOrWebView = windowOrWebView || remote.getCurrentWebContents();

    let ctorName = Object.getPrototypeOf(windowOrWebView).constructor.name;
    if (ctorName === 'WebContents') {
      this.getWebContents = () => windowOrWebView;
    } else {
      // NB: We do this because at the time a WebView is created, it doesn't
      // have a WebContents, we need to defer the call to getWebContents
      this.getWebContents = 'webContents' in windowOrWebView ?
        () => windowOrWebView.webContents :
        () => windowOrWebView.getWebContents();
    }
  }

  /**
   * Override the default logger for this class. You probably want to use
   * {{setGlobalLogger}} instead
   *
   * @param {Function} fn   The function which will operate like console.log
   */
  static setLogger(fn) {
    d = fn;
  }

  /**
   * Specify alternate string formatter for each context menu.
   * String table consist of string formatter as function instead per each context menu item,
   * allows to change string in runtime. All formatters are simply typeof () => string, except
   * lookUpDefinition provides word, ({word}) => string.
   *
   * @param {Object} stringTable The object contains string foramtter function for context menu.
   * It is allowed to specify only certain menu string as necessary, which will makes other string
   * fall backs to default.
   *
   */
  setAlternateStringFormatter(stringTable) {
    this.contextMenuStringTable = Object.assign(this.stringTable, stringTable);
  }

  /**
   * Shows a popup menu given the information returned from the context-menu
   * event. This is probably the only method you need to call in this class.
   *
   * @param  {Object} contextInfo   The object returned from the 'context-menu'
   *                                Electron event.
   *
   * @return {Promise}              Completion
   */
  async showPopupMenu(contextInfo) {
    let menu = await this.buildMenuForElement(contextInfo);

    // Opening a menu blocks the renderer process, which is definitely not
    // suitable for running tests
    if (!menu) return;
    menu.popup(remote.getCurrentWindow(), { async: true });
  }

  /**
   * Builds a context menu specific to the given info that _would_ be shown
   * immediately by {{showPopupMenu}}. Use this to add your own menu items to
   * the list but use most of the default behavior.
   *
   * @return {Promise<Menu>}      The newly created `Menu`
   */
  async buildMenuForElement(info) {
    //d(`Got context menu event with args: ${JSON.stringify(info)}`);

    if (info.linkURL && info.linkURL.length > 0) {
      return this.buildMenuForLink(info);
    }

    if (info.hasImageContents && info.srcURL && info.srcURL.length > 1) {
      return this.buildMenuForImage(info);
    }

    if (info.isEditable || (info.inputFieldType && info.inputFieldType !== 'none')) {
      return await this.buildMenuForTextInput(info);
    }

    return this.buildMenuForText(info);
  }

  /**
   * Builds a menu applicable to a text input field.
   *
   * @return {Menu}  The `Menu`
   */
  async buildMenuForTextInput(menuInfo) {
    let menu = new Menu();

    await this.addSpellingItems(menu, menuInfo);
    this.addSearchItems(menu, menuInfo);

    this.addCut(menu, menuInfo);
    this.addCopy(menu, menuInfo);
    this.addPaste(menu, menuInfo);
    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu);

    return menu;
  }

  /**
   * Builds a menu applicable to a link element.
   *
   * @return {Menu}  The `Menu`
   */
  buildMenuForLink(menuInfo) {
    let menu = new Menu();
    let isEmailAddress = menuInfo.linkURL.startsWith('mailto:');

    let copyLink = new MenuItem({
      label: isEmailAddress ? this.stringTable.copyMail() : this.stringTable.copyLinkUrl(),
      click: () => {
        // Omit the mailto: portion of the link; we just want the address
        clipboard.writeText(isEmailAddress ?
          menuInfo.linkText : menuInfo.linkURL);
      }
    });

    let openLink = new MenuItem({
      label: this.stringTable.openLinkUrl(),
      click: () => {
        //d(`Navigating to: ${menuInfo.linkURL}`);
        shell.openExternal(menuInfo.linkURL);
      }
    });

    menu.append(copyLink);
    menu.append(openLink);

    if (this.isSrcUrlValid(menuInfo)) {
      this.addSeparator(menu);
      this.addImageItems(menu, menuInfo);
    }

    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu, menuInfo);

    return menu;
  }

  /**
   * Builds a menu applicable to a text field.
   *
   * @return {Menu}  The `Menu`
   */
  buildMenuForText(menuInfo) {
    let menu = new Menu();

    this.addSearchItems(menu, menuInfo);
    this.addCopy(menu, menuInfo);
    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu, menuInfo);

    return menu;
  }

  /**
   * Builds a menu applicable to an image.
   *
   * @return {Menu}  The `Menu`
   */
  buildMenuForImage(menuInfo) {
    let menu = new Menu();

    if (this.isSrcUrlValid(menuInfo)) {
      this.addImageItems(menu, menuInfo);
    }
    this.addInspectElement(menu, menuInfo);
    this.processMenu(menu, menuInfo);

    return menu;
  }

  /**
   * Checks if the current text selection contains a single misspelled word and
   * if so, adds suggested spellings as individual menu items.
   */
  async addSpellingItems(menu, menuInfo) {
    let target = this.getWebContents();
    if (!menuInfo.misspelledWord || menuInfo.misspelledWord.length < 1) {
      return menu;
    }

    // Ensure that we have a spell-checker for this language
    if (!this.spellCheckHandler.currentSpellchecker) {
      return menu;
    }

    // Ensure that we have valid corrections for that word
    let corrections = await this.spellCheckHandler.getCorrectionsForMisspelling(menuInfo.misspelledWord);
    if (!corrections || !corrections.length) {
      return menu;
    }

    corrections.forEach((correction) => {
      let item = new MenuItem({
        label: correction,
        click: () => target.replaceMisspelling(correction)
      });

      menu.append(item);
    });

    this.addSeparator(menu);

    // Gate learning words based on OS support. At some point we can manage a
    // custom dictionary for Hunspell, but today is not that day
    if (process.platform === 'darwin') {
      let learnWord = new MenuItem({
        label: this.stringTable.addToDictionary(),
        click: async () => {
          // NB: This is a gross fix to invalidate the spelling underline,
          // refer to https://github.com/tinyspeck/slack-winssb/issues/354
          target.replaceMisspelling(menuInfo.selection);

          try {
            await this.spellChecker.add(menuInfo.misspelledWord);
          } catch (e) {
            d(`Failed to add entry to dictionary: ${e.message}`);
          }
        }
      });

      menu.append(learnWord);
    }

    return menu;
  }

  /**
   * Adds search-related menu items.
   */
  addSearchItems(menu, menuInfo) {
    if (!menuInfo.selectionText || menuInfo.selectionText.length < 1) {
      return menu;
    }

    let match = matchesWord(menuInfo.selectionText);
    if (!match || match.length === 0) {
      return menu;
    }

    if (process.platform === 'darwin') {
      let target = this.getWebContents();

      let lookUpDefinition = new MenuItem({
        label: this.stringTable.lookUpDefinition({word: truncateString(menuInfo.selectionText)}),
        click: () => target.showDefinitionForSelection()
      });

      menu.append(lookUpDefinition);
    }

    let search = new MenuItem({
      label: this.stringTable.searchGoogle(),
      click: () => {
        let url = `https://www.google.com/#q=${encodeURIComponent(menuInfo.selectionText)}`;

        //d(`Searching Google using ${url}`);
        shell.openExternal(url);
      }
    });

    menu.append(search);
    this.addSeparator(menu);

    return menu;
  }

  isSrcUrlValid(menuInfo) {
    return menuInfo.srcURL && menuInfo.srcURL.length > 0;
  }

  /**
   * Adds "Copy Image" and "Copy Image URL" items when `src` is valid.
   */
  addImageItems(menu, menuInfo) {
    let copyImage = new MenuItem({
      label: this.stringTable.copyImage(),
      click: () => this.convertImageToBase64(menuInfo.srcURL,
        (dataURL) => clipboard.writeImage(nativeImage.createFromDataURL(dataURL)))
    });

    menu.append(copyImage);

    let copyImageUrl = new MenuItem({
      label: this.stringTable.copyImageUrl(),
      click: () => clipboard.writeText(menuInfo.srcURL)
    });

    menu.append(copyImageUrl);
    return menu;
  }

  /**
   * Adds the Cut menu item
   */
  addCut(menu, menuInfo) {
    let target = this.getWebContents();
    menu.append(new MenuItem({
      label: this.stringTable.cut(),
      accelerator: 'CommandOrControl+X',
      enabled: menuInfo.editFlags.canCut,
      click: () => target.cut()
    }));

    return menu;
  }

  /**
   * Adds the Copy menu item.
   */
  addCopy(menu, menuInfo) {
    let target = this.getWebContents();
    menu.append(new MenuItem({
      label: this.stringTable.copy(),
      accelerator: 'CommandOrControl+C',
      enabled: menuInfo.editFlags.canCopy,
      click: () => target.copy()
    }));

    return menu;
  }

  /**
   * Adds the Paste menu item.
   */
  addPaste(menu, menuInfo) {
    let target = this.getWebContents();
    menu.append(new MenuItem({
      label: this.stringTable.paste(),
      accelerator: 'CommandOrControl+V',
      enabled: menuInfo.editFlags.canPaste,
      click: () => target.paste()
    }));

    return menu;
  }

  /**
   * Adds a separator item.
   */
  addSeparator(menu) {
    menu.append(new MenuItem({type: 'separator'}));
    return menu;
  }

  /**
   * Adds the "Inspect Element" menu item.
   */
  addInspectElement(menu, menuInfo, needsSeparator=true) {
    let target = this.getWebContents();
    if (!this.debugMode) return menu;
    if (needsSeparator) this.addSeparator(menu);

    let inspect = new MenuItem({
      label: this.stringTable.inspectElement(),
      click: () => target.inspectElement(menuInfo.x, menuInfo.y)
    });

    menu.append(inspect);
    return menu;
  }

  /**
   * Converts an image to a base-64 encoded string.
   *
   * @param  {String} url           The image URL
   * @param  {Function} callback    A callback that will be invoked with the result
   * @param  {String} outputFormat  The image format to use, defaults to 'image/png'
   */
  convertImageToBase64(url, callback, outputFormat='image/png') {
    let canvas = document.createElement('CANVAS');
    let ctx = canvas.getContext('2d');
    let img = new Image();
    img.crossOrigin = 'Anonymous';

    img.onload = () => {
      canvas.height = img.height;
      canvas.width = img.width;
      ctx.drawImage(img, 0, 0);

      let dataURL = canvas.toDataURL(outputFormat);
      canvas = null;
      callback(dataURL);
    };

    img.src = url;
  }
}
<|endoftext|>


<commit_before>'use strict';

var helpers = Chart.helpers;

module.exports = function(Chart) {

	Chart.defaults.financial = {
		label: '',

		hover: {
			mode: 'label'
		},

		scales: {
			xAxes: [{
				type: 'time',
				distribution: 'series',
				categoryPercentage: 0.8,
				barPercentage: 0.9,
				time: {
					format: 'll'
				},
				ticks: {
					source: 'data'
				}
			}],
			yAxes: [{
				type: 'financialLinear'
			}]
		},

		tooltips: {
			callbacks: {
				label: function(tooltipItem, data) {
					var o = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].o;
					var h = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].h;
					var l = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].l;
					var c = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].c;

					return ' O ' + o + ' H ' + h + ' L ' + l + ' C ' + c;
				}
			}
		}
	};

	/**
	 * This class is based off controller.bar.js from the upstream Chart.js library
	 */
	Chart.controllers.financial = Chart.DatasetController.extend({

		dataElementType: Chart.elements.Candlestick,

		initialize: function() {
			var me = this;
			var meta;

			Chart.DatasetController.prototype.initialize.apply(me, arguments);

			meta = me.getMeta();
			meta.stack = me.getDataset().stack;
			meta.bar = true;
		},

		update: function(reset) {
			var me = this;
			var elements = me.getMeta().data;
			var i, ilen;

			me._ruler = me.getRuler();

			for (i = 0, ilen = elements.length; i < ilen; ++i) {
				me.updateElement(elements[i], i, reset);
			}
		},

		updateElement: function(candle, index, reset) {
			var me = this;
			var chart = me.chart;
			var meta = me.getMeta();
			var dataset = me.getDataset();
			var custom = candle.custom || {};

			candle._xScale = me.getScaleForId(meta.xAxisID);
			candle._yScale = me.getScaleForId(meta.yAxisID);
			candle._datasetIndex = me.index;
			candle._index = index;

			candle._model = {
				datasetLabel: dataset.label || '',
				//label: '', // to get label value please use dataset.data[index].label

				// Appearance
				upCandleColor: dataset.upCandleColor,
				downCandleColor: dataset.downCandleColor,
				outlineCandleColor: dataset.outlineCandleColor,
				outlineCandleWidth: dataset.outlineCandleWidth,
			};

			me.updateElementGeometry(candle, index, reset);

			candle.pivot();
		},

		/**
		 * @private
		 */
		updateElementGeometry: function(rectangle, index, reset) {
			var me = this;
			var model = rectangle._model;
			var vscale = me.getValueScale();
			var base = vscale.getBasePixel();
			var horizontal = vscale.isHorizontal();
			var ruler = me._ruler || me.getRuler();
			var vpixels = me.calculateBarValuePixels(me.index, index);
			var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
			var candle = me.calculateCandleValuesPixels(me.index, index);

			model.horizontal = horizontal;
			model.base = reset ? base : vpixels.base;
			model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
			model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
			model.height = horizontal ? ipixels.size : undefined;
			model.width = horizontal ? undefined : ipixels.size;
			model.candle = candle;
		},

		/**
		 * @private
		 */
		getValueScaleId: function() {
			return this.getMeta().yAxisID;
		},

		/**
		 * @private
		 */
		getIndexScaleId: function() {
			return this.getMeta().xAxisID;
		},

		/**
		 * @private
		 */
		getValueScale: function() {
			return this.getScaleForId(this.getValueScaleId());
		},

		/**
		 * @private
		 */
		getIndexScale: function() {
			return this.getScaleForId(this.getIndexScaleId());
		},

		/**
		 * Returns the effective number of stacks based on groups and bar visibility.
		 * @private
		 */
		getStackCount: function(last) {
			var me = this;
			var chart = me.chart;
			var scale = me.getIndexScale();
			var stacked = scale.options.stacked;
			var ilen = last === undefined ? chart.data.datasets.length : last + 1;
			var stacks = [];
			var i, meta;

			for (i = 0; i < ilen; ++i) {
				meta = chart.getDatasetMeta(i);
				if (meta.bar && chart.isDatasetVisible(i) &&
					(stacked === false ||
						(stacked === true && stacks.indexOf(meta.stack) === -1) ||
						(stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
					stacks.push(meta.stack);
				}
			}

			return stacks.length;
		},

		/**
		 * Returns the stack index for the given dataset based on groups and bar visibility.
		 * @private
		 */
		getStackIndex: function(datasetIndex) {
			return this.getStackCount(datasetIndex) - 1;
		},

		/**
		 * @private
		 */
		getRuler: function() {
			var me = this;
			var scale = me.getIndexScale();
			var stackCount = me.getStackCount();
			var datasetIndex = me.index;
			var pixels = [];
			var isHorizontal = scale.isHorizontal();
			var start = isHorizontal ? scale.left : scale.top;
			var end = start + (isHorizontal ? scale.width : scale.height);
			var i, ilen;

			for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
				pixels.push(scale.getPixelForValue(null, i, datasetIndex));
			}

			return {
				pixels: pixels,
				start: start,
				end: end,
				stackCount: stackCount,
				scale: scale
			};
		},

		calculateCandleValuesPixels: function(datasetIndex, index) {
			var me = this;
			var chart = me.chart;
			var scale = me.getValueScale();
			var datasets = chart.data.datasets;

			return {
				o: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].o)),
				h: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].h)),
				l: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].l)),
				c: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].c))
			};
		},

		/**
		 * Note: pixel values are not clamped to the scale area.
		 * @private
		 */
		calculateBarValuePixels: function(datasetIndex, index) {
			var me = this;
			var chart = me.chart;
			var meta = me.getMeta();
			var scale = me.getValueScale();
			var datasets = chart.data.datasets;
			var value = datasets[datasetIndex].data[index].h;
			var start = datasets[datasetIndex].data[index].l;
			var i, imeta, ivalue, base, head, size;

			base = scale.getPixelForValue(start);
			head = scale.getPixelForValue(value);
			size = (head - base) / 2;

			return {
				size: size,
				base: base,
				head: head,
				center: head + size / 2
			};
		},

		/**
		 * @private
		 */
		calculateBarIndexPixels: function(datasetIndex, index, ruler) {
			var me = this;
			var options = ruler.scale.options;
			var stackIndex = me.getStackIndex(datasetIndex);
			var pixels = ruler.pixels;
			var base = pixels[index];
			var length = pixels.length;
			var start = ruler.start;
			var end = ruler.end;
			var leftSampleSize, rightSampleSize, leftCategorySize, rightCategorySize, fullBarSize, size;

			if (length === 1) {
				leftSampleSize = base > start ? base - start : end - base;
				rightSampleSize = base < end ? end - base : base - start;
			} else {
				if (index > 0) {
					leftSampleSize = (base - pixels[index - 1]) / 2;
					if (index === length - 1) {
						rightSampleSize = leftSampleSize;
					}
				}
				if (index < length - 1) {
					rightSampleSize = (pixels[index + 1] - base) / 2;
					if (index === 0) {
						leftSampleSize = rightSampleSize;
					}
				}
			}

			leftCategorySize = leftSampleSize * options.categoryPercentage;
			rightCategorySize = rightSampleSize * options.categoryPercentage;
			fullBarSize = (leftCategorySize + rightCategorySize) / ruler.stackCount;
			size = fullBarSize * options.barPercentage;

			size = Math.min(
				helpers.valueOrDefault(options.barThickness, size),
				helpers.valueOrDefault(options.maxBarThickness, Infinity));

			base -= leftCategorySize;
			base += fullBarSize * stackIndex;
			base += (fullBarSize - size) / 2;

			return {
				size: size,
				base: base,
				head: base + size,
				center: base + size / 2
			};
		},

		draw: function() {
			var ctx = this.chart.chart.ctx;
			var elements = this.getMeta().data;
			var dataset = this.getDataset();
			var ilen = elements.length;
			var i = 0;
			var d;

			Chart.canvasHelpers.clipArea(ctx, this.chart.chartArea);

			for (; i < ilen; ++i) {
				d = dataset.data[i].o;
				if (d !== null && d !== undefined && !isNaN(d)) {
					elements[i].draw();
				}
			}

			Chart.canvasHelpers.unclipArea(ctx);
		},

		removeHoverStyle: function(element, elementOpts) {

		},

		setHoverStyle: function(element) {

		},

	});
};
<commit_msg>Make financial controller extend bar controller<commit_after>'use strict';

var helpers = Chart.helpers;

module.exports = function(Chart) {

	Chart.defaults.financial = {
		label: '',

		hover: {
			mode: 'label'
		},

		scales: {
			xAxes: [{
				type: 'time',
				distribution: 'series',
				categoryPercentage: 0.8,
				barPercentage: 0.9,
				time: {
					format: 'll'
				},
				ticks: {
					source: 'data'
				}
			}],
			yAxes: [{
				type: 'financialLinear'
			}]
		},

		tooltips: {
			callbacks: {
				label: function(tooltipItem, data) {
					var o = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].o;
					var h = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].h;
					var l = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].l;
					var c = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index].c;

					return ' O ' + o + ' H ' + h + ' L ' + l + ' C ' + c;
				}
			}
		}
	};

	/**
	 * This class is based off controller.bar.js from the upstream Chart.js library
	 */
	Chart.controllers.financial = Chart.controllers.bar.extend({

		dataElementType: Chart.elements.Candlestick,

		updateElement: function(candle, index, reset) {
			var me = this;
			var chart = me.chart;
			var meta = me.getMeta();
			var dataset = me.getDataset();
			var custom = candle.custom || {};

			candle._xScale = me.getScaleForId(meta.xAxisID);
			candle._yScale = me.getScaleForId(meta.yAxisID);
			candle._datasetIndex = me.index;
			candle._index = index;

			candle._model = {
				datasetLabel: dataset.label || '',
				//label: '', // to get label value please use dataset.data[index].label

				// Appearance
				upCandleColor: dataset.upCandleColor,
				downCandleColor: dataset.downCandleColor,
				outlineCandleColor: dataset.outlineCandleColor,
				outlineCandleWidth: dataset.outlineCandleWidth,
			};

			me.updateElementGeometry(candle, index, reset);

			candle.pivot();
		},

		/**
		 * @private
		 */
		updateElementGeometry: function(rectangle, index, reset) {
			var me = this;
			var model = rectangle._model;
			var vscale = me.getValueScale();
			var base = vscale.getBasePixel();
			var horizontal = vscale.isHorizontal();
			var ruler = me._ruler || me.getRuler();
			var vpixels = me.calculateBarValuePixels(me.index, index);
			var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);

			model.horizontal = horizontal;
			model.base = reset ? base : vpixels.base;
			model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
			model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
			model.height = horizontal ? ipixels.size : undefined;
			model.width = horizontal ? undefined : ipixels.size;
			model.candle = me.calculateCandleValuesPixels(me.index, index);
		},

		/**
		 * @private
		 */
		calculateCandleValuesPixels: function(datasetIndex, index) {
			var me = this;
			var chart = me.chart;
			var scale = me.getValueScale();
			var datasets = chart.data.datasets;

			return {
				o: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].o)),
				h: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].h)),
				l: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].l)),
				c: scale.getPixelForValue(Number(datasets[datasetIndex].data[index].c))
			};
		},

		draw: function() {
			var ctx = this.chart.chart.ctx;
			var elements = this.getMeta().data;
			var dataset = this.getDataset();
			var ilen = elements.length;
			var i = 0;
			var d;

			Chart.canvasHelpers.clipArea(ctx, this.chart.chartArea);

			for (; i < ilen; ++i) {
				d = dataset.data[i].o;
				if (d !== null && d !== undefined && !isNaN(d)) {
					elements[i].draw();
				}
			}

			Chart.canvasHelpers.unclipArea(ctx);
		},

	});
};
<|endoftext|>


<commit_before>import _ from 'lodash'
import {ATTR, hasAttributeFor} from './attributes'

const SUPER_SUB_FONT_RATIO = 0.65  // matches MS word according to http://en.wikipedia.org/wiki/Subscript_and_superscript

function calcFontScale(fontSize, unitsPerEm) {
  return 1 / unitsPerEm * fontSize
}

function calcSuperSubFontSize(fontSize, minFontSize) {
  let superSubFontSize = Math.round(fontSize * SUPER_SUB_FONT_RATIO)
  return superSubFontSize > minFontSize ? superSubFontSize : minFontSize
}

function charFontStyle(char) {
  let attrs = char.attributes
  if(!attrs) return 'regular'

  let bold = false
  let italic = false

  if(!_.isUndefined(attrs[ATTR.BOLD])) bold = attrs[ATTR.BOLD]
  if(!_.isUndefined(attrs[ATTR.ITALIC])) italic = attrs[ATTR.ITALIC]

  if(bold && italic) return 'boldItalic'
  else if(bold) return 'bold'
  else if(italic) return 'italic'
  else return 'regular'
}

function calcFontSizeFromAttributes(fontSize, minFontSize, attributes) {
  let hasAttribute = hasAttributeFor(attributes)

  // superscript and subscript affect the font size
  let superscript = hasAttribute(ATTR.SUPERSCRIPT)
  let subscript = hasAttribute(ATTR.SUBSCRIPT)

  return superscript || subscript ? calcSuperSubFontSize(fontSize, minFontSize) : fontSize
}

function fontSpec(fontSize, font) {
  let styleSpec = font.styleName === 'Regular' ? '' : `${font.styleName} `
  let fontSizeSpec = `${fontSize}px `
  let name = font.familyName
  return styleSpec + fontSizeSpec + name
}

function calcCharAdvanceOpenType(char, fontSize, font, unitsPerEm) {
  let glyph = font.charToGlyph(char)
  return glyph.unicode ?
    glyph.advanceWidth * calcFontScale(fontSize, unitsPerEm) :
    0
}

let canvas = _.memoize(function() {
  return document.createElement('canvas')
})

let canvasContext = _.memoize(function() {
  return canvas().getContext('2d')
})

function clearCanvas() {
  let c = canvas()
  canvasContext().clearRect(0, 0, c.width, c.height)
}

function calcTextAdvanceCanvas(text, fontSize, font) {
  // need to override newline handling, measureText doesn't handle it correctly (returns a non-zero width)
  if(text === '\n') {
    return 0
  }
  let context = canvasContext()
  context.font = fontSpec(fontSize, font)
  return context.measureText(text).width
}

/**
 * Tests various string widths and compares the advance width (in pixels) results between OpenType.js and
 * the canvas fallback mechanism which returns the browser's actual rendered font width.
 * @type {Function}
 */
let isOpenTypeJsReliable = _.memoize(function(fontSize, font, unitsPerEm) {
  let strings = [
    '111111111111111111111111111111',
    'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
    'iiiiiiiiiiiiiiiiiiiiiiiiiiiiii',
    'wwwwwwwwwwwwwwwwwwwwwwwwwwwwww',
    'Lorem ipsum dolor sit amet, libris essent labitur duo cu.'
  ]

  let reduceOt = function(currentAdvance, char) {
    return currentAdvance + calcCharAdvanceOpenType(char, fontSize, font, unitsPerEm)
  }

  let reduceCanvas = function(currentAdvance, char) {
    return currentAdvance + calcTextAdvanceCanvas(char, fontSize, font)
  }

  let reliable = true
  for(let candidate of strings) {
    let chars = candidate.split('')
    let advanceOt = chars.reduce(reduceOt, 0)
    let advanceCanvas = calcTextAdvanceCanvas(candidate, fontSize, font)
    let delta = Math.abs(advanceOt - advanceCanvas)

    if(delta > 1) {
      console.warn(`OpenType.js NOT reliable on this browser/OS (or font not loaded):
  Candidate = [${candidate}], Fontspec = ${fontSpec(fontSize, font)}, Δ = ${delta}px
  Falling back to slower canvas measurement mechanism.`)

      // test if canvas char-by-char width additions are the same as canvas total text width
      // if this ever returns false, then the current approach will need to be refactored, see docs on calcCharAdvance
      let advanceCanvasByChar = chars.reduce(reduceCanvas, 0)
      let deltaCanvas = Math.abs(advanceCanvas - advanceCanvasByChar)

      if(deltaCanvas > 1) {
        console.error(`Canvas char-by-char width != canvas text width, oops!
  Candidate = [${candidate}], Fontspec = ${fontSpec(fontSize, font)}, Δ ot = ${delta}px, Δ canvas = ${deltaCanvas}px
  Please report this along with your browser/OS details.`)
      }

      reliable = false
      break
    }
  }

  // clear the canvas, not really necessary as measureText shouldn't write anything there
  clearCanvas()
  return reliable
}, (fontSize, font, unitsPerEm) => fontSpec(fontSize, font) + ' ' + unitsPerEm)

/**
 * Calculate the advance in pixels for a given char. In some browsers/platforms/font sizes, the fonts are not
 * rendered according to the specs in the font (see
 * http://stackoverflow.com/questions/30922573/firefox-rendering-of-opentype-font-does-not-match-the-font-specification).
 * Therefore, ensure the font spec matches the actual rendered width (via the canvas `measureText` method), and use
 * the font spec if it matches, otherwise fall back to the (slower) measuredText option.
 *
 * NOTE there may still be one difference between the browser's rendering and canvas-based calculations here: the
 * browser renders entire strings within elements, whereas this calculation renders one character to the canvas at
 * a time and adds up the widths. These two approaches seem to be equivalent except for IE in compatibility mode.
 *
 * TODO refactor mixin to deal with chunks of styled text rather than chars for IE in compatibility mode
 */
function calcCharAdvance(char, fontSize, font, unitsPerEm) {
  return isOpenTypeJsReliable(fontSize, font, unitsPerEm) ?
    calcCharAdvanceOpenType(char, fontSize, font, unitsPerEm) :
    calcTextAdvanceCanvas(char, fontSize, font)
}

function calcReplicaCharAdvance(replicaChar, fontSize, fonts, minFontSize, unitsPerEm) {
  let style = charFontStyle(replicaChar)
  let charFontSize = calcFontSizeFromAttributes(fontSize, minFontSize, replicaChar.attributes)
  return calcCharAdvance(replicaChar.char, charFontSize, fonts[style], unitsPerEm)
}

export default {
  setConfig(config) {
    this.config = config
  },

  /**
   * Get the font scale to convert between font units and pixels for the given font size.
   * @param fontSize
   * @return {number}
   */
  fontScale(fontSize) {
    return calcFontScale(fontSize, this.config.unitsPerEm)
  },

  /**
   * Return the font size given the default font size and current attributes.
   * @param fontSize
   * @param attributes
   */
  fontSizeFromAttributes(fontSize, attributes) {
    return calcFontSizeFromAttributes(fontSize, this.config.minFontSize, attributes)
  },

  /**
   * Determines the advance for a given replica char.
   * @param char The replica char object.
   * @param fontSize
   * @return {number}
   */
  replicaCharAdvance(char, fontSize) {
    return calcReplicaCharAdvance(char, fontSize, this.config.fonts, this.config.minFontSize, this.config.unitsPerEm)
  },

  /**
   * Determines the advance for a given char. Since it is not a replica char, the font style and other attribute
   * information cannot be determined. A normal weight, non-decorated font with no special attributes is assumed.
   */
  charAdvance(char, fontSize, font) {
    return calcCharAdvance(char, fontSize, font, this.config.unitsPerEm)
  },

  /**
   * Returns the advance width in pixels for a space character in the normal style.
   */
  advanceXForSpace(fontSize) {
    return this.charAdvance(' ', fontSize, this.config.fonts.regular)
  },

  /**
   * Obtain an Object with the char id and cursor position for a given pixel value. This is used to
   * set the current character and position the cursor correctly on a mouse click. If the target position
   * is past the last character, the index of the last character is returned.
   * @param {number} fontSize
   * @param {number} pixelValue
   * @param {Array} chars The characters used to compare against the given pixel value.
   * @return {Object} The cursor x position (cursorX) between characters, and the 0-based character index
   *   (index) for the given x value.
   */
  indexAndCursorForXValue(fontSize, pixelValue, chars) {
    let minFontSize = this.config.minFontSize
    fontSize = fontSize > minFontSize ? fontSize : minFontSize
    let currentWidthPx = 0
    let index = 0
    for(let i = 0; i < chars.length; i++) {
      let glyphAdvancePx = this.replicaCharAdvance(chars[i], fontSize)
      if(pixelValue < currentWidthPx + glyphAdvancePx / 2) {
        return {
          cursorX: currentWidthPx,
          index: index
        }
      } else {
        currentWidthPx += glyphAdvancePx
        if(glyphAdvancePx > 0) index++
      }
    }
    return {
      cursorX: currentWidthPx,
      index: index
    }
  },

  /**
   * Get the advance width in pixels for the given char or chars.
   * @param {number} fontSize
   * @param {object|Array} chars
   * @return {number}
   */
  advanceXForChars(fontSize, chars) {
    let minFontSize = this.config.minFontSize
    fontSize = fontSize > minFontSize ? fontSize : minFontSize
    if(_.isArray(chars)) {
      return chars.reduce((currentWidthPx, char) => {
        return currentWidthPx + this.replicaCharAdvance(char, fontSize)
      }, 0)
    } else {
      return this.replicaCharAdvance(chars, fontSize)
    }
  },

  /**
   * Gets the line height in pixels for a given font size, using the bold font.
   */
  lineHeight(fontSize) {
    let fontHeader = this.config.fonts.bold.tables.head
    return (fontHeader.yMax - fontHeader.yMin) * this.fontScale(fontSize)
  },

  /**
   * Gets the height in pixels of the top of the font, relative to the baseline, using the bold font.
   */
  top(fontSize) {
    let fontHeader = this.config.fonts.bold.tables.head
    return fontHeader.yMax * this.fontScale(fontSize)
  }
}
<commit_msg>Fallback to canvas measurement when font glyph not available<commit_after>import _ from 'lodash'
import {ATTR, hasAttributeFor} from './attributes'

const SUPER_SUB_FONT_RATIO = 0.65  // matches MS word according to http://en.wikipedia.org/wiki/Subscript_and_superscript

function calcFontScale(fontSize, unitsPerEm) {
  return 1 / unitsPerEm * fontSize
}

function calcSuperSubFontSize(fontSize, minFontSize) {
  let superSubFontSize = Math.round(fontSize * SUPER_SUB_FONT_RATIO)
  return superSubFontSize > minFontSize ? superSubFontSize : minFontSize
}

function charFontStyle(char) {
  let attrs = char.attributes
  if(!attrs) return 'regular'

  let bold = false
  let italic = false

  if(!_.isUndefined(attrs[ATTR.BOLD])) bold = attrs[ATTR.BOLD]
  if(!_.isUndefined(attrs[ATTR.ITALIC])) italic = attrs[ATTR.ITALIC]

  if(bold && italic) return 'boldItalic'
  else if(bold) return 'bold'
  else if(italic) return 'italic'
  else return 'regular'
}

function calcFontSizeFromAttributes(fontSize, minFontSize, attributes) {
  let hasAttribute = hasAttributeFor(attributes)

  // superscript and subscript affect the font size
  let superscript = hasAttribute(ATTR.SUPERSCRIPT)
  let subscript = hasAttribute(ATTR.SUBSCRIPT)

  return superscript || subscript ? calcSuperSubFontSize(fontSize, minFontSize) : fontSize
}

function fontSpec(fontSize, font) {
  let styleSpec = font.styleName === 'Regular' ? '' : `${font.styleName} `
  let fontSizeSpec = `${fontSize}px `
  let name = font.familyName
  return styleSpec + fontSizeSpec + name
}

function calcCharAdvanceOpenType(char, fontSize, font, unitsPerEm) {
  let glyph = font.charToGlyph(char)
  return glyph.unicode ?
    glyph.advanceWidth * calcFontScale(fontSize, unitsPerEm) :
    // font doesn't contain a glyph for this char, fallback to canvas measurement
    calcTextAdvanceCanvas(char, fontSize, font)
}

let canvas = _.memoize(function() {
  return document.createElement('canvas')
})

let canvasContext = _.memoize(function() {
  return canvas().getContext('2d')
})

function clearCanvas() {
  let c = canvas()
  canvasContext().clearRect(0, 0, c.width, c.height)
}

function calcTextAdvanceCanvas(text, fontSize, font) {
  // need to override newline handling, measureText doesn't handle it correctly (returns a non-zero width)
  if(text === '\n') {
    return 0
  }
  let context = canvasContext()
  context.font = fontSpec(fontSize, font)
  return context.measureText(text).width
}

/**
 * Tests various string widths and compares the advance width (in pixels) results between OpenType.js and
 * the canvas fallback mechanism which returns the browser's actual rendered font width.
 * @type {Function}
 */
let isOpenTypeJsReliable = _.memoize(function(fontSize, font, unitsPerEm) {
  let strings = [
    '111111111111111111111111111111',
    'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
    'iiiiiiiiiiiiiiiiiiiiiiiiiiiiii',
    'wwwwwwwwwwwwwwwwwwwwwwwwwwwwww',
    'Lorem ipsum dolor sit amet, libris essent labitur duo cu.'
  ]

  let reduceOt = function(currentAdvance, char) {
    return currentAdvance + calcCharAdvanceOpenType(char, fontSize, font, unitsPerEm)
  }

  let reduceCanvas = function(currentAdvance, char) {
    return currentAdvance + calcTextAdvanceCanvas(char, fontSize, font)
  }

  let reliable = true
  for(let candidate of strings) {
    let chars = candidate.split('')
    let advanceOt = chars.reduce(reduceOt, 0)
    let advanceCanvas = calcTextAdvanceCanvas(candidate, fontSize, font)
    let delta = Math.abs(advanceOt - advanceCanvas)

    if(delta > 1) {
      console.warn(`OpenType.js NOT reliable on this browser/OS (or font not loaded):
  Candidate = [${candidate}], Fontspec = ${fontSpec(fontSize, font)}, Δ = ${delta}px
  Falling back to slower canvas measurement mechanism.`)

      // test if canvas char-by-char width additions are the same as canvas total text width
      // if this ever returns false, then the current approach will need to be refactored, see docs on calcCharAdvance
      let advanceCanvasByChar = chars.reduce(reduceCanvas, 0)
      let deltaCanvas = Math.abs(advanceCanvas - advanceCanvasByChar)

      if(deltaCanvas > 1) {
        console.error(`Canvas char-by-char width != canvas text width, oops!
  Candidate = [${candidate}], Fontspec = ${fontSpec(fontSize, font)}, Δ ot = ${delta}px, Δ canvas = ${deltaCanvas}px
  Please report this along with your browser/OS details.`)
      }

      reliable = false
      break
    }
  }

  // clear the canvas, not really necessary as measureText shouldn't write anything there
  clearCanvas()
  return reliable
}, (fontSize, font, unitsPerEm) => fontSpec(fontSize, font) + ' ' + unitsPerEm)

/**
 * Calculate the advance in pixels for a given char. In some browsers/platforms/font sizes, the fonts are not
 * rendered according to the specs in the font (see
 * http://stackoverflow.com/questions/30922573/firefox-rendering-of-opentype-font-does-not-match-the-font-specification).
 * Therefore, ensure the font spec matches the actual rendered width (via the canvas `measureText` method), and use
 * the font spec if it matches, otherwise fall back to the (slower) measuredText option.
 *
 * NOTE there may still be one difference between the browser's rendering and canvas-based calculations here: the
 * browser renders entire strings within elements, whereas this calculation renders one character to the canvas at
 * a time and adds up the widths. These two approaches seem to be equivalent except for IE in compatibility mode.
 *
 * TODO refactor mixin to deal with chunks of styled text rather than chars for IE in compatibility mode
 */
function calcCharAdvance(char, fontSize, font, unitsPerEm) {
  return isOpenTypeJsReliable(fontSize, font, unitsPerEm) ?
    calcCharAdvanceOpenType(char, fontSize, font, unitsPerEm) :
    calcTextAdvanceCanvas(char, fontSize, font)
}

function calcReplicaCharAdvance(replicaChar, fontSize, fonts, minFontSize, unitsPerEm) {
  let style = charFontStyle(replicaChar)
  let charFontSize = calcFontSizeFromAttributes(fontSize, minFontSize, replicaChar.attributes)
  return calcCharAdvance(replicaChar.char, charFontSize, fonts[style], unitsPerEm)
}

export default {
  setConfig(config) {
    this.config = config
  },

  /**
   * Get the font scale to convert between font units and pixels for the given font size.
   * @param fontSize
   * @return {number}
   */
  fontScale(fontSize) {
    return calcFontScale(fontSize, this.config.unitsPerEm)
  },

  /**
   * Return the font size given the default font size and current attributes.
   * @param fontSize
   * @param attributes
   */
  fontSizeFromAttributes(fontSize, attributes) {
    return calcFontSizeFromAttributes(fontSize, this.config.minFontSize, attributes)
  },

  /**
   * Determines the advance for a given replica char.
   * @param char The replica char object.
   * @param fontSize
   * @return {number}
   */
  replicaCharAdvance(char, fontSize) {
    return calcReplicaCharAdvance(char, fontSize, this.config.fonts, this.config.minFontSize, this.config.unitsPerEm)
  },

  /**
   * Determines the advance for a given char. Since it is not a replica char, the font style and other attribute
   * information cannot be determined. A normal weight, non-decorated font with no special attributes is assumed.
   */
  charAdvance(char, fontSize, font) {
    return calcCharAdvance(char, fontSize, font, this.config.unitsPerEm)
  },

  /**
   * Returns the advance width in pixels for a space character in the normal style.
   */
  advanceXForSpace(fontSize) {
    return this.charAdvance(' ', fontSize, this.config.fonts.regular)
  },

  /**
   * Obtain an Object with the char id and cursor position for a given pixel value. This is used to
   * set the current character and position the cursor correctly on a mouse click. If the target position
   * is past the last character, the index of the last character is returned.
   * @param {number} fontSize
   * @param {number} pixelValue
   * @param {Array} chars The characters used to compare against the given pixel value.
   * @return {Object} The cursor x position (cursorX) between characters, and the 0-based character index
   *   (index) for the given x value.
   */
  indexAndCursorForXValue(fontSize, pixelValue, chars) {
    let minFontSize = this.config.minFontSize
    fontSize = fontSize > minFontSize ? fontSize : minFontSize
    let currentWidthPx = 0
    let index = 0
    for(let i = 0; i < chars.length; i++) {
      let glyphAdvancePx = this.replicaCharAdvance(chars[i], fontSize)
      if(pixelValue < currentWidthPx + glyphAdvancePx / 2) {
        return {
          cursorX: currentWidthPx,
          index: index
        }
      } else {
        currentWidthPx += glyphAdvancePx
        if(glyphAdvancePx > 0) index++
      }
    }
    return {
      cursorX: currentWidthPx,
      index: index
    }
  },

  /**
   * Get the advance width in pixels for the given char or chars.
   * @param {number} fontSize
   * @param {object|Array} chars
   * @return {number}
   */
  advanceXForChars(fontSize, chars) {
    let minFontSize = this.config.minFontSize
    fontSize = fontSize > minFontSize ? fontSize : minFontSize
    if(_.isArray(chars)) {
      return chars.reduce((currentWidthPx, char) => {
        return currentWidthPx + this.replicaCharAdvance(char, fontSize)
      }, 0)
    } else {
      return this.replicaCharAdvance(chars, fontSize)
    }
  },

  /**
   * Gets the line height in pixels for a given font size, using the bold font.
   */
  lineHeight(fontSize) {
    let fontHeader = this.config.fonts.bold.tables.head
    return (fontHeader.yMax - fontHeader.yMin) * this.fontScale(fontSize)
  },

  /**
   * Gets the height in pixels of the top of the font, relative to the baseline, using the bold font.
   */
  top(fontSize) {
    let fontHeader = this.config.fonts.bold.tables.head
    return fontHeader.yMax * this.fontScale(fontSize)
  }
}
<|endoftext|>


<commit_before>// curve25519

;(function (root, factory) {

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = factory(require('./bigint.js'))
  } else {
    root.Curve25519 = factory(root.BigInt)
  }

}(this, function (BigInt) {

// In order to generate a public value:
//	priv = BigInt.randBigInt(256)
//	pub = scalarMult(priv, basePoint)
//
// In order to perform key agreement:
//	shared = scalarMult(myPrivate, theirPublic)

var Curve25519 = function () {}

// p25519 is the curve25519 prime: 2^255 - 19
Curve25519.p25519 = BigInt.str2bigInt('7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed', 16)
// p25519Minus2 = 2^255 - 21
var p25519Minus2 = BigInt.str2bigInt('7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb', 16)
// a is a parameter of the elliptic curve
var a = BigInt.str2bigInt('486662', 10)
// basePoint is the generator of the elliptic curve group
var basePoint = BigInt.str2bigInt('9', 10)

// These variables are names for small, bigint constants.
var four = BigInt.str2bigInt('4', 10)

// groupAdd adds two elements of the elliptic curve group in Montgomery form.
function groupAdd(x1, xn, zn, xm, zm) {
	// x₃ = 4(x·x′ - z·z′)² · z1
	var xx = BigInt.multMod(xn, xm, Curve25519.p25519)
	var zz = BigInt.multMod(zn, zm, Curve25519.p25519)
	var d
	if (BigInt.greater(xx, zz)) {
		d = BigInt.sub(xx, zz)
	} else {
		d = BigInt.sub(zz, xx)
	}
	var sq = BigInt.multMod(d, d, Curve25519.p25519)
	var outx = BigInt.multMod(sq, four, Curve25519.p25519)

	// z₃ = 4(x·z′ - z·x′)² · x1
	var xz = BigInt.multMod(xm, zn, Curve25519.p25519)
	var zx = BigInt.multMod(zm, xn, Curve25519.p25519)
	if (BigInt.greater(xz, zx)) {
		d = BigInt.sub(xz, zx)
	} else {
		d = BigInt.sub(zx, xz)
	}
	sq = BigInt.multMod(d, d, Curve25519.p25519)
	var sq2 = BigInt.multMod(sq, x1, Curve25519.p25519)
	var outz = BigInt.multMod(sq2, four, Curve25519.p25519)

	return [outx, outz]
}

// groupDouble doubles a point in the elliptic curve group.
function groupDouble(x, z) {
	// x₂ = (x² - z²)²
	var xx = BigInt.multMod(x, x, Curve25519.p25519)
	var zz = BigInt.multMod(z, z, Curve25519.p25519)
	var d
	if (BigInt.greater(xx, zz)) {
		d = BigInt.sub(xx, zz)
	} else {
		d = BigInt.sub(zz, xx)
	}
	var outx = BigInt.multMod(d, d, Curve25519.p25519)

	// z₂ = 4xz·(x² + Axz + z²)
	var s = BigInt.add(xx, zz)
	var xz = BigInt.multMod(x, z, Curve25519.p25519)
	var axz = BigInt.mult(xz, a)
	s = BigInt.add(s, axz)
	var fourxz = BigInt.mult(xz, four)
	var outz = BigInt.multMod(fourxz, s, Curve25519.p25519)

	return [outx, outz]
}

// scalarMult calculates i*base in the elliptic curve.
Curve25519.scalarMult = function(i, base) {
	var size = Math.ceil(255 / BigInt.bpe)
	var bit255 = 254 % BigInt.bpe
	var scalar = BigInt.expand(i, size)
	scalar[0] &= (1 << BigInt.bpe) - 8
	scalar[size - 1] |= 1 << bit255

	var x1 = BigInt.str2bigInt('1', 10)
	var z1 = BigInt.str2bigInt('0', 10)
	var x2 = base
	var z2 = BigInt.str2bigInt('1', 10)

	var j, point
	for (i = size - 1; i >= 0; i--) {
		j = BigInt.bpe - 1
		if (i === size - 1) {
			j = bit255
		}
		for (; j >= 0; j--) {
			if (scalar[i]&(1 << j)) {
				point = groupAdd(base, x1, z1, x2, z2)
				x1 = point[0]
				z1 = point[1]
				point = groupDouble(x2, z2)
				x2 = point[0]
				z2 = point[1]
			} else {
				point = groupAdd(base, x1, z1, x2, z2)
				x2 = point[0]
				z2 = point[1]
				point = groupDouble(x1, z1)
				x1 = point[0]
				z1 = point[1]
			}
		}
	}
	var z1inv = BigInt.powMod(z1, p25519Minus2, Curve25519.p25519)
	var x = BigInt.multMod(z1inv, x1, Curve25519.p25519)

	return x
}

// P256

// var priv = BigInt.randBigInt(256)
// var pub = scalarMultP256(p256Gx, p256Gy, priv)
// var message = BigInt.str2bigInt('2349623424239482634', 10)

// p256 is the p256 prime
// var p256 = BigInt.str2bigInt('115792089210356248762697446949407573530086143415290314195533631308867097853951', 10)
// n256 is the number of points in the group
// var n256 = BigInt.str2bigInt('115792089210356248762697446949407573529996955224135760342422259061068512044369', 10)
// b256 is a parameter of the curve
// var b256 = BigInt.str2bigInt('5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16)
// p256Gx and p256Gy is the generator of the group
// var p256Gx = BigInt.str2bigInt('6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296', 16)
// var p256Gy = BigInt.str2bigInt('4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5', 16)

Curve25519.privateKeyToString = function(p){
	return BigInt.bigInt2str(p, 64)
}

Curve25519.privateKeyFromString = function(s){
	return BigInt.str2bigInt(s, 64)
}

Curve25519.sigToString = function(p){
	return JSON.stringify([BigInt.bigInt2str(p[0], 64), BigInt.bigInt2str(p[1], 64)])
}

Curve25519.sigFromString = function(s){
	var p = JSON.parse(s)
	p[0] = BigInt.str2bigInt(p[0], 64)
	p[1] = BigInt.str2bigInt(p[1], 64)
	return p
}

Curve25519.publicKeyToString = function(p){
	return JSON.stringify([BigInt.bigInt2str(p[0], 64), BigInt.bigInt2str(p[1], 64)])
}

Curve25519.publicKeyFromString = function(s){
	var p = JSON.parse(s)
	p[0] = BigInt.str2bigInt(p[0], 64)
	p[1] = BigInt.str2bigInt(p[1], 64)
	return p
}

// isOnCurve returns true if the given point is on the curve.
// function isOnCurve(x, y) {
// 	// y² = x³ - 3x + b
// 	var yy = BigInt.multMod(y, y, p256)
// 	var xxx = BigInt.multMod(x, BigInt.mult(x, x), p256)
// 	var threex = BigInt.multMod(three, x, p256)
// 	var s = BigInt.add(xxx, b256)
// 	if (BigInt.greater(threex, s)) {
// 		return false
// 	}
// 	s = BigInt.sub(s, threex)
// 	return BigInt.equals(s, yy)
// }

Curve25519.ecDH = function(priv, pub) {
	if (typeof pub === 'undefined') {
		return Curve25519.scalarMult(priv, basePoint)
	}
	else {
		return Curve25519.scalarMult(priv, pub)
	}
}

return Curve25519

}))
<commit_msg>Separation of elliptic.js and BigInt.js<commit_after>// curve25519

;(function (root, factory) {

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = factory(require('./bigint.js'))
  } else {
    root.Curve25519 = factory(root.BigInt)
  }

}(this, function (BigInt) {

// In order to generate a public value:
//	priv = BigInt.randBigInt(256)
//	pub = scalarMult(priv, basePoint)
//
// In order to perform key agreement:
//	shared = scalarMult(myPrivate, theirPublic)

var Curve25519 = function () {}

// p25519 is the curve25519 prime: 2^255 - 19
Curve25519.p25519 = BigInt.str2bigInt('7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed', 16)
// p25519Minus2 = 2^255 - 21
var p25519Minus2 = BigInt.str2bigInt('7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb', 16)
// a is a parameter of the elliptic curve
var a = BigInt.str2bigInt('486662', 10)
// basePoint is the generator of the elliptic curve group
var basePoint = BigInt.str2bigInt('9', 10)

// These variables are names for small, bigint constants.
var four = BigInt.str2bigInt('4', 10)

// groupAdd adds two elements of the elliptic curve group in Montgomery form.
function groupAdd(x1, xn, zn, xm, zm) {
	// x₃ = 4(x·x′ - z·z′)² · z1
	var xx = BigInt.multMod(xn, xm, Curve25519.p25519)
	var zz = BigInt.multMod(zn, zm, Curve25519.p25519)
	var d
	if (BigInt.greater(xx, zz)) {
		d = BigInt.sub(xx, zz)
	} else {
		d = BigInt.sub(zz, xx)
	}
	var sq = BigInt.multMod(d, d, Curve25519.p25519)
	var outx = BigInt.multMod(sq, four, Curve25519.p25519)

	// z₃ = 4(x·z′ - z·x′)² · x1
	var xz = BigInt.multMod(xm, zn, Curve25519.p25519)
	var zx = BigInt.multMod(zm, xn, Curve25519.p25519)
	if (BigInt.greater(xz, zx)) {
		d = BigInt.sub(xz, zx)
	} else {
		d = BigInt.sub(zx, xz)
	}
	sq = BigInt.multMod(d, d, Curve25519.p25519)
	var sq2 = BigInt.multMod(sq, x1, Curve25519.p25519)
	var outz = BigInt.multMod(sq2, four, Curve25519.p25519)

	return [outx, outz]
}

// groupDouble doubles a point in the elliptic curve group.
function groupDouble(x, z) {
	// x₂ = (x² - z²)²
	var xx = BigInt.multMod(x, x, Curve25519.p25519)
	var zz = BigInt.multMod(z, z, Curve25519.p25519)
	var d
	if (BigInt.greater(xx, zz)) {
		d = BigInt.sub(xx, zz)
	} else {
		d = BigInt.sub(zz, xx)
	}
	var outx = BigInt.multMod(d, d, Curve25519.p25519)

	// z₂ = 4xz·(x² + Axz + z²)
	var s = BigInt.add(xx, zz)
	var xz = BigInt.multMod(x, z, Curve25519.p25519)
	var axz = BigInt.mult(xz, a)
	s = BigInt.add(s, axz)
	var fourxz = BigInt.mult(xz, four)
	var outz = BigInt.multMod(fourxz, s, Curve25519.p25519)

	return [outx, outz]
}

// scalarMult calculates i*base in the elliptic curve.
Curve25519.scalarMult = function (scalar, base) {
	var x1 = BigInt.str2bigInt('1', 10)
	var z1 = BigInt.str2bigInt('0', 10)
	var x2 = base
	var z2 = BigInt.str2bigInt('1', 10)
	var point

	// Highest bit is one
	point = groupAdd(base, x1, z1, x2, z2)
	x1 = point[0]
	z1 = point[1]
	point = groupDouble(x2, z2)
	x2 = point[0]
	z2 = point[1]

	for (var i = 253; i >= 3; i--) {
		if (BigInt.getBit(scalar, i)) {
			point = groupAdd(base, x1, z1, x2, z2)
			x1 = point[0]
			z1 = point[1]
			point = groupDouble(x2, z2)
			x2 = point[0]
			z2 = point[1]
		} else {
			point = groupAdd(base, x1, z1, x2, z2)
			x2 = point[0]
			z2 = point[1]
			point = groupDouble(x1, z1)
			x1 = point[0]
			z1 = point[1]
		}
	}

	// Lowest 3 bits are zero
	for (var i = 2; i >= 0; i--) {
		point = groupDouble(x1, z1)
		x1 = point[0]
		z1 = point[1]
	}

	var z1inv = BigInt.powMod(z1, p25519Minus2, Curve25519.p25519)
	var x = BigInt.multMod(z1inv, x1, Curve25519.p25519)

	return x
}

// P256

// var priv = BigInt.randBigInt(256)
// var pub = scalarMultP256(p256Gx, p256Gy, priv)
// var message = BigInt.str2bigInt('2349623424239482634', 10)

// p256 is the p256 prime
// var p256 = BigInt.str2bigInt('115792089210356248762697446949407573530086143415290314195533631308867097853951', 10)
// n256 is the number of points in the group
// var n256 = BigInt.str2bigInt('115792089210356248762697446949407573529996955224135760342422259061068512044369', 10)
// b256 is a parameter of the curve
// var b256 = BigInt.str2bigInt('5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16)
// p256Gx and p256Gy is the generator of the group
// var p256Gx = BigInt.str2bigInt('6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296', 16)
// var p256Gy = BigInt.str2bigInt('4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5', 16)

Curve25519.privateKeyToString = function(p){
	return BigInt.bigInt2str(p, 64)
}

Curve25519.privateKeyFromString = function(s){
	return BigInt.str2bigInt(s, 64)
}

Curve25519.sigToString = function(p){
	return JSON.stringify([BigInt.bigInt2str(p[0], 64), BigInt.bigInt2str(p[1], 64)])
}

Curve25519.sigFromString = function(s){
	var p = JSON.parse(s)
	p[0] = BigInt.str2bigInt(p[0], 64)
	p[1] = BigInt.str2bigInt(p[1], 64)
	return p
}

Curve25519.publicKeyToString = function(p){
	return JSON.stringify([BigInt.bigInt2str(p[0], 64), BigInt.bigInt2str(p[1], 64)])
}

Curve25519.publicKeyFromString = function(s){
	var p = JSON.parse(s)
	p[0] = BigInt.str2bigInt(p[0], 64)
	p[1] = BigInt.str2bigInt(p[1], 64)
	return p
}

// isOnCurve returns true if the given point is on the curve.
// function isOnCurve(x, y) {
// 	// y² = x³ - 3x + b
// 	var yy = BigInt.multMod(y, y, p256)
// 	var xxx = BigInt.multMod(x, BigInt.mult(x, x), p256)
// 	var threex = BigInt.multMod(three, x, p256)
// 	var s = BigInt.add(xxx, b256)
// 	if (BigInt.greater(threex, s)) {
// 		return false
// 	}
// 	s = BigInt.sub(s, threex)
// 	return BigInt.equals(s, yy)
// }

Curve25519.ecDH = function(priv, pub) {
	if (typeof pub === 'undefined') {
		return Curve25519.scalarMult(priv, basePoint)
	}
	else {
		return Curve25519.scalarMult(priv, pub)
	}
}

return Curve25519

}))
<|endoftext|>