<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import {injectIntl, intlShape} from 'react-intl'
import _isEqual from 'lodash/isEqual'
import {withTheme} from 'styled-components'
import {js} from 'tocco-util'

import {theme} from '../../utilStyles'
import {
  StyledDateAbstractInput,
  StyledDateAbstractWrapper
} from './StyledDateAbstract'

class DateAbstract extends React.Component {
  Flatpickr = null
  componentIsUnmounted = false

  constructor(props) {
    super(props)
    this.wrapper = React.createRef()

    this.state = {altInput: ''}

    import(/* webpackChunkName: "flatpickr" */ '!style-loader!css-loader!flatpickr/dist/themes/light.css')

    Promise.all([
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr'),
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr/dist/l10n/de.js'),
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr/dist/l10n/fr.js'),
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr/dist/l10n/it.js')
    ]).then(response => {
      if (!this.componentIsUnmounted) {
        this.Flatpickr = response[0].default
        this.localeMap = {
          'de-CH': response[1].German,
          'de': response[1].German,
          'fr': response[2].French,
          'it': response[3].Italian
        }

        this.initializeFlatPickr()
      }
    })
  }

  initializeFlatPickr = flatpickr => {
    const locale = this.localeMap[this.props.intl.locale]

    this.options = {
      wrap: true,
      onChange: this.handleOnChange,
      altInput: true,
      altInputClass: '',
      clickOpens: !this.props.immutable,
      defaultDate: this.props.value,
      ...(locale ? {locale} : {}),
      ...(this.props.options ? this.props.options.flatpickrOptions : {})
    }

    this.flatpickr = new this.Flatpickr(this.wrapper.current, this.options)
    this.flatpickr.calendarContainer.classList.add('tocco-ui-theme')
    this.flatpickr.calendarContainer.style.fontFamily = theme.fontFamily('regular')(this.props)

    if (this.props.initialized) {
      this.props.initialized()
    }
    this.setState({altInput: this.flatpickr.altInput.value})
  }

  getLocale = localeCode => {
    if (this.localeMap) {
      let locale = this.localeMap[localeCode]

      // Fallback english
      if (!locale && this.Flatpickr) {
        locale = this.Flatpickr.l10ns.en
      }
      return locale
    }
    return null
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (this.Flatpickr && this.flatpickr) {
      const optionsDiff = js.difference(this.props.options.flatpickrOptions, prevProps.options.flatpickrOptions)
      if (Object.keys(optionsDiff).length > 0) {
        Object.entries(optionsDiff).forEach(([key, value]) => {
          this.flatpickr.set(key, value)
        })
        this.flatpickr.redraw()
      }

      if (!_isEqual(prevProps.intl.locale, this.props.intl.locale)) {
        const locale = this.getLocale(this.props.intl.locale)
        this.Flatpickr.localize(locale)
        this.flatpickr.set('locale', locale)
        this.flatpickr.redraw()
      }
      if (!_isEqual(prevProps.value, this.props.value)) {
        this.flatpickr.setDate(this.props.value, false)
        this.flatpickr.redraw()
      }
    }
  }

  componentWillUnmount() {
    this.componentIsUnmounted = true
    if (this.flatpickr) {
      this.flatpickr.destroy()
    }
  }

  handleOnChange = selectedDates => {
    const isoStrings = selectedDates.map(date => date.toISOString())
    this.setState({altInput: this.flatpickr.altInput.value})
    if (this.hasValue() || isoStrings.length !== 0) {
      this.props.onChange(isoStrings)
    }
  }

  hasValue = () => {
    return this.props.value && this.props.value.length > 0 && this.props.value.every(v => v)
  }

  handleOnBlur = () => {
    if (this.props.onBlur) {
      const altValue = this.flatpickr.altInput.value
      this.props.onBlur(altValue, this.flatpickr.selectedDates, r => this.flatpickr.setDate(r, true))
    }
  }

  render() {
    return (
      <StyledDateAbstractWrapper
        data-wrap
        onBlur={this.handleOnBlur}
        immutable={this.props.immutable}
        ref={this.wrapper}
        id={this.props.id}
      >
        <StyledDateAbstractInput
          type="text"
          data-input
          immutable={this.props.immutable}
          {...(this.props.options ? {placeholder: this.props.options.placeholderText} : {})}
        />
        <StyledDateAbstractInput
          disabled
          immutable={this.props.immutable}
          value={this.state.altInput}
        />
      </StyledDateAbstractWrapper>
    )
  }
}

DateAbstract.propTypes = {
  id: PropTypes.string,
  intl: intlShape.isRequired,
  onBlur: PropTypes.func,
  onChange: PropTypes.func.isRequired,
  value: PropTypes.arrayOf(PropTypes.string),
  options: PropTypes.shape({
    placeholderText: PropTypes.string,
    flatpickrOptions: PropTypes.object
  }),
  immutable: PropTypes.bool,
  initialized: PropTypes.func,
  events: PropTypes.shape({
    onFocus: PropTypes.func
  })
}

export default withTheme(injectIntl(DateAbstract))
<commit_msg>refactor(tocco-ui): make date field focusable with id<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import {injectIntl, intlShape} from 'react-intl'
import _isEqual from 'lodash/isEqual'
import {withTheme} from 'styled-components'
import {js} from 'tocco-util'

import {theme} from '../../utilStyles'
import {
  StyledDateAbstractInput,
  StyledDateAbstractWrapper
} from './StyledDateAbstract'

class DateAbstract extends React.Component {
  Flatpickr = null
  componentIsUnmounted = false

  constructor(props) {
    super(props)
    this.wrapper = React.createRef()

    this.state = {altInput: ''}

    import(/* webpackChunkName: "flatpickr" */ '!style-loader!css-loader!flatpickr/dist/themes/light.css')

    Promise.all([
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr'),
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr/dist/l10n/de.js'),
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr/dist/l10n/fr.js'),
      import(/* webpackChunkName: "flatpickr" */ 'flatpickr/dist/l10n/it.js')
    ]).then(response => {
      if (!this.componentIsUnmounted) {
        this.Flatpickr = response[0].default
        this.localeMap = {
          'de-CH': response[1].German,
          'de': response[1].German,
          'fr': response[2].French,
          'it': response[3].Italian
        }

        this.initializeFlatPickr()
      }
    })
  }

  initializeFlatPickr = flatpickr => {
    const locale = this.localeMap[this.props.intl.locale]

    this.options = {
      wrap: true,
      onChange: this.handleOnChange,
      altInput: true,
      altInputClass: '',
      clickOpens: !this.props.immutable,
      defaultDate: this.props.value,
      ...(locale ? {locale} : {}),
      ...(this.props.options ? this.props.options.flatpickrOptions : {})
    }

    this.flatpickr = new this.Flatpickr(this.wrapper.current, this.options)
    this.flatpickr.calendarContainer.classList.add('tocco-ui-theme')
    this.flatpickr.calendarContainer.style.fontFamily = theme.fontFamily('regular')(this.props)

    if (this.props.initialized) {
      this.props.initialized()
    }
    this.setState({altInput: this.flatpickr.altInput.value})
  }

  getLocale = localeCode => {
    if (this.localeMap) {
      let locale = this.localeMap[localeCode]

      // Fallback english
      if (!locale && this.Flatpickr) {
        locale = this.Flatpickr.l10ns.en
      }
      return locale
    }
    return null
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (this.Flatpickr && this.flatpickr) {
      const optionsDiff = js.difference(this.props.options.flatpickrOptions, prevProps.options.flatpickrOptions)
      if (Object.keys(optionsDiff).length > 0) {
        Object.entries(optionsDiff).forEach(([key, value]) => {
          this.flatpickr.set(key, value)
        })
        this.flatpickr.redraw()
      }

      if (!_isEqual(prevProps.intl.locale, this.props.intl.locale)) {
        const locale = this.getLocale(this.props.intl.locale)
        this.Flatpickr.localize(locale)
        this.flatpickr.set('locale', locale)
        this.flatpickr.redraw()
      }
      if (!_isEqual(prevProps.value, this.props.value)) {
        this.flatpickr.setDate(this.props.value, false)
        this.flatpickr.redraw()
      }
    }
  }

  componentWillUnmount() {
    this.componentIsUnmounted = true
    if (this.flatpickr) {
      this.flatpickr.destroy()
    }
  }

  handleOnChange = selectedDates => {
    const isoStrings = selectedDates.map(date => date.toISOString())
    this.setState({altInput: this.flatpickr.altInput.value})
    if (this.hasValue() || isoStrings.length !== 0) {
      this.props.onChange(isoStrings)
    }
  }

  hasValue = () => {
    return this.props.value && this.props.value.length > 0 && this.props.value.every(v => v)
  }

  handleOnBlur = () => {
    if (this.props.onBlur) {
      const altValue = this.flatpickr.altInput.value
      this.props.onBlur(altValue, this.flatpickr.selectedDates, r => this.flatpickr.setDate(r, true))
    }
  }

  render() {
    return (
      <div
        id={this.props.id}
        tabIndex="-1"
        onFocus={() => {
          const inputElement = this.wrapper.current.getElementsByClassName('flatpickr-input')[1]
          inputElement.focus()
          setTimeout(() => {
            this.flatpickr.open()
          })
        }}
      >
        <StyledDateAbstractWrapper
          data-wrap
          onBlur={this.handleOnBlur}
          immutable={this.props.immutable}
          ref={this.wrapper}

        >
          <StyledDateAbstractInput
            type="text"
            data-input
            immutable={this.props.immutable}
            {...(this.props.options ? {placeholder: this.props.options.placeholderText} : {})}
          />
          <StyledDateAbstractInput
            disabled
            immutable={this.props.immutable}
            value={this.state.altInput}
          />
        </StyledDateAbstractWrapper>
      </div>
    )
  }
}

DateAbstract.propTypes = {
  id: PropTypes.string,
  intl: intlShape.isRequired,
  onBlur: PropTypes.func,
  onChange: PropTypes.func.isRequired,
  value: PropTypes.arrayOf(PropTypes.string),
  options: PropTypes.shape({
    placeholderText: PropTypes.string,
    flatpickrOptions: PropTypes.object
  }),
  immutable: PropTypes.bool,
  initialized: PropTypes.func,
  events: PropTypes.shape({
    onFocus: PropTypes.func
  })
}

export default withTheme(injectIntl(DateAbstract))
<|endoftext|>


<commit_before>module.exports = {
  normalizeEntityName: function () {
    // this prevents an error when the entityName is not specified (since that doesn't actually matter to us)
  },

  afterInstall: function () {
    return this.addBowerPackageToProject('zxcvbn', '4.0.1');
  }
};
<commit_msg>Update zxcvbn version<commit_after>module.exports = {
  normalizeEntityName: function () {
    // this prevents an error when the entityName is not specified (since that doesn't actually matter to us)
  },

  afterInstall: function () {
    return this.addBowerPackageToProject('zxcvbn', '4.1.1');
  }
};
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import Autosuggest from 'react-autosuggest'

import ButtonLink from '../../ButtonLink'
import {
  StyledLocationEdit,
  StyledPostcodeInput
} from './StyledLocationEdit'
import IconTocco from '../../IconTocco'

export const getGoogleMapsAddress = locationInput => {
  const mapsBaseAddress = `https://www.google.com/maps/search/?api=1&query=`

  const queryParams = locationInput
    ? Object.values(locationInput)
      .filter(value => value)
      .map(value => (typeof value === 'object' && value.display) ? value.display : value)
      .join('+')
    : ''

  return `${mapsBaseAddress}${queryParams}`
}

class LocationEdit extends React.Component {
  returnGetSuggestion = attr => suggestion => suggestion[attr]

  renderSuggestion = suggestion => {
    const cantonString = suggestion.state ? `- ${suggestion.state}` : ''
    const countryString = suggestion.country ? `/ ${suggestion.country.display}` : ''

    return {suggestion.postcode} {suggestion.city} {cantonString} {countryString}
  }

  onChange = field => (event, {newValue}) => {
    this.props.onChange({[field]: newValue})
  }

  returnSuggestionsContainer = ({containerProps, children}) =>

      {this.props.options.isLoading ?
<IconTocco/>
 : children}

  onSuggestionSelected = (event, {suggestion}) => {
    this.props.onChange(suggestion)
  }

  returnOnSuggestionFetchRequested = field => ({value}) => {
    this.props.options.fetchSuggestions({[field]: value}, this.props.value.country)
  }

  onSuggestionsClearRequested = () => {}

  showGoogleMaps = value => value && Object.values(value).some(val => val)

  render() {
    const inputPropsZip = {
      value: this.props.value.postcode || '',
      onChange: this.onChange('postcode'),
      disabled: this.props.readOnly
    }

    const inputPropsCity = {
      value: this.props.value.city || '',
      onChange: this.onChange('city'),
      disabled: this.props.readOnly
    }

    return (

        <Autosuggest
          suggestions={this.props.options.suggestions || []}
          onSuggestionsFetchRequested={this.returnOnSuggestionFetchRequested('city')}
          getSuggestionValue={this.returnGetSuggestion('city')}
          renderSuggestion={this.renderSuggestion}
          onSuggestionsClearRequested={this.onSuggestionsClearRequested}
          inputProps={inputPropsCity}
          renderSuggestionsContainer={this.returnSuggestionsContainer}
          onSuggestionSelected={this.onSuggestionSelected}
          focusInputOnSuggestionClick={false}
          shouldRenderSuggestions={() => !this.props.readOnly}
        />
        {this.showGoogleMaps(this.props.value)
            && <ButtonLink
              href={getGoogleMapsAddress(this.props.value)}
              icon="map-marked-alt"
              iconPosition="sole"
              look="ball"
              tabIndex={-1}
              target="_blank"
              dense={false}
              title={this.props.options.mapButtonTitle || 'Show on Maps'}
            />
        }
      </StyledLocationEdit>
    )
  }
}

const locationObjectPropType = PropTypes.shape({
  city: PropTypes.string,
  postcode: PropTypes.string,
  address: PropTypes.string,
  country: PropTypes.shape({
    display: PropTypes.string,
    key: PropTypes.string
  }),
  canton: PropTypes.string,
  district: PropTypes.string
})

LocationEdit.propTypes = {
  onChange: PropTypes.func,
  value: locationObjectPropType,
  options: PropTypes.shape({
    suggestions: PropTypes.arrayOf(locationObjectPropType),
    fetchSuggestions: PropTypes.func,
    isLoading: PropTypes.bool,
    mapButtonTitle: PropTypes.string
  }),
  name: PropTypes.string,
  id: PropTypes.string,
  readOnly: PropTypes.bool
}

export default LocationEdit
<commit_msg>refactor(tocco-ui): LocationEdit only show suggestion on input<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import Autosuggest from 'react-autosuggest'

import ButtonLink from '../../ButtonLink'
import {
  StyledLocationEdit,
  StyledPostcodeInput
} from './StyledLocationEdit'
import IconTocco from '../../IconTocco'

export const getGoogleMapsAddress = locationInput => {
  const mapsBaseAddress = `https://www.google.com/maps/search/?api=1&query=`

  const queryParams = locationInput
    ? Object.values(locationInput)
      .filter(value => value)
      .map(value => (typeof value === 'object' && value.display) ? value.display : value)
      .join('+')
    : ''

  return `${mapsBaseAddress}${queryParams}`
}

class LocationEdit extends React.Component {
  returnGetSuggestion = attr => suggestion => suggestion[attr]

  renderSuggestion = suggestion => {
    const cantonString = suggestion.state ? `- ${suggestion.state}` : ''
    const countryString = suggestion.country ? `/ ${suggestion.country.display}` : ''

    return {suggestion.postcode} {suggestion.city} {cantonString} {countryString}
  }

  onChange = field => (event, {newValue}) => {
    this.props.onChange({[field]: newValue})
  }

  returnSuggestionsContainer = ({containerProps, children}) =>

      {this.props.options.isLoading ?
<IconTocco/>
 : children}

  onSuggestionSelected = (event, {suggestion}) => {
    this.props.onChange(suggestion)
  }

  returnOnSuggestionFetchRequested = field => ({value}) => {
    this.props.options.fetchSuggestions({[field]: value}, this.props.value.country)
  }

  onSuggestionsClearRequested = () => {}

  showGoogleMaps = value => value && Object.values(value).some(val => val)

  render() {
    const inputPropsZip = {
      value: this.props.value.postcode || '',
      onChange: this.onChange('postcode'),
      disabled: this.props.readOnly
    }

    const inputPropsCity = {
      value: this.props.value.city || '',
      onChange: this.onChange('city'),
      disabled: this.props.readOnly
    }

    return (

        <Autosuggest
          suggestions={this.props.options.suggestions || []}
          onSuggestionsFetchRequested={this.returnOnSuggestionFetchRequested('city')}
          getSuggestionValue={this.returnGetSuggestion('city')}
          renderSuggestion={this.renderSuggestion}
          onSuggestionsClearRequested={this.onSuggestionsClearRequested}
          inputProps={inputPropsCity}
          renderSuggestionsContainer={this.returnSuggestionsContainer}
          onSuggestionSelected={this.onSuggestionSelected}
          focusInputOnSuggestionClick={false}
          shouldRenderSuggestions={v => v && !this.props.readOnly}
        />
        {this.showGoogleMaps(this.props.value)
            && <ButtonLink
              href={getGoogleMapsAddress(this.props.value)}
              icon="map-marked-alt"
              iconPosition="sole"
              look="ball"
              tabIndex={-1}
              target="_blank"
              dense={false}
              title={this.props.options.mapButtonTitle || 'Show on Maps'}
            />
        }
      </StyledLocationEdit>
    )
  }
}

const locationObjectPropType = PropTypes.shape({
  city: PropTypes.string,
  postcode: PropTypes.string,
  address: PropTypes.string,
  country: PropTypes.shape({
    display: PropTypes.string,
    key: PropTypes.string
  }),
  canton: PropTypes.string,
  district: PropTypes.string
})

LocationEdit.propTypes = {
  onChange: PropTypes.func,
  value: locationObjectPropType,
  options: PropTypes.shape({
    suggestions: PropTypes.arrayOf(locationObjectPropType),
    fetchSuggestions: PropTypes.func,
    isLoading: PropTypes.bool,
    mapButtonTitle: PropTypes.string
  }),
  name: PropTypes.string,
  id: PropTypes.string,
  readOnly: PropTypes.bool
}

export default LocationEdit
<|endoftext|>


<commit_before>export { Params, Base };

import * as ValueMax from "../ValueMax.js";
import * as Weights from "../Weights.js";

/**
 * Embedding (2d) layer parameters.
 */
class Params extends Weights.Params {

  /**
   * @param {number} channelMultiplier
   *   Every input channel will be expanded into so many embedding channels. The outChannels (output channel count)
   * is always depending on channelMultiplier and equal to ( inChannels * channelMultiplier ). If null, it will be
   * extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   *
   * @override
   */
  init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    let parameterMap = new Map( [
      [ Weights.Params.Keys.inChannels,        inChannels ],
      [ Weights.Params.Keys.channelMultiplier, channelMultiplier ],

      // For an embedding layer, its output channel count always depends on channelMultiplier.
      [ Weights.Params.Keys.outChannels,       Infinity ],
    ] );

    return super.init( inputFloat32Array, byteOffsetBegin, parameterMap );
  }

}

/**
 * Embedding could achieve non-linear mapping (just like any perceptron). But it is achieved by lookup table (instead
 * of weighted sum, bias and activation function). This implies:
 *   - It may use more (CPU or GPU) memory, but may use less (CPU or GPU) computation.
 *   - It can only achieve channel expansion, and can not achieve channel aggregation. (because no weighted sum)
 *   - It can only represent context-independent (not context-dependent) information. (because no weighted sum)
 *   - It can only handle integer input (i.e. int32, not float32).
 *
 * It is useful as the first layer of text or image processing because their inputs are all integer (e.g. character codes,
 * word indices, color codes, etc). And, the first layer only needs carry context-independent information (and all the other
 * layers after it will produce context-dependent information).
 *
 * This object always accepts tensor3d (dtype = int32).
 *   - The axis 0 is height. (Image height) (Text lines and usually only 1 line.)
 *   - The axis 1 is width. (Image width) (Text length (e.g. character count).)
 *   - The axis 2 is channel. (Image color channel) (Text character code channel and usually only 1 channel.)
 *
 * An embedding layer contains one params (this.params) and inChannels embedding vocabulary tables.
 *   - Every input channel has one embedding vocabulary table.
 *   - Every embedding vocabulary table has vocabularyCountPerInputChannel vocabularies.
 *   - Every vocabulary has channelMultiplier embedding channels.
 *
 *
 *
 * @member {boolean} bEmbedVocabularyId
 *   If true, one of embedding channels will be an auto-generated vocabulary id (i.e. 0, 1, 2, ...). So only
 * ( channelMultiplier - 1 ) embedding channels will be extracted from inputFloat32Array. The extra vocabulary id
 * channel achieves residual connection. Residual connection means apply_and_destroy_or_keep() will append (concatenate)
 * input to output. Since apply_and_destroy_or_keep()'s input is just vocabulary id (one channel or multiple channels),
 * pre-embedded vocabulary id inside the embedding table acheives the same effect by less computation (but more memory).
 *
 * @member {number} vocabularyCountPerInputChannel
 *   Every input channel will have how many vocabularies. This is also vocabulary count per vocabulary table (because
 * every input channel has a vocabulary table). For an image data (R-G-B-A four channels), there will be 256
 * vocabularies per input channel because every channel is represented by one byte (8 bits) which has 2^8 = 256 kinds
 * of possible values.
 *
 * @member {number} channelMultiplier
 *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
 * embedding channels. It could be viewed as embeddingChannelCountPerInputChannel.
 */
class Base {

  /**
   * Generator for initializing this object.
   *
   * @param {ValueMax.Percentage.Aggregate} progressParent
   *   Some new progressToAdvance will be created and added to progressParent. The created progressToAdvance will be
   * increased when every time advanced. The progressParent.getRoot() will be returned when every time yield.
   *
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number} byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number} inChannels
   *   The input channel count.
   *
   * @param {number} channelMultiplier
   *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
   * embedding channels. The outChannels (output channel count) is always depending on channelMultiplier and equal
   * to ( inChannels * channelMultiplier ). If null, it will be extracted from inputFloat32Array (i.e. by evolution).
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep). For example, for the branch of step 0 of ShuffleNetV2.
   * For another example, the input image should be shared across many neural networks.
   *
   * @yield {ValueMax.Percentage.Aggregate}
   *   Yield ( value = progressParent.getRoot() ) when ( done = false ).
   *
   * @yield {boolean}
   *   Yield ( value = true ) when ( done = true ) successfully.
   *   Yield ( value = false ) when ( done = true ) failed.
   */
  * initer(
    progressParent,
    inputFloat32Array, byteOffsetBegin,
    inChannels, channelMultiplier = null, vocabularyCountPerInputChannel = 256, bEmbedVocabularyId = true,
    bKeepInputTensor
  ) {

    // Estimate the maximum value of progress.
    let progressMax =
      1             // for extracting parameters from inputFloat32Array.
      + inChannels  // for extracting vocabulary table of every input channel from inputFloat32Array.
      + inChannels; // for building vocabulary table tensor2d of every input channel.

    let progressRoot = progressParent.getRoot();
    let progressToAdvance = progressParent.addChild( new ValueMax.Percentage.Concrete( progressMax ) );

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    disposeTensors();
    this.params = this.vocabularyTables = null; // So that distinguishable if re-initialization failed.

    this.vocabularyCountPerInputChannel = vocabularyCountPerInputChannel;
    this.bEmbedVocabularyId = bEmbedVocabularyId;
    this.bKeepInputTensor = bKeepInputTensor;

    this.params = new Params();
    if ( !this.params.init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier ) )
      return false;

    ++progressToAdvance.value;
    yield progressRoot;  // Parameters extracted. Report progress.

    let channelMultiplier = this.channelMultiplier; // The real channelMultiplier. May be specified or extracted.
    if ( channelMultiplier < 1 )
      return false; // At least, there should be one embedding channel.

    //let vocabularyTableShape = [ vocabularyCountPerInputChannel, channelMultiplier ];
    let vocabularyTableShape_toExtract;
    if ( bEmbedVocabularyId ) {
      // If there will be an auto-generated vocabulary id embedding channel, extract one less channels from data.
      vocabularyTableShape_toExtract = [ vocabularyCountPerInputChannel, channelMultiplier - 1 ];
    } else {
      // Otherwise, all embedding channels are extracted from data.
      vocabularyTableShape_toExtract = [ vocabularyCountPerInputChannel, channelMultiplier ];
    }

    // Extract data of vocabulary tables from inputFloat32Array.
    this.vocabularyTables = new Array( inChannels );
    {
      let nextByteOffsetBegin = this.params.defaultByteOffsetEnd;
      for ( let i = 0; i < inChannels; ++i ) {
        this.vocabularyTables[ i ] = new Weights.Base();
        if ( !this.vocabularyTables[ i ].init( inputFloat32Array, nextByteOffsetBegin, null, 0, vocabularyTableShape_toExtract ) )
          return false;  // e.g. input array do not have enough data.
        nextByteOffsetBegin = this.vocabularyTables[ i ].defaultByteOffsetEnd;

        ++progressToAdvance.value;
        yield progressRoot;  // One vocabulary table extracted. Report progress.
      }
    }

    // Build tf.tensor of vocabulary tables.
    try {
      // Create vocabulary id list (tensor2d). (for concatenating with vocabulary table)
      let numberSequencer = new Array( vocabularyCountPerInputChannel ).keys(); // Generator: 0, 1, 2, ..., ( vocabularyCountPerInputChannel - 1 )
      const idsTensor2d = tf.tensor2d( [ ...numberSequencer ], [ vocabularyCountPerInputChannel, 1 ] );

      try {
        let theLastAxisId = ( vocabularyTableShape_toExtract.length - 1 ); // e.g. will be 1 for tensor2d.
        this.vocabularyTablesTensor2dArray = new Array( this.vocabularyTables.length );
        for ( let i = 0; i < this.vocabularyTables.length; ++i ) {

          // Create an embedding vocabulary table (without vocabulary id).
          const vocabularyTableTensor2dWithoutIds = tf.tensor2d( this.vocabularyTables[ i ], vocabularyTableShape_toExtract );

          if ( bEmbedVocabularyId ) {
            try { // Concatenate vocabulary id prefix vocabulary table (as residual connection).
              this.vocabularyTablesTensor2dArray[ i ] = idsTensor2d.concat( vocabularyTableTensor2dWithoutIds, theLastAxisId );
            } catch ( e ) {
              return false; // e.g. out of (GPU) memory.
            } finally {
              vocabularyTableTensor2dWithoutIds.dispose();
            }
          } else { // No vocabulary id.
            this.vocabularyTablesTensor2dArray[ i ] = vocabularyTableTensor2dWithoutIds;
          }

          ++progressToAdvance.value;
          yield progressRoot;  // One vocabulary table tensor2d built. Report progress.
        }

      } catch ( e ) {
        return false; // e.g. out of (GPU) memory.
      } finally {
        idsTensor2d.dispose();
      }

    } catch ( e ) {
      return false; // e.g. out of (GPU) memory.
    }

    return true; // Initialized successfully.
  }

  /** @return {boolean} Return true if this object initialized (i.e. initer()) successfully. */
  isValid() {
    if ( this.vocabularyTablesTensor2dArray )
      if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ] ) // At least, there should be one vocabulary table.
        if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ].isValid() )  // the last vocabulary table is valid.
          return true;
    return false;
  }

  /** Release tf.tensor. */
  disposeTensors() {
    if ( this.vocabularyTablesTensor2dArray ) {
      Base.disposeTensorArray_NotNull( this.vocabularyTablesTensor2dArray );
      this.vocabularyTablesTensor2dArray = null;
    }
  }

  /**
   * Process the input and produce output by using the weights of this neural network layer.
   *
   * @param {tf.tensor3d} inputTensor3d
   *   A tensor3d data (e.g. height-width-color for color image, or 1-width-1 for text) with this.inChannels
   * (e.g. 4 for r-g-b-a, or 1 for text) channels. The inputTensor3d.dtype must be int32 (i.e. can not be float32)
   * so that they can be used as tf.gather()'s indices. If ( this.bKeepInputTensor == false ), this inputTensor3d
   * will be disposed. If ( this.bKeepInputTensor == true ), this inputTensor3d will be kept.
   *
   * @return {tf.tensor3d} The predicted output as tensor3d. Return null, if failed (e.g. out of GPU memory).
   */
  apply_and_destroy_or_keep( inputTensor3d ) {

//!!! ...unfinished... could use gahter, gather, concat instead of split, gather, concat?

    // For example, suppose input is a color image (i.e. height-width-color tensor3d). The last
    // axis is a 4 color (r-g-b-a) channel. Splitting along the last axis (the color channel)
    // results in an array [ r, g, b, a ] which has 4 tensor3d (in fact, they should be
    // viewed as tensor1d).
    let theLastAxisId = ( inputTensor3d.shape.length - 1 );  // Or, ( inputTensor3d.rank - 1 )

    // For a 4 color (r-g-b-a) channel image, splitCount will be 4.
    //
    // This should be the same as this.inChannels.
    let splitCount = inputTensor3d.shape[ theLastAxisId ];

    // Extract vocabulary indices from input.
    //
    // Split the last axis (of input) as many as the shape size (of the last axis) (i.e. become tensor2d).
    // In fact, the result is still tensor3d but has only one channel.
    const vocabularyIndicesOneChannelTensor3dArray = inputTensor3d.split( splitCount, theLastAxisId );

    try {
//!!! ...unfinished... could re-use this array shell (without re-allocating every time apply_and_destroy_or_keep() is called)?
      let embeddedTensor3dArray = new Array( vocabularyIndicesOneChannelTensor3dArray.length );

      try {

        // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
        // Every tensor3d (one channel) will be expanded to tensor3d (multiple channels).
        //
        // Note: this.vocabularyTablesTensor2dArray[] already be prefixed vocabulary id (when init()). So it
        // has residual connection in advance.
        for ( let channelIndex = 0; channelIndex < vocabularyIndicesOneChannelTensor3dArray.length; ++channelIndex ) {
          let oneChannelTensor3d = vocabularyIndicesOneChannelTensor3dArray[ channelIndex ];
          let embeddedTensor3d = this.vocabularyTablesTensor2dArray[ channelIndex ].gather( oneChannelTensor3d );
          embeddedTensor3dArray[ channelIndex ] = embeddedTensor3d;
        }

        // Concatenate along the last axis, so that it is still tensor3d but with embedded (more) channels in the last axis.
        let predictResult = tf.concat( embeddedTensor3dArray, theLastAxisId );

//!!! ...unfinished... could using function pointer to avoid conditional-branching?
        if ( !this.bKeepInputTensor )
          inputTensor3d.dispose();

        return predictResult;

      } catch ( e ) {
        throw e;

      } finally {
        Base.disposeTensorArray_NotNull( embeddedTensor3dArray ); // Avoid tensors' memory leakage.
        //embeddedTensor3dArray = null;
      }

    } catch ( e ) {
      throw e;

    } finally {
      Base.disposeTensorArray_NotNull( vocabularyIndicesOneChannelTensor3dArray ); // Avoid tensors' memory leakage.
      //vocabularyIndicesOneChannelTensor3dArray = null;
    }

//!!! ...unfinished... squeeze-and-excitation.
  }

  /**
   * Release an array of tf.tensor.
   *
   * This method is a little like tf.dispose() but can only handle one-layer array. But it should be faster than
   * tf.dispose( an_array ) because no dynamic memory allocation.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array contains tf.tensor to be disposed. The tensorArray itself can not be null (for reducing conditional-branch
   * to improve performance). It can not have nested array. Its element must be either tf.tensor or null.
   */
  static disposeTensorArray_NotNull( tensorArray ) {
    for ( let i = 0; i < tensorArray.length; ++i ) {
      let t = tensorArray[ i ];
      if ( t )
        t.dispose();
    }
  }

  get byteOffsetBegin() { return this.params.defaultByteOffsetBegin; }
  get byteOffsetEnd()   { return this.vocabularyTables[ this.params.inChannels - 1 ].defaultByteOffsetEnd; }

  get inChannels()        { return this.params.inChannels; }
  get channelMultiplier() { return this.params.channelMultiplier; }
  get outChannels()       { return this.params.outChannels; }
}
<commit_msg>Update Embedding2d.js<commit_after>export { Params, Base };

import * as ValueMax from "../ValueMax.js";
import * as Weights from "../Weights.js";

/**
 * Embedding (2d) layer parameters.
 */
class Params extends Weights.Params {

  /**
   * @param {number} channelMultiplier
   *   Every input channel will be expanded into so many embedding channels. The outChannels (output channel count)
   * is always depending on channelMultiplier and equal to ( inChannels * channelMultiplier ). If null, it will be
   * extracted from inputFloat32Array (i.e. by evolution).
   *
   * @return {boolean} Return false, if initialization failed.
   *
   * @override
   */
  init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier = null ) {

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    let parameterMap = new Map( [
      [ Weights.Params.Keys.inChannels,        inChannels ],
      [ Weights.Params.Keys.channelMultiplier, channelMultiplier ],

      // For an embedding layer, its output channel count always depends on channelMultiplier.
      [ Weights.Params.Keys.outChannels,       Infinity ],
    ] );

    return super.init( inputFloat32Array, byteOffsetBegin, parameterMap );
  }

}

/**
 * Embedding could achieve non-linear mapping (just like any perceptron). But it is achieved by lookup table (instead
 * of weighted sum, bias and activation function). This implies:
 *   - It may use more (CPU or GPU) memory, but may use less (CPU or GPU) computation.
 *   - It can only achieve channel expansion, and can not achieve channel aggregation. (because no weighted sum)
 *   - It can only represent context-independent (not context-dependent) information. (because no weighted sum)
 *   - It can only handle integer input (i.e. int32, not float32).
 *
 * It is useful as the first layer of text or image processing because their inputs are all integer (e.g. character codes,
 * word indices, color codes, etc). And, the first layer only needs carry context-independent information (and all the other
 * layers after it will produce context-dependent information).
 *
 * This object always accepts tensor3d (dtype = int32).
 *   - The axis 0 is height. (Image height) (Text lines and usually only 1 line.)
 *   - The axis 1 is width. (Image width) (Text length (e.g. character count).)
 *   - The axis 2 is channel. (Image color channel) (Text character code channel and usually only 1 channel.)
 *
 * An embedding layer contains one params (this.params) and inChannels embedding vocabulary tables.
 *   - Every input channel has one embedding vocabulary table.
 *   - Every embedding vocabulary table has vocabularyCountPerInputChannel vocabularies.
 *   - Every vocabulary has channelMultiplier embedding channels.
 *
 *
 *
 * @member {number} vocabularyCountPerInputChannel
 *   Every input channel will have how many vocabularies. This is also vocabulary count per vocabulary table (because
 * every input channel has a vocabulary table). For an image data (R-G-B-A four channels), there will be 256
 * vocabularies per input channel because every channel is represented by one byte (8 bits) which has 2^8 = 256 kinds
 * of possible values.
 *
 * @member {boolean} bEmbedVocabularyId
 *   If true, one of embedding channels will be an auto-generated vocabulary id (i.e. 0, 1, 2, ...). So only
 * ( channelMultiplier - 1 ) embedding channels will be extracted from inputFloat32Array. The extra vocabulary id
 * channel achieves residual connection. Residual connection means apply_and_destroy_or_keep() will append (concatenate)
 * input to output. Since apply_and_destroy_or_keep()'s input is just vocabulary id (one channel or multiple channels),
 * pre-embedded vocabulary id inside the embedding table acheives the same effect by less computation (but more memory).
 *
 * @member {number} channelMultiplier
 *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
 * embedding channels. It could be viewed as embeddingChannelCountPerInputChannel.
 */
class Base {

  /**
   * Generator for initializing this object.
   *
   * @param {ValueMax.Percentage.Aggregate} progressParent
   *   Some new progressToAdvance will be created and added to progressParent. The created progressToAdvance will be
   * increased when every time advanced. The progressParent.getRoot() will be returned when every time yield.
   *
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number} byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number} inChannels
   *   The input channel count.
   *
   * @param {number} channelMultiplier
   *   Every vocabulary will have how many embedding channels. Every input channel will be expanded into so many
   * embedding channels. The outChannels (output channel count) is always depending on channelMultiplier and equal
   * to ( inChannels * channelMultiplier ). If null, it will be extracted from inputFloat32Array (i.e. by evolution).
   *
   * @param {boolean} bKeepInputTensor
   *   If true, apply_and_destroy_or_keep() will not dispose inputTensor (i.e. keep). For example, for the branch of step 0 of ShuffleNetV2.
   * For another example, the input image should be shared across many neural networks.
   *
   * @yield {ValueMax.Percentage.Aggregate}
   *   Yield ( value = progressParent.getRoot() ) when ( done = false ).
   *
   * @yield {boolean}
   *   Yield ( value = true ) when ( done = true ) successfully.
   *   Yield ( value = false ) when ( done = true ) failed.
   */
  * initer(
    progressParent,
    inputFloat32Array, byteOffsetBegin,
    inChannels, channelMultiplier = null, vocabularyCountPerInputChannel = 256, bEmbedVocabularyId = true,
    bKeepInputTensor
  ) {

    // Estimate the maximum value of progress.
    let progressMax =
      1             // for extracting parameters from inputFloat32Array.
      + inChannels  // for extracting vocabulary table of every input channel from inputFloat32Array.
      + inChannels; // for building vocabulary table tensor2d of every input channel.

    let progressRoot = progressParent.getRoot();
    let progressToAdvance = progressParent.addChild( new ValueMax.Percentage.Concrete( progressMax ) );

//!!! ...unfinished...
// inverted residual connection (by add or by concatenate) ? (dense net)
// squeeze-and-excitation ?
// Shuffled Grouped Pointwise Convolution ... ? (by tf.gather() ?)

    disposeTensors();
    this.params = this.vocabularyTables = null; // So that distinguishable if re-initialization failed.

    this.vocabularyCountPerInputChannel = vocabularyCountPerInputChannel;
    this.bEmbedVocabularyId = bEmbedVocabularyId;
    this.bKeepInputTensor = bKeepInputTensor;

    this.params = new Params();
    if ( !this.params.init( inputFloat32Array, byteOffsetBegin, inChannels, channelMultiplier ) )
      return false;

    ++progressToAdvance.value;
    yield progressRoot;  // Parameters extracted. Report progress.

    let channelMultiplier = this.channelMultiplier; // The real channelMultiplier. May be specified or extracted.
    if ( channelMultiplier < 1 )
      return false; // At least, there should be one embedding channel.

    //let vocabularyTableShape = [ vocabularyCountPerInputChannel, channelMultiplier ];
    let vocabularyTableShape_toExtract;
    if ( bEmbedVocabularyId ) {
      // If there will be an auto-generated vocabulary id embedding channel, extract one less channels from data.
      vocabularyTableShape_toExtract = [ vocabularyCountPerInputChannel, channelMultiplier - 1 ];
    } else {
      // Otherwise, all embedding channels are extracted from data.
      vocabularyTableShape_toExtract = [ vocabularyCountPerInputChannel, channelMultiplier ];
    }

    // Extract data of vocabulary tables from inputFloat32Array.
    this.vocabularyTables = new Array( inChannels );
    {
      let nextByteOffsetBegin = this.params.defaultByteOffsetEnd;
      for ( let i = 0; i < inChannels; ++i ) {
        this.vocabularyTables[ i ] = new Weights.Base();
        if ( !this.vocabularyTables[ i ].init( inputFloat32Array, nextByteOffsetBegin, null, 0, vocabularyTableShape_toExtract ) )
          return false;  // e.g. input array do not have enough data.
        nextByteOffsetBegin = this.vocabularyTables[ i ].defaultByteOffsetEnd;

        ++progressToAdvance.value;
        yield progressRoot;  // One vocabulary table extracted. Report progress.
      }
    }

    // Build tf.tensor of vocabulary tables.
    try {
      // Create vocabulary id list (tensor2d). (for concatenating with vocabulary table)
      let numberSequencer = new Array( vocabularyCountPerInputChannel ).keys(); // Generator: 0, 1, 2, ..., ( vocabularyCountPerInputChannel - 1 )
      const idsTensor2d = tf.tensor2d( [ ...numberSequencer ], [ vocabularyCountPerInputChannel, 1 ] );

      try {
        let theLastAxisId = ( vocabularyTableShape_toExtract.length - 1 ); // e.g. will be 1 for tensor2d.
        this.vocabularyTablesTensor2dArray = new Array( this.vocabularyTables.length );
        for ( let i = 0; i < this.vocabularyTables.length; ++i ) {

          // Create an embedding vocabulary table (without vocabulary id).
          const vocabularyTableTensor2dWithoutIds = tf.tensor2d( this.vocabularyTables[ i ], vocabularyTableShape_toExtract );

          if ( bEmbedVocabularyId ) {
            try { // Concatenate vocabulary id prefix vocabulary table (as residual connection).
              this.vocabularyTablesTensor2dArray[ i ] = idsTensor2d.concat( vocabularyTableTensor2dWithoutIds, theLastAxisId );
            } finally {
              vocabularyTableTensor2dWithoutIds.dispose();
            }
          } else { // No vocabulary id.
            this.vocabularyTablesTensor2dArray[ i ] = vocabularyTableTensor2dWithoutIds;
          }

          ++progressToAdvance.value;
          yield progressRoot;  // One vocabulary table tensor2d built. Report progress.
        }

      } finally {
        idsTensor2d.dispose();
      }

    } catch ( e ) {
      return false; // e.g. out of (GPU) memory.
    }

    return true; // Initialized successfully.
  }

  /** @return {boolean} Return true if this object initialized (i.e. initer()) successfully. */
  isValid() {
    if ( this.vocabularyTablesTensor2dArray )
      if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ] ) // At least, there should be one vocabulary table.
        if ( this.vocabularyTablesTensor2dArray[ this.params.inChannels - 1 ].isValid() )  // the last vocabulary table is valid.
          return true;
    return false;
  }

  /** Release tf.tensor. */
  disposeTensors() {
    if ( this.vocabularyTablesTensor2dArray ) {
      Base.disposeTensorArray_NotNull( this.vocabularyTablesTensor2dArray );
      this.vocabularyTablesTensor2dArray = null;
    }
  }

  /**
   * Process the input and produce output by using the weights of this neural network layer.
   *
   * @param {tf.tensor3d} inputTensor3d
   *   A tensor3d data (e.g. height-width-color for color image, or 1-width-1 for text) with this.inChannels
   * (e.g. 4 for r-g-b-a, or 1 for text) channels. The inputTensor3d.dtype must be int32 (i.e. can not be float32)
   * so that they can be used as tf.gather()'s indices. If ( this.bKeepInputTensor == false ), this inputTensor3d
   * will be disposed. If ( this.bKeepInputTensor == true ), this inputTensor3d will be kept.
   *
   * @return {tf.tensor3d} The predicted output as tensor3d. Return null, if failed (e.g. out of GPU memory).
   */
  apply_and_destroy_or_keep( inputTensor3d ) {

//!!! ...unfinished... could use gahter, gather, concat instead of split, gather, concat?

    // For example, suppose input is a color image (i.e. height-width-color tensor3d). The last
    // axis is a 4 color (r-g-b-a) channel. Splitting along the last axis (the color channel)
    // results in an array [ r, g, b, a ] which has 4 tensor3d (in fact, they should be
    // viewed as tensor1d).
    let theLastAxisId = ( inputTensor3d.shape.length - 1 );  // Or, ( inputTensor3d.rank - 1 )

    // For a 4 color (r-g-b-a) channel image, splitCount will be 4.
    //
    // This should be the same as this.inChannels.
    let splitCount = inputTensor3d.shape[ theLastAxisId ];

    // Extract vocabulary indices from input.
    //
    // Split the last axis (of input) as many as the shape size (of the last axis) (i.e. become tensor2d).
    // In fact, the result is still tensor3d but has only one channel.
    const vocabularyIndicesOneChannelTensor3dArray = inputTensor3d.split( splitCount, theLastAxisId );

    try {
//!!! ...unfinished... could re-use this array shell (without re-allocating every time apply_and_destroy_or_keep() is called)?
      let embeddedTensor3dArray = new Array( vocabularyIndicesOneChannelTensor3dArray.length );

      try {

        // Embedding (looking up different vocabulary tables according to channel index of vocabulary indices).
        // Every tensor3d (one channel) will be expanded to tensor3d (multiple channels).
        //
        // Note: this.vocabularyTablesTensor2dArray[] already be prefixed vocabulary id (when init()). So it
        // has residual connection in advance.
        for ( let channelIndex = 0; channelIndex < vocabularyIndicesOneChannelTensor3dArray.length; ++channelIndex ) {
          let oneChannelTensor3d = vocabularyIndicesOneChannelTensor3dArray[ channelIndex ];
          let embeddedTensor3d = this.vocabularyTablesTensor2dArray[ channelIndex ].gather( oneChannelTensor3d );
          embeddedTensor3dArray[ channelIndex ] = embeddedTensor3d;
        }

        // Concatenate along the last axis, so that it is still tensor3d but with embedded (more) channels in the last axis.
        let predictResult = tf.concat( embeddedTensor3dArray, theLastAxisId );

//!!! ...unfinished... could using function pointer to avoid conditional-branching?
        if ( !this.bKeepInputTensor )
          inputTensor3d.dispose();

        return predictResult;

      } catch ( e ) {
        throw e;

      } finally {
        Base.disposeTensorArray_NotNull( embeddedTensor3dArray ); // Avoid tensors' memory leakage.
        //embeddedTensor3dArray = null;
      }

    } catch ( e ) {
      throw e;

    } finally {
      Base.disposeTensorArray_NotNull( vocabularyIndicesOneChannelTensor3dArray ); // Avoid tensors' memory leakage.
      //vocabularyIndicesOneChannelTensor3dArray = null;
    }

//!!! ...unfinished... squeeze-and-excitation.
  }

  /**
   * Release an array of tf.tensor.
   *
   * This method is a little like tf.dispose() but can only handle one-layer array. But it should be faster than
   * tf.dispose( an_array ) because no dynamic memory allocation.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array contains tf.tensor to be disposed. The tensorArray itself can not be null (for reducing conditional-branch
   * to improve performance). It can not have nested array. Its element must be either tf.tensor or null.
   */
  static disposeTensorArray_NotNull( tensorArray ) {
    for ( let i = 0; i < tensorArray.length; ++i ) {
      let t = tensorArray[ i ];
      if ( t )
        t.dispose();
    }
  }

  get byteOffsetBegin() { return this.params.defaultByteOffsetBegin; }
  get byteOffsetEnd()   { return this.vocabularyTables[ this.params.inChannels - 1 ].defaultByteOffsetEnd; }

  get inChannels()        { return this.params.inChannels; }
  get channelMultiplier() { return this.params.channelMultiplier; }
  get outChannels()       { return this.params.outChannels; }
}
<|endoftext|>


<commit_before>'use strict';

var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
    regPathInstructions = /([MmLlHhVvCcSsQqTtAaZz])\s*/,
    regPathData = /(?=-)|[\s,]+/,
    pathElems = ['path', 'glyph', 'missing-glyph'],
    hasMarkerMid;

/**
 * Convert absolute Path to relative,
 * collapse repeated instructions,
 * detect and convert Lineto shorthands,
 * remove useless instructions like "l0,0",
 * trim useless delimiters and leading zeros,
 * decrease accuracy of floating-point numbers.
 *
 * @see http://www.w3.org/TR/SVG/paths.html#PathData
 *
 * @param {Object} item current iteration item
 * @param {Object} params plugin params
 * @return {Boolean} if false, item will be filtered out
 *
 * @author Kir Belevich
 */
exports.convertPathData = function(item, params) {

    if (item.isElem(pathElems) && item.hasAttr('d')) {

        hasMarkerMid = item.hasAttr('marker-mid');

        var data = path2js(item.attr('d').value);

        if (data.length) {
            data = convertToRelative(data);

            data = filters(data, params);

            item.attr('d').value = js2path(data, params);
        }

    }

};

/**
 * Convert path string to JS representation.
 *
 * @param {String} pathString input string
 * @param {Object} params plugin params
 *
 * @return {Array} output array
 */
function path2js(pathString) {

        // JS representation of the path data
    var path = [],
        // current instruction context
        instruction;

    // splitting path string into array like ['M', '10 50', 'L', '20 30']
    pathString.split(regPathInstructions).forEach(function(data) {
        if (data) {
            // instruction item
            if (regPathInstructions.test(data)) {
                instruction = data;

                // z - instruction w/o data
                if ('Zz'.indexOf(instruction) > -1) {
                    path.push({
                        instruction: 'z'
                    });
                }
            // data item
            } else {

                // M 35.898 14.374 L 35.898 14.374 → M35.898 14.374L35.898 14.374
                data = data.trim().split(regPathData).map(function(str) {
                    return +str;
                });

                // very stupid defense strategy
                if (typeof data[0] === 'number' && !isNaN(data[0])) {

                    // deal with very first 'Mm' and multiple points data
                    if ('Mm'.indexOf(instruction) > -1) {

                        path.push({
                            instruction: instruction,
                            data: data.splice(0, 2)
                        });

                        if (data.length) {
                            instruction = instruction === instruction.toLowerCase() ? 'l' : 'L';
                        }

                    }

                    var pair = 0;

                    if ('HhVv'.indexOf(instruction) > -1) {
                        pair = 1;
                    } else if ('LlTt'.indexOf(instruction) > -1) {
                        pair = 2;
                    } else if ('QqSs'.indexOf(instruction) > -1) {
                        pair = 4;
                    } else if ('Cc'.indexOf(instruction) > -1) {
                        pair = 6;
                    } else if ('Aa'.indexOf(instruction) > -1) {
                        pair = 7;
                    }

                    while(data.length) {
                        path.push({
                            instruction: instruction,
                            data: data.splice(0, pair)
                        });
                    }

                }

            }
        }
    });

    return path;

}

/**
 * Convert absolute path data coordinates to relative.
 *
 * @param {Array} path input path data
 * @param {Object} params plugin params
 *
 * @return {Array} output path data
 */
function convertToRelative(path) {

    var instruction,
        data,
        newPoint,
        point = [0, 0];

    path.forEach(function(item) {

        instruction = item.instruction;
        data = item.data;

        // data !== !z
        if (data) {

            // already relative
            // recalculate current point
            if ('mcslqta'.indexOf(instruction) > -1) {

                newPoint = data.slice(-2);

                point[0] += newPoint[0];
                point[1] += newPoint[1];

            } else if (instruction === 'h') {

                point[0] += data[0];

            } else if (instruction === 'v') {

                point[1] += data[0];

            }

            // convert absolute path data coordinates to relative
            // M → m
            // L → l
            // T → t
            if ('MLT'.indexOf(instruction) > -1) {

                // don't convert M if point is [0, 0]
                if (instruction !== 'M' || point[0] !== 0 && point[1] !== 0) {
                    instruction = instruction.toLowerCase();
                }

                // x y
                // 0 1
                data[0] -= point[0];
                data[1] -= point[1];

                point[0] += data[0];
                point[1] += data[1];

            // C → c
            } else if (instruction === 'C') {

                instruction = 'c';

                // x1 y1 x2 y2 x y
                // 0  1  2  3  4 5
                data[0] -= point[0];
                data[1] -= point[1];
                data[2] -= point[0];
                data[3] -= point[1];
                data[4] -= point[0];
                data[5] -= point[1];

                point[0] += data[4];
                point[1] += data[5];

            // S → s
            // Q → q
            } else if ('SQ'.indexOf(instruction) > -1) {

                instruction = instruction.toLowerCase();

                // x1 y1 x y
                // 0  1  2 3
                data[0] -= point[0];
                data[1] -= point[1];
                data[2] -= point[0];
                data[3] -= point[1];

                point[0] += data[2];
                point[1] += data[3];

            // A → a
            } else if (instruction === 'A') {

                instruction = 'a';

                // rx ry x-axis-rotation large-arc-flag sweep-flag x y
                // 0  1  2               3              4          5 6
                data[5] -= point[0];
                data[6] -= point[1];

                point[0] += data[5];
                point[1] += data[6];

            // H → h
            } else if (instruction === 'H') {

                instruction = 'h';

                data[0] -= point[0];

                point[0] += data[0];

            // V → v
            } else if (instruction === 'V') {

                instruction = 'v';

                data[0] -= point[1];

                point[1] += data[0];

            }

            item.instruction = instruction;
            item.data = data;
            item.point = point.slice(0);

        }

        // !data === z, reset current point
        else {
            point = [0, 0];
        }

    });

    return path;

}

/**
 * Main filters loop.
 *
 * @param {Array} path input path data
 * @param {Object} params plugin params
 *
 * @return {Array} output path data
 */
function filters(path, params) {

    var instruction,
        data,
        point = [0, 0],
        prev = {
            point: [0, 0]
        };

    path = path.filter(function(item) {

        instruction = item.instruction;
        data = item.data;
        point = item.point;

        if (data) {

            if (params.floatPrecision) {
                data = roundData(data, params.floatPrecision);
            }

            // convert straight curves into lines segments
            if (params.straightCurves) {

                // c
                if (
                    instruction === 'c' &&
                    isCurveStraightLine(
                        [ 0, data[0], data[2], data[4] ],
                        [ 0, data[1], data[3], data[5] ]
                    )
                ) {
                    instruction = 'l';
                    data = data.slice(-2);
                }

                // s
                else if (instruction === 's') {

                    if (
                        isCurveStraightLine(
                            [ 0, data[0], data[2] ],
                            [ 0, data[1], data[3] ]
                        )
                    ) {
                        instruction = 'l';
                        data = data.slice(-2);
                    }

                }

                // q
                else if (
                    prev.item &&
                    instruction === 'q' &&
                    isCurveStraightLine(
                        [ 0, data[0], data[2] ],
                        [ 0, data[1], data[3] ]
                    )
                ) {
                    // save the original one for the future potential q + t conversion
                    item.original = {
                        instruction: instruction,
                        data: data
                    };

                    instruction = 'l';
                    data = data.slice(-2);
                }

                else if (instruction === 't') {

                    // q (original) + t
                    if (
                        prev.item &&
                        prev.item.original &&
                        prev.item.original.instruction === 'q'
                    ) {
                        if (isCurveStraightLine(
                            [ prev.item.original.data[0], prev.item.original.data[2], data[0] ],
                            [ prev.item.original.data[1], prev.item.original.data[3], data[1] ]
                        )) {
                            instruction = 'l';
                            data = data.slice(-2);
                        } else {
                            prev.item.instruction = 'q';
                            prev.item.data = prev.item.original.data;
                        }
                    }

                    // [^qt] + t
                    else if (!prev.item || 'qt'.indexOf(prev.item.instruction) === -1) {
                        instruction = 'l';
                        data = data.slice(-2);
                    }

                }

                // a
                else if (
                    instruction === 'a' &&
                    (data[0] === 0 || data[1] === 0)
                ) {
                    instruction = 'l';
                    data = data.slice(-2);
                }
            }

            // horizontal and vertical line shorthands
            // l 50 0 → h 50
            // l 0 50 → v 50
            if (
                params.lineShorthands &&
                instruction === 'l'
            ) {
                if (data[1] === 0) {
                    instruction = 'h';
                    data = [data[0]];
                } else if (data[0] === 0) {
                    instruction = 'v';
                    data = [data[1]];
                }
            }

            // convert curves into smooth shorthands
            if (params.curveSmoothShorthands && prev.item) {

                // curveto
                if (instruction === 'c') {

                    // c + c → c + s
                    if (
                        prev.item.instruction === 'c' &&
                        data[0] === -(prev.item.data[2] - prev.item.data[4]) &&
                        data[1] === -(prev.item.data[3] - prev.item.data[5])
                    ) {
                        instruction = 's';
                        data = data.slice(2);
                    }

                    // s + c → s + s
                    else if (
                        prev.item.instruction === 's' &&
                        data[0] === -(prev.item.data[0] - prev.item.data[2]) &&
                        data[1] === -(prev.item.data[1] - prev.item.data[3])
                    ) {
                        instruction = 's';
                        data = data.slice(2);
                    }

                    // [^cs] + c → [^cs] + s
                    else if (
                        'cs'.indexOf(prev.item.instruction) === -1 &&
                        data[0] === 0 &&
                        data[1] === 0
                    ) {
                        instruction = 's';
                        data = data.slice(2);
                    }

                }

                // quadratic Bézier curveto
                else if (instruction === 'q') {

                    // q + q → q + t
                    if (
                        prev.item.instruction === 'q' &&
                        data[0] === (prev.item.data[2] - prev.item.data[0]) &&
                        data[1] === (prev.item.data[3] - prev.item.data[1])
                    ) {
                        instruction = 't';
                        data = data.slice(2);
                    }

                    // t + q → t + t
                    else if (
                        prev.item.instruction === 't' &&
                        data[2] === prev.item.data[0] &&
                        data[3] === prev.item.data[1]
                    ) {
                        instruction = 't';
                        data = data.slice(2);
                    }

                }

            }

            // remove useless non-first path segments
            if (params.removeUseless) {

                // m 0,0 / l 0,0 / h 0 / v 0 / q 0,0 0,0 / t 0,0 / c 0,0 0,0 0,0 / s 0,0 0,0
                if (
                    (
                     'lhvqtcs'.indexOf(instruction) > -1
                    ) &&
                    data.every(function(i) { return i === 0; })
                ) {
                    return false;
                }

                // a 25,25 -30 0,1 0,0
                if (
                    instruction === 'a' &&
                    data[5] === 0 &&
                    data[6] === 0
                ) {
                    return false;
                }

            }

            // collapse repeated instructions data
            if (
                params.collapseRepeated &&
                !hasMarkerMid &&
                prev.item &&
                instruction === prev.item.instruction
            ) {
                // increase previous h or v data with current
                if (instruction === 'h' || instruction === 'v') {
                    prev.item.data[0] += data[0];
                // concat previous data with current
                } else {
                    prev.item.data = prev.item.data.concat(data);
                }

                // if there was an original then remove it because of the new data
                delete prev.item.original;

                // filter current item
                return false;
            }

            item.instruction = instruction;
            item.data = data;

            prev = {
                item: item,
                point: point.slice(0)
            };

        }

        return true;

    });

    return path;

}

/**
 * Decrease accuracy of floating-point numbers
 * in path data keeping a specified number of decimals.
 *
 * @param {Array} data input data array
 * @param {Number} fixed number of decimals
 *
 * @return {Array} output data array
 */
function roundData(data, fixed) {

    return data.map(function(num) {
        return +num.toFixed(fixed);
    });

}

/**
 * Checks if curve is a straight line by calculating a polygon area.
 *
 * @see http://www.mathopenref.com/coordpolygonarea2.html
 *
 * @param {Array} xs array of curve points x-coordinates
 * @param {Array} ys array of curve points y-coordinates
 *
 * @return {Boolean}
 */

function isCurveStraightLine(xs, ys) {

    var points = xs.length,
        area = 0,
        j = points - 1;

    for (var i=0; i < points; i++) {
        area += (xs[j] + xs[i]) * (ys[j] - ys[i]);
        j = i;
    }

    if (+area.toFixed(2)) return false;

    return true;

}

/**
 * Convert path array to string.
 *
 * @param {Array} path input path data
 * @param {Object} params plugin params
 *
 * @return {String} output path string
 */
function js2path(path, params) {

        // out path data string
    var pathString = '';

    path.forEach(function(item) {

        pathString += item.instruction + (item.data ? cleanupOutData(item.data, params) : '');

    });

    return pathString;

}
<commit_msg>plugins/convertPathData: drastic speed improvement with the huge Path data<commit_after>'use strict';

var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
    regPathInstructions = /([MmLlHhVvCcSsQqTtAaZz])\s*/,
    regPathData = /(?=-)|[\s,]+/,
    pathElems = ['path', 'glyph', 'missing-glyph'],
    hasMarkerMid;

/**
 * Convert absolute Path to relative,
 * collapse repeated instructions,
 * detect and convert Lineto shorthands,
 * remove useless instructions like "l0,0",
 * trim useless delimiters and leading zeros,
 * decrease accuracy of floating-point numbers.
 *
 * @see http://www.w3.org/TR/SVG/paths.html#PathData
 *
 * @param {Object} item current iteration item
 * @param {Object} params plugin params
 * @return {Boolean} if false, item will be filtered out
 *
 * @author Kir Belevich
 */
exports.convertPathData = function(item, params) {

    if (item.isElem(pathElems) && item.hasAttr('d')) {

        hasMarkerMid = item.hasAttr('marker-mid');

        var data = path2js(item.attr('d').value);

        if (data.length) {
            data = convertToRelative(data);

            data = filters(data, params);

            item.attr('d').value = js2path(data, params);
        }

    }

};

/**
 * Convert path string to JS representation.
 *
 * @param {String} pathString input string
 * @param {Object} params plugin params
 *
 * @return {Array} output array
 */
function path2js(pathString) {

        // JS representation of the path data
    var path = [],
        // current instruction context
        instruction;

    // splitting path string into array like ['M', '10 50', 'L', '20 30']
    pathString.split(regPathInstructions).forEach(function(data) {
        if (data) {
            // instruction item
            if (regPathInstructions.test(data)) {
                instruction = data;

                // z - instruction w/o data
                if ('Zz'.indexOf(instruction) > -1) {
                    path.push({
                        instruction: 'z'
                    });
                }
            // data item
            } else {

                // M 35.898 14.374 L 35.898 14.374 → M35.898 14.374L35.898 14.374
                data = data.trim().split(regPathData).map(function(str) {
                    return +str;
                });

                // very stupid defense strategy
                if (typeof data[0] === 'number' && !isNaN(data[0])) {

                    var index = 0,
                        pair = 2;

                    // deal with very first 'Mm' and multiple points data
                    if ('Mm'.indexOf(instruction) > -1) {

                        path.push({
                            instruction: instruction,
                            data: data.slice(index, index + pair)
                        });

                        index += pair;

                        if (data.length) {
                            instruction = instruction === instruction.toLowerCase() ? 'l' : 'L';
                        }

                    }

                    if ('HhVv'.indexOf(instruction) > -1) {
                        pair = 1;
                    } else if ('LlTt'.indexOf(instruction) > -1) {
                        pair = 2;
                    } else if ('QqSs'.indexOf(instruction) > -1) {
                        pair = 4;
                    } else if ('Cc'.indexOf(instruction) > -1) {
                        pair = 6;
                    } else if ('Aa'.indexOf(instruction) > -1) {
                        pair = 7;
                    }

                    while(index < data.length) {
                        path.push({
                            instruction: instruction,
                            data: data.slice(index, index + pair)
                        });

                        index += pair;
                    }

                }

            }
        }
    });

    return path;

}

/**
 * Convert absolute path data coordinates to relative.
 *
 * @param {Array} path input path data
 * @param {Object} params plugin params
 *
 * @return {Array} output path data
 */
function convertToRelative(path) {

    var instruction,
        data,
        newPoint,
        point = [0, 0];

    path.forEach(function(item) {

        instruction = item.instruction;
        data = item.data;

        // data !== !z
        if (data) {

            // already relative
            // recalculate current point
            if ('mcslqta'.indexOf(instruction) > -1) {

                newPoint = data.slice(-2);

                point[0] += newPoint[0];
                point[1] += newPoint[1];

            } else if (instruction === 'h') {

                point[0] += data[0];

            } else if (instruction === 'v') {

                point[1] += data[0];

            }

            // convert absolute path data coordinates to relative
            // M → m
            // L → l
            // T → t
            if ('MLT'.indexOf(instruction) > -1) {

                // don't convert M if point is [0, 0]
                if (instruction !== 'M' || point[0] !== 0 && point[1] !== 0) {
                    instruction = instruction.toLowerCase();
                }

                // x y
                // 0 1
                data[0] -= point[0];
                data[1] -= point[1];

                point[0] += data[0];
                point[1] += data[1];

            // C → c
            } else if (instruction === 'C') {

                instruction = 'c';

                // x1 y1 x2 y2 x y
                // 0  1  2  3  4 5
                data[0] -= point[0];
                data[1] -= point[1];
                data[2] -= point[0];
                data[3] -= point[1];
                data[4] -= point[0];
                data[5] -= point[1];

                point[0] += data[4];
                point[1] += data[5];

            // S → s
            // Q → q
            } else if ('SQ'.indexOf(instruction) > -1) {

                instruction = instruction.toLowerCase();

                // x1 y1 x y
                // 0  1  2 3
                data[0] -= point[0];
                data[1] -= point[1];
                data[2] -= point[0];
                data[3] -= point[1];

                point[0] += data[2];
                point[1] += data[3];

            // A → a
            } else if (instruction === 'A') {

                instruction = 'a';

                // rx ry x-axis-rotation large-arc-flag sweep-flag x y
                // 0  1  2               3              4          5 6
                data[5] -= point[0];
                data[6] -= point[1];

                point[0] += data[5];
                point[1] += data[6];

            // H → h
            } else if (instruction === 'H') {

                instruction = 'h';

                data[0] -= point[0];

                point[0] += data[0];

            // V → v
            } else if (instruction === 'V') {

                instruction = 'v';

                data[0] -= point[1];

                point[1] += data[0];

            }

            item.instruction = instruction;
            item.data = data;
            item.point = point.slice(0);

        }

        // !data === z, reset current point
        else {
            point = [0, 0];
        }

    });

    return path;

}

/**
 * Main filters loop.
 *
 * @param {Array} path input path data
 * @param {Object} params plugin params
 *
 * @return {Array} output path data
 */
function filters(path, params) {

    var instruction,
        data,
        point = [0, 0],
        prev = {
            point: [0, 0]
        };

    path = path.filter(function(item) {

        instruction = item.instruction;
        data = item.data;
        point = item.point;

        if (data) {

            if (params.floatPrecision) {
                data = roundData(data, params.floatPrecision);
            }

            // convert straight curves into lines segments
            if (params.straightCurves) {

                // c
                if (
                    instruction === 'c' &&
                    isCurveStraightLine(
                        [ 0, data[0], data[2], data[4] ],
                        [ 0, data[1], data[3], data[5] ]
                    )
                ) {
                    instruction = 'l';
                    data = data.slice(-2);
                }

                // s
                else if (instruction === 's') {

                    if (
                        isCurveStraightLine(
                            [ 0, data[0], data[2] ],
                            [ 0, data[1], data[3] ]
                        )
                    ) {
                        instruction = 'l';
                        data = data.slice(-2);
                    }

                }

                // q
                else if (
                    prev.item &&
                    instruction === 'q' &&
                    isCurveStraightLine(
                        [ 0, data[0], data[2] ],
                        [ 0, data[1], data[3] ]
                    )
                ) {
                    // save the original one for the future potential q + t conversion
                    item.original = {
                        instruction: instruction,
                        data: data
                    };

                    instruction = 'l';
                    data = data.slice(-2);
                }

                else if (instruction === 't') {

                    // q (original) + t
                    if (
                        prev.item &&
                        prev.item.original &&
                        prev.item.original.instruction === 'q'
                    ) {
                        if (isCurveStraightLine(
                            [ prev.item.original.data[0], prev.item.original.data[2], data[0] ],
                            [ prev.item.original.data[1], prev.item.original.data[3], data[1] ]
                        )) {
                            instruction = 'l';
                            data = data.slice(-2);
                        } else {
                            prev.item.instruction = 'q';
                            prev.item.data = prev.item.original.data;
                        }
                    }

                    // [^qt] + t
                    else if (!prev.item || 'qt'.indexOf(prev.item.instruction) === -1) {
                        instruction = 'l';
                        data = data.slice(-2);
                    }

                }

                // a
                else if (
                    instruction === 'a' &&
                    (data[0] === 0 || data[1] === 0)
                ) {
                    instruction = 'l';
                    data = data.slice(-2);
                }
            }

            // horizontal and vertical line shorthands
            // l 50 0 → h 50
            // l 0 50 → v 50
            if (
                params.lineShorthands &&
                instruction === 'l'
            ) {
                if (data[1] === 0) {
                    instruction = 'h';
                    data = [data[0]];
                } else if (data[0] === 0) {
                    instruction = 'v';
                    data = [data[1]];
                }
            }

            // convert curves into smooth shorthands
            if (params.curveSmoothShorthands && prev.item) {

                // curveto
                if (instruction === 'c') {

                    // c + c → c + s
                    if (
                        prev.item.instruction === 'c' &&
                        data[0] === -(prev.item.data[2] - prev.item.data[4]) &&
                        data[1] === -(prev.item.data[3] - prev.item.data[5])
                    ) {
                        instruction = 's';
                        data = data.slice(2);
                    }

                    // s + c → s + s
                    else if (
                        prev.item.instruction === 's' &&
                        data[0] === -(prev.item.data[0] - prev.item.data[2]) &&
                        data[1] === -(prev.item.data[1] - prev.item.data[3])
                    ) {
                        instruction = 's';
                        data = data.slice(2);
                    }

                    // [^cs] + c → [^cs] + s
                    else if (
                        'cs'.indexOf(prev.item.instruction) === -1 &&
                        data[0] === 0 &&
                        data[1] === 0
                    ) {
                        instruction = 's';
                        data = data.slice(2);
                    }

                }

                // quadratic Bézier curveto
                else if (instruction === 'q') {

                    // q + q → q + t
                    if (
                        prev.item.instruction === 'q' &&
                        data[0] === (prev.item.data[2] - prev.item.data[0]) &&
                        data[1] === (prev.item.data[3] - prev.item.data[1])
                    ) {
                        instruction = 't';
                        data = data.slice(2);
                    }

                    // t + q → t + t
                    else if (
                        prev.item.instruction === 't' &&
                        data[2] === prev.item.data[0] &&
                        data[3] === prev.item.data[1]
                    ) {
                        instruction = 't';
                        data = data.slice(2);
                    }

                }

            }

            // remove useless non-first path segments
            if (params.removeUseless) {

                // m 0,0 / l 0,0 / h 0 / v 0 / q 0,0 0,0 / t 0,0 / c 0,0 0,0 0,0 / s 0,0 0,0
                if (
                    (
                     'lhvqtcs'.indexOf(instruction) > -1
                    ) &&
                    data.every(function(i) { return i === 0; })
                ) {
                    return false;
                }

                // a 25,25 -30 0,1 0,0
                if (
                    instruction === 'a' &&
                    data[5] === 0 &&
                    data[6] === 0
                ) {
                    return false;
                }

            }

            // collapse repeated instructions data
            if (
                params.collapseRepeated &&
                !hasMarkerMid &&
                prev.item &&
                instruction === prev.item.instruction
            ) {
                // increase previous h or v data with current
                if (instruction === 'h' || instruction === 'v') {
                    prev.item.data[0] += data[0];
                // concat previous data with current
                } else {
                    prev.item.data = prev.item.data.concat(data);
                }

                // if there was an original then remove it because of the new data
                delete prev.item.original;

                // filter current item
                return false;
            }

            item.instruction = instruction;
            item.data = data;

            prev = {
                item: item,
                point: point.slice(0)
            };

        }

        return true;

    });

    return path;

}

/**
 * Decrease accuracy of floating-point numbers
 * in path data keeping a specified number of decimals.
 *
 * @param {Array} data input data array
 * @param {Number} fixed number of decimals
 *
 * @return {Array} output data array
 */
function roundData(data, fixed) {

    return data.map(function(num) {
        return +num.toFixed(fixed);
    });

}

/**
 * Checks if curve is a straight line by calculating a polygon area.
 *
 * @see http://www.mathopenref.com/coordpolygonarea2.html
 *
 * @param {Array} xs array of curve points x-coordinates
 * @param {Array} ys array of curve points y-coordinates
 *
 * @return {Boolean}
 */

function isCurveStraightLine(xs, ys) {

    var points = xs.length,
        area = 0,
        j = points - 1;

    for (var i=0; i < points; i++) {
        area += (xs[j] + xs[i]) * (ys[j] - ys[i]);
        j = i;
    }

    if (+area.toFixed(2)) return false;

    return true;

}

/**
 * Convert path array to string.
 *
 * @param {Array} path input path data
 * @param {Object} params plugin params
 *
 * @return {String} output path string
 */
function js2path(path, params) {

        // out path data string
    var pathString = '';

    path.forEach(function(item) {

        pathString += item.instruction + (item.data ? cleanupOutData(item.data, params) : '');

    });

    return pathString;

}
<|endoftext|>