<commit_before>
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var PDFImage = (function PDFImageClosure() {
  /**
   * Decode the image in the main thread if it supported. Resovles the promise
   * when the image data is ready.
   */
  function handleImageData(handler, xref, res, image, promise) {
    if (image instanceof JpegStream && image.isNativelyDecodable(xref, res)) {
      // For natively supported jpegs send them to the main thread for decoding.
      var dict = image.dict;
      var colorSpace = dict.get('ColorSpace', 'CS');
      colorSpace = ColorSpace.parse(colorSpace, xref, res);
      var numComps = colorSpace.numComps;
      handler.send('JpegDecode', [image.getIR(), numComps], function(message) {
        var data = message.data;
        var stream = new Stream(data, 0, data.length, image.dict);
        promise.resolve(stream);
      });
    } else {
      promise.resolve(image);
    }
  }
  /**
   * Decode and clamp a value. The formula is different from the spec because we
   * don't decode to float range [0,1], we decode it in the [0,max] range.
   */
  function decodeAndClamp(value, addend, coefficient, max) {
    value = addend + value * coefficient;
    // Clamp the value to the range
    return value < 0 ? 0 : value > max ? max : value;
  }
  function PDFImage(xref, res, image, inline, smask, mask) {
    this.image = image;
    if (image.getParams) {
      // JPX/JPEG2000 streams directly contain bits per component
      // and color space mode information.
      TODO('get params from actual stream');
      // var bits = ...
      // var colorspace = ...
    }
    // TODO cache rendered images?

    var dict = image.dict;
    this.width = dict.get('Width', 'W');
    this.height = dict.get('Height', 'H');

    if (this.width < 1 || this.height < 1)
      error('Invalid image width: ' + this.width + ' or height: ' +
            this.height);

    this.interpolate = dict.get('Interpolate', 'I') || false;
    this.imageMask = dict.get('ImageMask', 'IM') || false;

    var bitsPerComponent = image.bitsPerComponent;
    if (!bitsPerComponent) {
      bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
      if (!bitsPerComponent) {
        if (this.imageMask)
          bitsPerComponent = 1;
        else
          error('Bits per component missing in image: ' + this.imageMask);
      }
    }
    this.bpc = bitsPerComponent;

    if (!this.imageMask) {
      var colorSpace = dict.get('ColorSpace', 'CS');
      if (!colorSpace) {
        TODO('JPX images (which don"t require color spaces');
        colorSpace = new Name('DeviceRGB');
      }
      this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
      this.numComps = this.colorSpace.numComps;
    }

    this.decode = dict.get('Decode', 'D');
    this.needsDecode = false;
    if (this.decode && this.colorSpace &&
        !this.colorSpace.isDefaultDecode(this.decode)) {
      this.needsDecode = true;
      // Do some preprocessing to avoid more math.
      var max = (1 << bitsPerComponent) - 1;
      this.decodeCoefficients = [];
      this.decodeAddends = [];
      for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
        var dmin = this.decode[i];
        var dmax = this.decode[i + 1];
        this.decodeCoefficients[j] = dmax - dmin;
        this.decodeAddends[j] = max * dmin;
      }
    }

    if (smask) {
      this.smask = new PDFImage(xref, res, smask, false);
    } else if (mask) {
      this.mask = new PDFImage(xref, res, mask, false);
    }
  }
  /**
   * Handles processing of image data and calls the callback with an argument
   * of a PDFImage when the image is ready to be used.
   */
  PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref,
                                                     res, image, inline) {
    var imageDataPromise = new Promise();
    var smaskPromise = new Promise();
    var maskPromise = new Promise();
    // The image data and smask data may not be ready yet, wait till both are
    // resolved.
    Promise.all([imageDataPromise, smaskPromise, maskPromise]).then(
        function(results) {
      var imageData = results[0], smaskData = results[1], maskData = results[2];
      var image = new PDFImage(xref, res, imageData, inline, smaskData,
                               maskData);
      callback(image);
    });

    handleImageData(handler, xref, res, image, imageDataPromise);

    var smask = image.dict.get('SMask');
    var mask = image.dict.get('Mask');

    if (smask) {
      handleImageData(handler, xref, res, smask, smaskPromise);
      maskPromise.resolve(null);
    } else {
      smaskPromise.resolve(null);
      if (mask && isStream(mask)) {
        handleImageData(handler, xref, res, mask, maskPromise);
      } else if (mask) {
        TODO('handle color key masking');
        maskPromise.resolve(null);
      } else {
        maskPromise.resolve(null);
      }
    }
  };

  /**
   * Resize an image using the nearest neighbor algorithm.  Currently only
   * supports one and three component images.
   * @param {TypedArray} pixels The original image with one component.
   * @param {Number} bpc Number of bits per component.
   * @param {Number} components Number of color components, 1 or 3 is supported.
   * @param {Number} w1 Original width.
   * @param {Number} h1 Original height.
   * @param {Number} w2 New width.
   * @param {Number} h2 New height.
   * @return {TypedArray} Resized image data.
   */
  PDFImage.resize = function PDFImage_resize(pixels, bpc, components,
                                             w1, h1, w2, h2) {
    var length = w2 * h2 * components;
    var temp = bpc <= 8 ? new Uint8Array(length) :
        bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
    var xRatio = w1 / w2;
    var yRatio = h1 / h2;
    var px, py, newIndex, oldIndex;
    for (var i = 0; i < h2; i++) {
      for (var j = 0; j < w2; j++) {
        px = Math.floor(j * xRatio);
        py = Math.floor(i * yRatio);
        newIndex = (i * w2) + j;
        oldIndex = ((py * w1) + px);
        if (components === 1) {
          temp[newIndex] = pixels[oldIndex];
        } else if (components === 3) {
          newIndex *= 3;
          oldIndex *= 3;
          temp[newIndex] = pixels[oldIndex];
          temp[newIndex + 1] = pixels[oldIndex + 1];
          temp[newIndex + 2] = pixels[oldIndex + 2];
        }
      }
    }
    return temp;
  };

  PDFImage.prototype = {
    get drawWidth() {
      if (!this.smask)
        return this.width;
      return Math.max(this.width, this.smask.width);
    },
    get drawHeight() {
      if (!this.smask)
        return this.height;
      return Math.max(this.height, this.smask.height);
    },
    getComponents: function PDFImage_getComponents(buffer) {
      var bpc = this.bpc;
      var needsDecode = this.needsDecode;
      var decodeMap = this.decode;

      // This image doesn't require any extra work.
      if (bpc == 8 && !needsDecode)
        return buffer;

      var bufferLength = buffer.length;
      var width = this.width;
      var height = this.height;
      var numComps = this.numComps;

      var length = width * height * numComps;
      var bufferPos = 0;
      var output = bpc <= 8 ? new Uint8Array(length) :
        bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
      var rowComps = width * numComps;
      var decodeAddends, decodeCoefficients;
      if (needsDecode) {
        decodeAddends = this.decodeAddends;
        decodeCoefficients = this.decodeCoefficients;
      }
      var max = (1 << bpc) - 1;

      if (bpc == 8) {
        // Optimization for reading 8 bpc images that have a decode.
        for (var i = 0, ii = length; i < ii; ++i) {
          var compIndex = i % numComps;
          var value = buffer[i];
          value = decodeAndClamp(value, decodeAddends[compIndex],
                          decodeCoefficients[compIndex], max);
          output[i] = value;
        }
      } else if (bpc == 1) {
        // Optimization for reading 1 bpc images.
        var valueZero = 0, valueOne = 1;
        if (decodeMap) {
          valueZero = decodeMap[0] ? 1 : 0;
          valueOne = decodeMap[1] ? 1 : 0;
        }
        var mask = 0;
        var buf = 0;

        for (var i = 0, ii = length; i < ii; ++i) {
          if (i % rowComps == 0) {
            mask = 0;
            buf = 0;
          } else {
            mask >>= 1;
          }

          if (mask <= 0) {
            buf = buffer[bufferPos++];
            mask = 128;
          }

          output[i] = !(buf & mask) ? valueZero : valueOne;
        }
      } else {
        // The general case that handles all other bpc values.
        var bits = 0, buf = 0;
        for (var i = 0, ii = length; i < ii; ++i) {
          if (i % rowComps == 0) {
            buf = 0;
            bits = 0;
          }

          while (bits < bpc) {
            buf = (buf << 8) | buffer[bufferPos++];
            bits += 8;
          }

          var remainingBits = bits - bpc;
          var value = buf >> remainingBits;
          if (needsDecode) {
            var compIndex = i % numComps;
            value = decodeAndClamp(value, decodeAddends[compIndex],
                            decodeCoefficients[compIndex], max);
          }
          output[i] = value;
          buf = buf & ((1 << remainingBits) - 1);
          bits = remainingBits;
        }
      }
      return output;
    },
    getOpacity: function PDFImage_getOpacity(width, height) {
      var smask = this.smask;
      var mask = this.mask;
      var originalWidth = this.width;
      var originalHeight = this.height;
      var buf;

      if (smask) {
        var sw = smask.width;
        var sh = smask.height;
        buf = new Uint8Array(sw * sh);
        smask.fillGrayBuffer(buf);
        if (sw != width || sh != height)
          buf = PDFImage.resize(buf, smask.bpc, 1, sw, sh, width, height);
      } else if (mask) {
        var sw = mask.width;
        var sh = mask.height;
        buf = new Uint8Array(sw * sh);
        mask.numComps = 1;
        mask.fillGrayBuffer(buf);

        // Need to invert values in buffer
        for (var i = 0, ii = sw * sh; i < ii; ++i)
          buf[i] = 255 - buf[i];

        if (sw != width || sh != height)
          buf = PDFImage.resize(buf, mask.bpc, 1, sw, sh, width, height);
      } else {
        buf = new Uint8Array(width * height);
        for (var i = 0, ii = width * height; i < ii; ++i)
          buf[i] = 255;
      }
      return buf;
    },
    applyStencilMask: function PDFImage_applyStencilMask(buffer,
                                                         inverseDecode) {
      var width = this.width, height = this.height;
      var bitStrideLength = (width + 7) >> 3;
      var imgArray = this.getImageBytes(bitStrideLength * height);
      var imgArrayPos = 0;
      var i, j, mask, buf;
      // removing making non-masked pixels transparent
      var bufferPos = 3; // alpha component offset
      for (i = 0; i < height; i++) {
        mask = 0;
        for (j = 0; j < width; j++) {
          if (!mask) {
            buf = imgArray[imgArrayPos++];
            mask = 128;
          }
          if (!(buf & mask) == inverseDecode) {
            buffer[bufferPos] = 0;
          }
          bufferPos += 4;
          mask >>= 1;
        }
      }
    },
    fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) {
      var numComps = this.numComps;
      var originalWidth = this.width;
      var originalHeight = this.height;
      var bpc = this.bpc;

      // rows start at byte boundary;
      var rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
      var imgArray = this.getImageBytes(originalHeight * rowBytes);

      var comps = this.colorSpace.getRgbBuffer(
        this.getComponents(imgArray), bpc);
      if (originalWidth != width || originalHeight != height)
        comps = PDFImage.resize(comps, this.bpc, 3, originalWidth,
                                originalHeight, width, height);
      var compsPos = 0;
      var opacity = this.getOpacity(width, height);
      var opacityPos = 0;
      var length = width * height * 4;

      for (var i = 0; i < length; i += 4) {
        buffer[i] = comps[compsPos++];
        buffer[i + 1] = comps[compsPos++];
        buffer[i + 2] = comps[compsPos++];
        buffer[i + 3] = opacity[opacityPos++];
      }
    },
    fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
      var numComps = this.numComps;
      if (numComps != 1)
        error('Reading gray scale from a color image: ' + numComps);

      var width = this.width;
      var height = this.height;
      var bpc = this.bpc;

      // rows start at byte boundary;
      var rowBytes = (width * numComps * bpc + 7) >> 3;
      var imgArray = this.getImageBytes(height * rowBytes);

      var comps = this.getComponents(imgArray);
      var length = width * height;
      // we aren't using a colorspace so we need to scale the value
      var scale = 255 / ((1 << bpc) - 1);
      for (var i = 0; i < length; ++i)
        buffer[i] = (scale * comps[i]) | 0;
    },
    getImageBytes: function PDFImage_getImageBytes(length) {
      this.image.reset();
      return this.image.getBytes(length);
    }
  };
  return PDFImage;
})();

function loadJpegStream(id, imageData, objs) {
  var img = new Image();
  img.onload = (function loadJpegStream_onloadClosure() {
    objs.resolve(id, img);
  });
  img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
}

<commit_msg>Undo removal of Mode line<commit_after>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var PDFImage = (function PDFImageClosure() {
  /**
   * Decode the image in the main thread if it supported. Resovles the promise
   * when the image data is ready.
   */
  function handleImageData(handler, xref, res, image, promise) {
    if (image instanceof JpegStream && image.isNativelyDecodable(xref, res)) {
      // For natively supported jpegs send them to the main thread for decoding.
      var dict = image.dict;
      var colorSpace = dict.get('ColorSpace', 'CS');
      colorSpace = ColorSpace.parse(colorSpace, xref, res);
      var numComps = colorSpace.numComps;
      handler.send('JpegDecode', [image.getIR(), numComps], function(message) {
        var data = message.data;
        var stream = new Stream(data, 0, data.length, image.dict);
        promise.resolve(stream);
      });
    } else {
      promise.resolve(image);
    }
  }
  /**
   * Decode and clamp a value. The formula is different from the spec because we
   * don't decode to float range [0,1], we decode it in the [0,max] range.
   */
  function decodeAndClamp(value, addend, coefficient, max) {
    value = addend + value * coefficient;
    // Clamp the value to the range
    return value < 0 ? 0 : value > max ? max : value;
  }
  function PDFImage(xref, res, image, inline, smask, mask) {
    this.image = image;
    if (image.getParams) {
      // JPX/JPEG2000 streams directly contain bits per component
      // and color space mode information.
      TODO('get params from actual stream');
      // var bits = ...
      // var colorspace = ...
    }
    // TODO cache rendered images?

    var dict = image.dict;
    this.width = dict.get('Width', 'W');
    this.height = dict.get('Height', 'H');

    if (this.width < 1 || this.height < 1)
      error('Invalid image width: ' + this.width + ' or height: ' +
            this.height);

    this.interpolate = dict.get('Interpolate', 'I') || false;
    this.imageMask = dict.get('ImageMask', 'IM') || false;

    var bitsPerComponent = image.bitsPerComponent;
    if (!bitsPerComponent) {
      bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
      if (!bitsPerComponent) {
        if (this.imageMask)
          bitsPerComponent = 1;
        else
          error('Bits per component missing in image: ' + this.imageMask);
      }
    }
    this.bpc = bitsPerComponent;

    if (!this.imageMask) {
      var colorSpace = dict.get('ColorSpace', 'CS');
      if (!colorSpace) {
        TODO('JPX images (which don"t require color spaces');
        colorSpace = new Name('DeviceRGB');
      }
      this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
      this.numComps = this.colorSpace.numComps;
    }

    this.decode = dict.get('Decode', 'D');
    this.needsDecode = false;
    if (this.decode && this.colorSpace &&
        !this.colorSpace.isDefaultDecode(this.decode)) {
      this.needsDecode = true;
      // Do some preprocessing to avoid more math.
      var max = (1 << bitsPerComponent) - 1;
      this.decodeCoefficients = [];
      this.decodeAddends = [];
      for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
        var dmin = this.decode[i];
        var dmax = this.decode[i + 1];
        this.decodeCoefficients[j] = dmax - dmin;
        this.decodeAddends[j] = max * dmin;
      }
    }

    if (smask) {
      this.smask = new PDFImage(xref, res, smask, false);
    } else if (mask) {
      this.mask = new PDFImage(xref, res, mask, false);
    }
  }
  /**
   * Handles processing of image data and calls the callback with an argument
   * of a PDFImage when the image is ready to be used.
   */
  PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref,
                                                     res, image, inline) {
    var imageDataPromise = new Promise();
    var smaskPromise = new Promise();
    var maskPromise = new Promise();
    // The image data and smask data may not be ready yet, wait till both are
    // resolved.
    Promise.all([imageDataPromise, smaskPromise, maskPromise]).then(
        function(results) {
      var imageData = results[0], smaskData = results[1], maskData = results[2];
      var image = new PDFImage(xref, res, imageData, inline, smaskData,
                               maskData);
      callback(image);
    });

    handleImageData(handler, xref, res, image, imageDataPromise);

    var smask = image.dict.get('SMask');
    var mask = image.dict.get('Mask');

    if (smask) {
      handleImageData(handler, xref, res, smask, smaskPromise);
      maskPromise.resolve(null);
    } else {
      smaskPromise.resolve(null);
      if (mask && isStream(mask)) {
        handleImageData(handler, xref, res, mask, maskPromise);
      } else if (mask) {
        TODO('handle color key masking');
        maskPromise.resolve(null);
      } else {
        maskPromise.resolve(null);
      }
    }
  };

  /**
   * Resize an image using the nearest neighbor algorithm.  Currently only
   * supports one and three component images.
   * @param {TypedArray} pixels The original image with one component.
   * @param {Number} bpc Number of bits per component.
   * @param {Number} components Number of color components, 1 or 3 is supported.
   * @param {Number} w1 Original width.
   * @param {Number} h1 Original height.
   * @param {Number} w2 New width.
   * @param {Number} h2 New height.
   * @return {TypedArray} Resized image data.
   */
  PDFImage.resize = function PDFImage_resize(pixels, bpc, components,
                                             w1, h1, w2, h2) {
    var length = w2 * h2 * components;
    var temp = bpc <= 8 ? new Uint8Array(length) :
        bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
    var xRatio = w1 / w2;
    var yRatio = h1 / h2;
    var px, py, newIndex, oldIndex;
    for (var i = 0; i < h2; i++) {
      for (var j = 0; j < w2; j++) {
        px = Math.floor(j * xRatio);
        py = Math.floor(i * yRatio);
        newIndex = (i * w2) + j;
        oldIndex = ((py * w1) + px);
        if (components === 1) {
          temp[newIndex] = pixels[oldIndex];
        } else if (components === 3) {
          newIndex *= 3;
          oldIndex *= 3;
          temp[newIndex] = pixels[oldIndex];
          temp[newIndex + 1] = pixels[oldIndex + 1];
          temp[newIndex + 2] = pixels[oldIndex + 2];
        }
      }
    }
    return temp;
  };

  PDFImage.prototype = {
    get drawWidth() {
      if (!this.smask)
        return this.width;
      return Math.max(this.width, this.smask.width);
    },
    get drawHeight() {
      if (!this.smask)
        return this.height;
      return Math.max(this.height, this.smask.height);
    },
    getComponents: function PDFImage_getComponents(buffer) {
      var bpc = this.bpc;
      var needsDecode = this.needsDecode;
      var decodeMap = this.decode;

      // This image doesn't require any extra work.
      if (bpc == 8 && !needsDecode)
        return buffer;

      var bufferLength = buffer.length;
      var width = this.width;
      var height = this.height;
      var numComps = this.numComps;

      var length = width * height * numComps;
      var bufferPos = 0;
      var output = bpc <= 8 ? new Uint8Array(length) :
        bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
      var rowComps = width * numComps;
      var decodeAddends, decodeCoefficients;
      if (needsDecode) {
        decodeAddends = this.decodeAddends;
        decodeCoefficients = this.decodeCoefficients;
      }
      var max = (1 << bpc) - 1;

      if (bpc == 8) {
        // Optimization for reading 8 bpc images that have a decode.
        for (var i = 0, ii = length; i < ii; ++i) {
          var compIndex = i % numComps;
          var value = buffer[i];
          value = decodeAndClamp(value, decodeAddends[compIndex],
                          decodeCoefficients[compIndex], max);
          output[i] = value;
        }
      } else if (bpc == 1) {
        // Optimization for reading 1 bpc images.
        var valueZero = 0, valueOne = 1;
        if (decodeMap) {
          valueZero = decodeMap[0] ? 1 : 0;
          valueOne = decodeMap[1] ? 1 : 0;
        }
        var mask = 0;
        var buf = 0;

        for (var i = 0, ii = length; i < ii; ++i) {
          if (i % rowComps == 0) {
            mask = 0;
            buf = 0;
          } else {
            mask >>= 1;
          }

          if (mask <= 0) {
            buf = buffer[bufferPos++];
            mask = 128;
          }

          output[i] = !(buf & mask) ? valueZero : valueOne;
        }
      } else {
        // The general case that handles all other bpc values.
        var bits = 0, buf = 0;
        for (var i = 0, ii = length; i < ii; ++i) {
          if (i % rowComps == 0) {
            buf = 0;
            bits = 0;
          }

          while (bits < bpc) {
            buf = (buf << 8) | buffer[bufferPos++];
            bits += 8;
          }

          var remainingBits = bits - bpc;
          var value = buf >> remainingBits;
          if (needsDecode) {
            var compIndex = i % numComps;
            value = decodeAndClamp(value, decodeAddends[compIndex],
                            decodeCoefficients[compIndex], max);
          }
          output[i] = value;
          buf = buf & ((1 << remainingBits) - 1);
          bits = remainingBits;
        }
      }
      return output;
    },
    getOpacity: function PDFImage_getOpacity(width, height) {
      var smask = this.smask;
      var mask = this.mask;
      var originalWidth = this.width;
      var originalHeight = this.height;
      var buf;

      if (smask) {
        var sw = smask.width;
        var sh = smask.height;
        buf = new Uint8Array(sw * sh);
        smask.fillGrayBuffer(buf);
        if (sw != width || sh != height)
          buf = PDFImage.resize(buf, smask.bpc, 1, sw, sh, width, height);
      } else if (mask) {
        var sw = mask.width;
        var sh = mask.height;
        buf = new Uint8Array(sw * sh);
        mask.numComps = 1;
        mask.fillGrayBuffer(buf);

        // Need to invert values in buffer
        for (var i = 0, ii = sw * sh; i < ii; ++i)
          buf[i] = 255 - buf[i];

        if (sw != width || sh != height)
          buf = PDFImage.resize(buf, mask.bpc, 1, sw, sh, width, height);
      } else {
        buf = new Uint8Array(width * height);
        for (var i = 0, ii = width * height; i < ii; ++i)
          buf[i] = 255;
      }
      return buf;
    },
    applyStencilMask: function PDFImage_applyStencilMask(buffer,
                                                         inverseDecode) {
      var width = this.width, height = this.height;
      var bitStrideLength = (width + 7) >> 3;
      var imgArray = this.getImageBytes(bitStrideLength * height);
      var imgArrayPos = 0;
      var i, j, mask, buf;
      // removing making non-masked pixels transparent
      var bufferPos = 3; // alpha component offset
      for (i = 0; i < height; i++) {
        mask = 0;
        for (j = 0; j < width; j++) {
          if (!mask) {
            buf = imgArray[imgArrayPos++];
            mask = 128;
          }
          if (!(buf & mask) == inverseDecode) {
            buffer[bufferPos] = 0;
          }
          bufferPos += 4;
          mask >>= 1;
        }
      }
    },
    fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) {
      var numComps = this.numComps;
      var originalWidth = this.width;
      var originalHeight = this.height;
      var bpc = this.bpc;

      // rows start at byte boundary;
      var rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
      var imgArray = this.getImageBytes(originalHeight * rowBytes);

      var comps = this.colorSpace.getRgbBuffer(
        this.getComponents(imgArray), bpc);
      if (originalWidth != width || originalHeight != height)
        comps = PDFImage.resize(comps, this.bpc, 3, originalWidth,
                                originalHeight, width, height);
      var compsPos = 0;
      var opacity = this.getOpacity(width, height);
      var opacityPos = 0;
      var length = width * height * 4;

      for (var i = 0; i < length; i += 4) {
        buffer[i] = comps[compsPos++];
        buffer[i + 1] = comps[compsPos++];
        buffer[i + 2] = comps[compsPos++];
        buffer[i + 3] = opacity[opacityPos++];
      }
    },
    fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
      var numComps = this.numComps;
      if (numComps != 1)
        error('Reading gray scale from a color image: ' + numComps);

      var width = this.width;
      var height = this.height;
      var bpc = this.bpc;

      // rows start at byte boundary;
      var rowBytes = (width * numComps * bpc + 7) >> 3;
      var imgArray = this.getImageBytes(height * rowBytes);

      var comps = this.getComponents(imgArray);
      var length = width * height;
      // we aren't using a colorspace so we need to scale the value
      var scale = 255 / ((1 << bpc) - 1);
      for (var i = 0; i < length; ++i)
        buffer[i] = (scale * comps[i]) | 0;
    },
    getImageBytes: function PDFImage_getImageBytes(length) {
      this.image.reset();
      return this.image.getBytes(length);
    }
  };
  return PDFImage;
})();

function loadJpegStream(id, imageData, objs) {
  var img = new Image();
  img.onload = (function loadJpegStream_onloadClosure() {
    objs.resolve(id, img);
  });
  img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
}

<|endoftext|>


<commit_before>/**
 * Created by asiulik on 2017-03-06.
 */
define([
    'jquery',
    'lodash',
    './config',
    './utils/utils'
], function ($, _, Config, Utils) {

    function ProgressiveImage(el, options) {
        this.element = el;
        this.options = _.assign(this.options, options || {});
    }

    ProgressiveImage.prototype.options = {

        requestOnVisible: true,
        strategy: "image",
        breakpoints: Config.breakpoints,
        isElementInView: function($element){
            return Utils.isElementInView($element);
        }

    };

    ProgressiveImage.prototype.createBreakpointConfig = function (breakpointObj, progressiveImageConfig) {

        var arrFromAttr = progressiveImageConfig.split(',');

        _.each(arrFromAttr, function (value) {

            var splited = value.split(" ");

            if (arrFromAttr[0] === 'none')
                isLazyLoadStrategy = true;

            breakpointObj[splited[0]] = splited[1];

        });

        return breakpointObj;

    };

    ProgressiveImage.prototype.init = function () {

        this.$element = $(this.element);
        this.breakpointsConfigObj = {};

        var _this = this;

        this.breakpointsConfigObj.none = getUrl(this.element, this.options.strategy);

        this.createBreakpointConfig(this.breakpointsConfigObj, this.element.getAttribute(Config.attrNameSlug));

        this.setPositionConfig();

        if (!this.options.requestOnVisible) {
            this.checkBreakpoint();
        }

        this.$element = $(this.element);

        $(window).on('resize orientationchange', function () {
            _this.checkBreakpoint();
        });

        this.checkViewportTh = function () {

            if (_this.visible) return;

            var isInViewport = _this.options.isElementInView(_this.$element);

            if (isInViewport) {

                _this.visible = true;
                _this.checkBreakpoint();

            }
        };

        this.checkViewportTh();

        $(window).on('scroll load', this.checkViewportTh);
        $(window).on('resize', this.checkViewportTh);

    };

    ProgressiveImage.prototype.setPositionConfig = function () {

        var _this = this;
        var arrBgPosition = this.element.getAttribute(Config.positionAttrSlug);

        if (arrBgPosition) {

            this.positionConfig = {};

            arrBgPosition.split(',').map(function (item) {

                var configPair = item.split(' ');

                _this.positionConfig[configPair[0]] = {
                    x: configPair[1].split("/")[0],
                    y: configPair[1].split("/")[1]
                };
            });
        }
    };

    ProgressiveImage.prototype.getAvailableBreakpoint = function (breakpointValue, breakpointsConfig, availableBreakpointsCollection) {

        var breakpoint;
        var availableBreakpoints = availableBreakpointsCollection;

        if (!breakpointsConfig[breakpointValue]) {
            for (var t = availableBreakpoints.indexOf(breakpointValue); t >= 0; t--)
                if ("undefined" != typeof breakpointsConfig[availableBreakpoints[t]]) {
                    breakpoint = availableBreakpoints[t];
                    break
                }
        } else {
            breakpoint = breakpointValue;
        }

        return breakpoint;

    };

    ProgressiveImage.prototype.getImage = function (breakpointValue) {

        var _this = this;

        var breakpoint = this.getAvailableBreakpoint(
                breakpointValue,
                this.breakpointsConfigObj,
                this.options.breakpoints
        );

        var newImg = new Image;

        if (_this.positionConfig) {
            var position = this.positionConfig[breakpoint] || this.positionConfig['none'];
            updatePosition(this.element, this.options.strategy, position);
        }

        if (this.options.forceUpdate) {
            updateSrc(this.breakpointsConfigObj[breakpoint], _this.element, _this.options.strategy);
        }

        newImg.src = this.breakpointsConfigObj[breakpoint];

        newImg.onload = function () {
            updateSrc(newImg.src, _this.element, _this.options.strategy);
        };

    };

    ProgressiveImage.prototype.checkBreakpoint = function () {

        if (this.options.requestOnVisible && !this.visible) return;

        if (this.currentBreakPoint !== getBreakpoint()) {
            this.currentBreakPoint = getBreakpoint();
            this.getImage(this.currentBreakPoint);
        }

    };

    ProgressiveImage.prototype.delete = function () {
        if (this.checkViewportTh) {
            $(window).off('scroll', this.checkViewportTh);
            $(window).off('resize', this.checkViewportTh);
        }
    };

    function updatePosition(element, strategy, position) {

        if (!position) position = {};

        position.x = position.x || 0.5;
        position.y = position.y || 0.5;

        if (strategy === Config.BACKGROUND_STRATEGY) {

            element.style.backgroundPosition =
                    _mapPositionToPercent(position.x) + ' ' + _mapPositionToPercent(position.y)
        }
    }

    function getStrategyTypeByTagName(tagName) {

        var strategy;

        switch (tagName) {

            case 'IMG':
                strategy = Config.IMAGE_STRATEGY;
                break;

            case 'VIDEO':
                strategy = Config.POSTER_STRATEGY;
                break;

            case 'A':
                strategy = Config.LINK_STRATEGY;
                break;

            default:
                strategy = Config.BACKGROUND_STRATEGY;
        }

        return strategy;

    }

    function updateSrc(src, element) {

        var strategy = getStrategyTypeByTagName(element.tagName);

        if (strategy === Config.IMAGE_STRATEGY) {
            element.src = src;
        }

        if (strategy === Config.LINK_STRATEGY) {
            element.href = src;
        }

        if (strategy === Config.POSTER_STRATEGY) {
            Utils.setBgImage(element, src);
        }

        if (strategy === Config.BACKGROUND_STRATEGY) {
            Utils.setBgImage(element, src);
        }

        return element;
    }

    function getUrl(element, strategy) {

        if (strategy === Config.IMAGE_STRATEGY) {
            return element.src;
        }

        if (strategy === Config.BACKGROUND_STRATEGY) {
            var backgroundImage = element.style.backgroundImage;
            return backgroundImage.slice(4, -1).replace(/["|']/g, "");
        }

        throw new Error('Get url invalid strategy detected: ', strategy);
    }

    function _mapPositionToPercent(value) {
        return value * 100 + '%';
    }

    function getBreakpoint() {
        return Utils.getBreakpoint();
    }

    return ProgressiveImage;

});<commit_msg>add progressiveImage  options desc<commit_after>/**
 * Created by asiulik on 2017-03-06.
 */
define([
    'jquery',
    'lodash',
    './config',
    './utils/utils'
], function ($, _, Config, Utils) {

    function ProgressiveImage(el, options) {
        this.element = el;
        this.options = _.assign(this.options, options || {});
    }

    ProgressiveImage.prototype.options = {

        /*
        *  requestOnVisible make request when isElementInView return true value by currentBreakpoint
        * */
        requestOnVisible: true,
        /*
        *  supported breakpoints collection
        * */
        breakpoints: Config.breakpoints,
        /*
        * isElementInView should return boolean true/false
        * */
        isElementInView: function($element){
            return Utils.isElementInView($element);
        }

    };

    ProgressiveImage.prototype.createBreakpointConfig = function (breakpointObj, progressiveImageConfig) {

        var arrFromAttr = progressiveImageConfig.split(',');

        _.each(arrFromAttr, function (value) {

            var splited = value.split(" ");

            if (arrFromAttr[0] === 'none')
                isLazyLoadStrategy = true;

            breakpointObj[splited[0]] = splited[1];

        });

        return breakpointObj;

    };

    ProgressiveImage.prototype.init = function () {

        this.$element = $(this.element);
        this.breakpointsConfigObj = {};

        var _this = this;

        this.breakpointsConfigObj.none = getUrl(this.element, this.options.strategy);

        this.createBreakpointConfig(this.breakpointsConfigObj, this.element.getAttribute(Config.attrNameSlug));

        this.setPositionConfig();

        if (!this.options.requestOnVisible) {
            this.checkBreakpoint();
        }

        this.$element = $(this.element);

        $(window).on('resize orientationchange', function () {
            _this.checkBreakpoint();
        });

        this.checkViewportTh = function () {

            if (_this.visible) return;

            var isInViewport = _this.options.isElementInView(_this.$element);

            if (isInViewport) {

                _this.visible = true;
                _this.checkBreakpoint();

            }
        };

        this.checkViewportTh();

        $(window).on('scroll load', this.checkViewportTh);
        $(window).on('resize', this.checkViewportTh);

    };

    ProgressiveImage.prototype.setPositionConfig = function () {

        var _this = this;
        var arrBgPosition = this.element.getAttribute(Config.positionAttrSlug);

        if (arrBgPosition) {

            this.positionConfig = {};

            arrBgPosition.split(',').map(function (item) {

                var configPair = item.split(' ');

                _this.positionConfig[configPair[0]] = {
                    x: configPair[1].split("/")[0],
                    y: configPair[1].split("/")[1]
                };
            });
        }
    };

    ProgressiveImage.prototype.getAvailableBreakpoint = function (breakpointValue, breakpointsConfig, availableBreakpointsCollection) {

        var breakpoint;
        var availableBreakpoints = availableBreakpointsCollection;

        if (!breakpointsConfig[breakpointValue]) {
            for (var t = availableBreakpoints.indexOf(breakpointValue); t >= 0; t--)
                if ("undefined" != typeof breakpointsConfig[availableBreakpoints[t]]) {
                    breakpoint = availableBreakpoints[t];
                    break
                }
        } else {
            breakpoint = breakpointValue;
        }

        return breakpoint;

    };

    ProgressiveImage.prototype.getImage = function (breakpointValue) {

        var _this = this;

        var breakpoint = this.getAvailableBreakpoint(
                breakpointValue,
                this.breakpointsConfigObj,
                this.options.breakpoints
        );

        var newImg = new Image;

        if (_this.positionConfig) {
            var position = this.positionConfig[breakpoint] || this.positionConfig['none'];
            updatePosition(this.element, this.options.strategy, position);
        }

        if (this.options.forceUpdate) {
            updateSrc(this.breakpointsConfigObj[breakpoint], _this.element, _this.options.strategy);
        }

        newImg.src = this.breakpointsConfigObj[breakpoint];

        newImg.onload = function () {
            updateSrc(newImg.src, _this.element, _this.options.strategy);
        };

    };

    ProgressiveImage.prototype.checkBreakpoint = function () {

        if (this.options.requestOnVisible && !this.visible) return;

        if (this.currentBreakPoint !== getBreakpoint()) {
            this.currentBreakPoint = getBreakpoint();
            this.getImage(this.currentBreakPoint);
        }

    };

    ProgressiveImage.prototype.delete = function () {
        if (this.checkViewportTh) {
            $(window).off('scroll', this.checkViewportTh);
            $(window).off('resize', this.checkViewportTh);
        }
    };

    function updatePosition(element, strategy, position) {

        if (!position) position = {};

        position.x = position.x || 0.5;
        position.y = position.y || 0.5;

        if (strategy === Config.BACKGROUND_STRATEGY) {

            element.style.backgroundPosition =
                    _mapPositionToPercent(position.x) + ' ' + _mapPositionToPercent(position.y)
        }
    }

    function getStrategyTypeByTagName(tagName) {

        var strategy;

        switch (tagName) {

            case 'IMG':
                strategy = Config.IMAGE_STRATEGY;
                break;

            case 'VIDEO':
                strategy = Config.POSTER_STRATEGY;
                break;

            case 'A':
                strategy = Config.LINK_STRATEGY;
                break;

            default:
                strategy = Config.BACKGROUND_STRATEGY;
        }

        return strategy;

    }

    function updateSrc(src, element) {

        var strategy = getStrategyTypeByTagName(element.tagName);

        if (strategy === Config.IMAGE_STRATEGY) {
            element.src = src;
        }

        if (strategy === Config.LINK_STRATEGY) {
            element.href = src;
        }

        if (strategy === Config.POSTER_STRATEGY) {
            Utils.setBgImage(element, src);
        }

        if (strategy === Config.BACKGROUND_STRATEGY) {
            Utils.setBgImage(element, src);
        }

        return element;
    }

    function getUrl(element, strategy) {

        if (strategy === Config.IMAGE_STRATEGY) {
            return element.src;
        }

        if (strategy === Config.BACKGROUND_STRATEGY) {
            var backgroundImage = element.style.backgroundImage;
            return backgroundImage.slice(4, -1).replace(/["|']/g, "");
        }

        throw new Error('Get url invalid strategy detected: ', strategy);
    }

    function _mapPositionToPercent(value) {
        return value * 100 + '%';
    }

    function getBreakpoint() {
        return Utils.getBreakpoint();
    }

    return ProgressiveImage;

});<|endoftext|>


<commit_before>import * as CSS from './lib/css';
import * as DOM from './lib/dom';
import EventManager from './lib/event-manager';
import updateGeometry from './update-geometry';
import updateScroll from './update-scroll';
import { toInt, outerWidth } from './lib/util';

import clickRail from './handlers/click-rail';
import dragThumb from './handlers/drag-thumb';
import keyboard from './handlers/keyboard';
import wheel from './handlers/mouse-wheel';
import touch from './handlers/touch';

const defaultSettings = () => ({
  handlers: ['click-rail', 'drag-thumb', 'keyboard', 'wheel', 'touch'],
  maxScrollbarLength: null,
  minScrollbarLength: null,
  scrollXMarginOffset: 0,
  scrollYMarginOffset: 0,
  suppressScrollX: false,
  suppressScrollY: false,
  swipePropagation: true,
  swipeEasing: true,
  useBothWheelAxes: false,
  wheelPropagation: false,
  wheelSpeed: 1,
});

const handlers = {
  'click-rail': clickRail,
  'drag-thumb': dragThumb,
  keyboard,
  wheel,
  touch,
};

const psClassName = 'ps';

export default class PerfectScrollbar {
  constructor(element, userSettings = {}) {
    if (typeof element === 'string') {
      element = document.querySelector(element);
    }

    if (!element || !element.nodeName) {
      throw new Error('no element is specified to initialize PerfectScrollbar');
    }

    this.element = element;

    element.classList.add(psClassName);

    this.settings = defaultSettings();
    for (const key in userSettings) {
      this.settings[key] = userSettings[key];
    }

    this.containerWidth = null;
    this.containerHeight = null;
    this.contentWidth = null;
    this.contentHeight = null;

    const focus = () => element.classList.add('ps--focus');
    const blur = () => element.classList.remove('ps--focus');

    this.isRtl = CSS.get(element).direction === 'rtl';
    this.isNegativeScroll = (() => {
      const originalScrollLeft = element.scrollLeft;
      let result = null;
      element.scrollLeft = -1;
      result = element.scrollLeft < 0;
      element.scrollLeft = originalScrollLeft;
      return result;
    })();
    this.negativeScrollAdjustment = this.isNegativeScroll
      ? element.scrollWidth - element.clientWidth
      : 0;
    this.event = new EventManager();
    this.ownerDocument = element.ownerDocument || document;

    this.scrollbarXRail = DOM.div('ps__rail-x');
    element.appendChild(this.scrollbarXRail);
    this.scrollbarX = DOM.div('ps__thumb-x');
    this.scrollbarXRail.appendChild(this.scrollbarX);
    this.scrollbarX.setAttribute('tabindex', 0);
    this.event.bind(this.scrollbarX, 'focus', focus);
    this.event.bind(this.scrollbarX, 'blur', blur);
    this.scrollbarXActive = null;
    this.scrollbarXWidth = null;
    this.scrollbarXLeft = null;
    const railXStyle = CSS.get(this.scrollbarXRail);
    this.scrollbarXBottom = parseInt(railXStyle.bottom, 10);
    if (isNaN(this.scrollbarXBottom)) {
      this.isScrollbarXUsingBottom = false;
      this.scrollbarXTop = toInt(railXStyle.top);
    } else {
      this.isScrollbarXUsingBottom = true;
    }
    this.railBorderXWidth =
      toInt(railXStyle.borderLeftWidth) + toInt(railXStyle.borderRightWidth);
    // Set rail to display:block to calculate margins
    CSS.set(this.scrollbarXRail, { display: 'block' });
    this.railXMarginWidth =
      toInt(railXStyle.marginLeft) + toInt(railXStyle.marginRight);
    CSS.set(this.scrollbarXRail, { display: '' });
    this.railXWidth = null;
    this.railXRatio = null;

    this.scrollbarYRail = DOM.div('ps__rail-y');
    element.appendChild(this.scrollbarYRail);
    this.scrollbarY = DOM.div('ps__thumb-y');
    this.scrollbarYRail.appendChild(this.scrollbarY);
    this.scrollbarY.setAttribute('tabindex', 0);
    this.event.bind(this.scrollbarY, 'focus', focus);
    this.event.bind(this.scrollbarY, 'blur', blur);
    this.scrollbarYActive = null;
    this.scrollbarYHeight = null;
    this.scrollbarYTop = null;
    const railYStyle = CSS.get(this.scrollbarYRail);
    this.scrollbarYRight = parseInt(railYStyle.right, 10);
    if (isNaN(this.scrollbarYRight)) {
      this.isScrollbarYUsingRight = false;
      this.scrollbarYLeft = toInt(railYStyle.left);
    } else {
      this.isScrollbarYUsingRight = true;
    }
    this.scrollbarYOuterWidth = this.isRtl ? outerWidth(this.scrollbarY) : null;
    this.railBorderYWidth =
      toInt(railYStyle.borderTopWidth) + toInt(railYStyle.borderBottomWidth);
    CSS.set(this.scrollbarYRail, { display: 'block' });
    this.railYMarginHeight =
      toInt(railYStyle.marginTop) + toInt(railYStyle.marginBottom);
    CSS.set(this.scrollbarYRail, { display: '' });
    this.railYHeight = null;
    this.railYRatio = null;

    this.settings.handlers.forEach(handlerName => handlers[handlerName](this));

    this.event.bind(this.element, 'scroll', () => updateGeometry(this));
    updateGeometry(this);
  }

  get isInitialized() {
    return this.element.classList.contains(psClassName);
  }

  update() {
    if (!this.isInitialized) {
      return;
    }

    // Recalcuate negative scrollLeft adjustment
    this.negativeScrollAdjustment = this.isNegativeScroll
      ? this.element.scrollWidth - this.element.clientWidth
      : 0;

    // Recalculate rail margins
    CSS.set(this.scrollbarXRail, { display: 'block' });
    CSS.set(this.scrollbarYRail, { display: 'block' });
    this.railXMarginWidth =
      toInt(CSS.get(this.scrollbarXRail).marginLeft) +
      toInt(CSS.get(this.scrollbarXRail).marginRight);
    this.railYMarginHeight =
      toInt(CSS.get(this.scrollbarYRail).marginTop) +
      toInt(CSS.get(this.scrollbarYRail).marginBottom);

    // Hide scrollbars not to affect scrollWidth and scrollHeight
    CSS.set(this.scrollbarXRail, { display: 'none' });
    CSS.set(this.scrollbarYRail, { display: 'none' });

    updateGeometry(this);

    // Update top/left scroll to trigger events
    updateScroll(this, 'top', this.element.scrollTop);
    updateScroll(this, 'left', this.element.scrollLeft);

    CSS.set(this.scrollbarXRail, { display: '' });
    CSS.set(this.scrollbarYRail, { display: '' });
  }

  destroy() {
    if (!this.isInitialized) {
      return;
    }

    this.event.unbindAll();
    DOM.remove(this.scrollbarX);
    DOM.remove(this.scrollbarY);
    DOM.remove(this.scrollbarXRail);
    DOM.remove(this.scrollbarYRail);
    this.removePsClasses();

    // unset elements
    this.element = null;
    this.scrollbarX = null;
    this.scrollbarY = null;
    this.scrollbarXRail = null;
    this.scrollbarYRail = null;
  }

  removePsClasses() {
    for (let i = 0; i < this.element.classList.length; i++) {
      const className = this.element.classList[i];
      if (className === 'ps' || className.indexOf('ps-') === 0) {
        this.element.classList.remove(className);
      }
    }
  }
}
<commit_msg>Do not fire custom events on update()<commit_after>import * as CSS from './lib/css';
import * as DOM from './lib/dom';
import EventManager from './lib/event-manager';
import updateGeometry from './update-geometry';
import updateScroll from './update-scroll';
import { toInt, outerWidth } from './lib/util';

import clickRail from './handlers/click-rail';
import dragThumb from './handlers/drag-thumb';
import keyboard from './handlers/keyboard';
import wheel from './handlers/mouse-wheel';
import touch from './handlers/touch';

const defaultSettings = () => ({
  handlers: ['click-rail', 'drag-thumb', 'keyboard', 'wheel', 'touch'],
  maxScrollbarLength: null,
  minScrollbarLength: null,
  scrollXMarginOffset: 0,
  scrollYMarginOffset: 0,
  suppressScrollX: false,
  suppressScrollY: false,
  swipePropagation: true,
  swipeEasing: true,
  useBothWheelAxes: false,
  wheelPropagation: false,
  wheelSpeed: 1,
});

const handlers = {
  'click-rail': clickRail,
  'drag-thumb': dragThumb,
  keyboard,
  wheel,
  touch,
};

const psClassName = 'ps';

export default class PerfectScrollbar {
  constructor(element, userSettings = {}) {
    if (typeof element === 'string') {
      element = document.querySelector(element);
    }

    if (!element || !element.nodeName) {
      throw new Error('no element is specified to initialize PerfectScrollbar');
    }

    this.element = element;

    element.classList.add(psClassName);

    this.settings = defaultSettings();
    for (const key in userSettings) {
      this.settings[key] = userSettings[key];
    }

    this.containerWidth = null;
    this.containerHeight = null;
    this.contentWidth = null;
    this.contentHeight = null;

    const focus = () => element.classList.add('ps--focus');
    const blur = () => element.classList.remove('ps--focus');

    this.isRtl = CSS.get(element).direction === 'rtl';
    this.isNegativeScroll = (() => {
      const originalScrollLeft = element.scrollLeft;
      let result = null;
      element.scrollLeft = -1;
      result = element.scrollLeft < 0;
      element.scrollLeft = originalScrollLeft;
      return result;
    })();
    this.negativeScrollAdjustment = this.isNegativeScroll
      ? element.scrollWidth - element.clientWidth
      : 0;
    this.event = new EventManager();
    this.ownerDocument = element.ownerDocument || document;

    this.scrollbarXRail = DOM.div('ps__rail-x');
    element.appendChild(this.scrollbarXRail);
    this.scrollbarX = DOM.div('ps__thumb-x');
    this.scrollbarXRail.appendChild(this.scrollbarX);
    this.scrollbarX.setAttribute('tabindex', 0);
    this.event.bind(this.scrollbarX, 'focus', focus);
    this.event.bind(this.scrollbarX, 'blur', blur);
    this.scrollbarXActive = null;
    this.scrollbarXWidth = null;
    this.scrollbarXLeft = null;
    const railXStyle = CSS.get(this.scrollbarXRail);
    this.scrollbarXBottom = parseInt(railXStyle.bottom, 10);
    if (isNaN(this.scrollbarXBottom)) {
      this.isScrollbarXUsingBottom = false;
      this.scrollbarXTop = toInt(railXStyle.top);
    } else {
      this.isScrollbarXUsingBottom = true;
    }
    this.railBorderXWidth =
      toInt(railXStyle.borderLeftWidth) + toInt(railXStyle.borderRightWidth);
    // Set rail to display:block to calculate margins
    CSS.set(this.scrollbarXRail, { display: 'block' });
    this.railXMarginWidth =
      toInt(railXStyle.marginLeft) + toInt(railXStyle.marginRight);
    CSS.set(this.scrollbarXRail, { display: '' });
    this.railXWidth = null;
    this.railXRatio = null;

    this.scrollbarYRail = DOM.div('ps__rail-y');
    element.appendChild(this.scrollbarYRail);
    this.scrollbarY = DOM.div('ps__thumb-y');
    this.scrollbarYRail.appendChild(this.scrollbarY);
    this.scrollbarY.setAttribute('tabindex', 0);
    this.event.bind(this.scrollbarY, 'focus', focus);
    this.event.bind(this.scrollbarY, 'blur', blur);
    this.scrollbarYActive = null;
    this.scrollbarYHeight = null;
    this.scrollbarYTop = null;
    const railYStyle = CSS.get(this.scrollbarYRail);
    this.scrollbarYRight = parseInt(railYStyle.right, 10);
    if (isNaN(this.scrollbarYRight)) {
      this.isScrollbarYUsingRight = false;
      this.scrollbarYLeft = toInt(railYStyle.left);
    } else {
      this.isScrollbarYUsingRight = true;
    }
    this.scrollbarYOuterWidth = this.isRtl ? outerWidth(this.scrollbarY) : null;
    this.railBorderYWidth =
      toInt(railYStyle.borderTopWidth) + toInt(railYStyle.borderBottomWidth);
    CSS.set(this.scrollbarYRail, { display: 'block' });
    this.railYMarginHeight =
      toInt(railYStyle.marginTop) + toInt(railYStyle.marginBottom);
    CSS.set(this.scrollbarYRail, { display: '' });
    this.railYHeight = null;
    this.railYRatio = null;

    this.settings.handlers.forEach(handlerName => handlers[handlerName](this));

    this.event.bind(this.element, 'scroll', () => updateGeometry(this));
    updateGeometry(this);
  }

  get isInitialized() {
    return this.element.classList.contains(psClassName);
  }

  update() {
    if (!this.isInitialized) {
      return;
    }

    // Recalcuate negative scrollLeft adjustment
    this.negativeScrollAdjustment = this.isNegativeScroll
      ? this.element.scrollWidth - this.element.clientWidth
      : 0;

    // Recalculate rail margins
    CSS.set(this.scrollbarXRail, { display: 'block' });
    CSS.set(this.scrollbarYRail, { display: 'block' });
    this.railXMarginWidth =
      toInt(CSS.get(this.scrollbarXRail).marginLeft) +
      toInt(CSS.get(this.scrollbarXRail).marginRight);
    this.railYMarginHeight =
      toInt(CSS.get(this.scrollbarYRail).marginTop) +
      toInt(CSS.get(this.scrollbarYRail).marginBottom);

    // Hide scrollbars not to affect scrollWidth and scrollHeight
    CSS.set(this.scrollbarXRail, { display: 'none' });
    CSS.set(this.scrollbarYRail, { display: 'none' });

    updateGeometry(this);

    CSS.set(this.scrollbarXRail, { display: '' });
    CSS.set(this.scrollbarYRail, { display: '' });
  }

  destroy() {
    if (!this.isInitialized) {
      return;
    }

    this.event.unbindAll();
    DOM.remove(this.scrollbarX);
    DOM.remove(this.scrollbarY);
    DOM.remove(this.scrollbarXRail);
    DOM.remove(this.scrollbarYRail);
    this.removePsClasses();

    // unset elements
    this.element = null;
    this.scrollbarX = null;
    this.scrollbarY = null;
    this.scrollbarXRail = null;
    this.scrollbarYRail = null;
  }

  removePsClasses() {
    for (let i = 0; i < this.element.classList.length; i++) {
      const className = this.element.classList[i];
      if (className === 'ps' || className.indexOf('ps-') === 0) {
        this.element.classList.remove(className);
      }
    }
  }
}
<|endoftext|>


<commit_before>var axios = require('axios');

var protocol = 'http:';
var baseUrl = `${protocol}//ebird.org/ws1.1`
var defaultParams = {
    fmt: 'json',
};

var standardGet = (path) => {
    return (params) => {
        return axios.get(`${baseUrl}${path}`, {
            params: {
                ...defaultParams,
                ...params,
            },
        }).then((response) => {
            return response.data;
        }).catch((response) => {
            throw response.data;
        });
    };
};

var ebird = {
    data: {
        obs: {
            geo: {
                recent: standardGet('/data/obs/geo/recent'),
            },

            geo_spp: {
                recent: standardGet('/data/obs/geo_spp/recent'),
            },

            hotspot: {
                recent: standardGet('/data/obs/hotspot/recent'),
            },

            hotspot_spp: {
                recent: standardGet('/data/obs/hotspot_spp/recent'),
            },

            loc: {
                recent: standardGet('/data/obs/loc/recent'),
            },

            loc_spp: {
                recent: standardGet('/data/obs/loc_spp/recent'),
            },

            region: {
                recent: standardGet('/data/obs/region/recent'),
            },

            region_spp: {
                recent: standardGet('/data/obs/region_spp/recent'),
            },
        },

        notable: {
            geo: {
                recent: standardGet('/data/notable/geo/recent'),
            },

            hotspot: {
                recent: standardGet('/data/notable/hotspot/recent'),
            },

            loc: {
                recent: standardGet('/data/notable/loc/recent'),
            },

            region: {
                recent: standardGet('/data/notable/region/recent'),
            },
        },

        nearest: {
            geo_spp: {
                recent: standardGet('/data/nearest/geo_spp/recent'),
            }
        },
    },

    product: {
        obs: {
            hotspot: {
                recent: standardGet('/product/obs/hotspot/recent'),
            },
        },
    },

    ref: {
        taxa: standardGet('/ref/taxa/ebird'),

        hotspot: {
            region: standardGet('/ref/hotspot/region'),
            geo: standardGet('/ref/hotspot/geo'),
        },

        location: (params) => {
            var func = params.match ? standardGet('/ref/location/find') : standardGet('/ref/location/list');
            return func(params);
        }
    },
};

module.exports = ebird;
<commit_msg>Added ref/region api (undocumented)<commit_after>var axios = require('axios');

var protocol = 'http:';
var baseUrl = `${protocol}//ebird.org/ws1.1`
var defaultParams = {
    fmt: 'json',
};

var standardGet = (path) => {
    return (params) => {
        return axios.get(`${baseUrl}${path}`, {
            params: {
                ...defaultParams,
                ...params,
            },
        }).then((response) => {
            return response.data;
        }).catch((response) => {
            throw response.data;
        });
    };
};

var ebird = {
    data: {
        obs: {
            geo: {
                recent: standardGet('/data/obs/geo/recent'),
            },

            geo_spp: {
                recent: standardGet('/data/obs/geo_spp/recent'),
            },

            hotspot: {
                recent: standardGet('/data/obs/hotspot/recent'),
            },

            hotspot_spp: {
                recent: standardGet('/data/obs/hotspot_spp/recent'),
            },

            loc: {
                recent: standardGet('/data/obs/loc/recent'),
            },

            loc_spp: {
                recent: standardGet('/data/obs/loc_spp/recent'),
            },

            region: {
                recent: standardGet('/data/obs/region/recent'),
            },

            region_spp: {
                recent: standardGet('/data/obs/region_spp/recent'),
            },
        },

        notable: {
            geo: {
                recent: standardGet('/data/notable/geo/recent'),
            },

            hotspot: {
                recent: standardGet('/data/notable/hotspot/recent'),
            },

            loc: {
                recent: standardGet('/data/notable/loc/recent'),
            },

            region: {
                recent: standardGet('/data/notable/region/recent'),
            },
        },

        nearest: {
            geo_spp: {
                recent: standardGet('/data/nearest/geo_spp/recent'),
            }
        },
    },

    product: {
        obs: {
            hotspot: {
                recent: standardGet('/product/obs/hotspot/recent'),
            },
        },
    },

    ref: {
        taxa: standardGet('/ref/taxa/ebird'),

        hotspot: {
            region: standardGet('/ref/hotspot/region'),
            geo: standardGet('/ref/hotspot/geo'),
        },

        location: (params) => {
            var func = params.match ? standardGet('/ref/location/find') : standardGet('/ref/location/list');
            return func(params);
        },
        region: standardGet('/ref/region/find'),
    },
};

module.exports = ebird;
<|endoftext|>


<commit_before>import { shuffle } from 'underscore'
import helpers from './helpers/helpers'
const { flatten, sampleArray, roundDown, decodeFloat } = helpers
import { scaleOrdinal, schemeCategory10, scaleLinear } from 'd3-scale'
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY } from 'd3-force'
import { quadtree as d3quadtree } from 'd3-quadtree'
import { select, selectAll, event } from 'd3-selection'
import { drag as d3drag } from 'd3-drag'
import { range } from 'd3-array'
import { getData } from './api'
import "../main.scss"
import { Nodes, initializeNodes, setFollowedBy, initializeFollowings, cycle } from './nodes'
import { initFlot, initNetworkConnectivity, initDiversityChart, initNodeDiversityChart } from './charts.js'
import { cycleDur, width, height } from './config.js'
import './datasetPicker'
import './visualization'
import mediator from './mediator'
import './editableParameters/diversitySlider'
import './editableParameters/controls'

let start, lastCycleTime = 0, rafID = null,
  halo = document.querySelector("#halo"),
  popoverElement = document.querySelector("#popover"),
  popoverID = popoverElement.querySelector(".node_id"),
  popoverDiversity = popoverElement.querySelector('.node_diversity'),
  quadtree = d3quadtree(),
  renderer = new THREE.WebGLRenderer({ alpha: true, canvas: document.querySelector("#webgl-canvas") }),
  scene = new THREE.Scene(),
  camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 10000),
  nodePositions, nodeSizesColors,
  edgeGeometry = new THREE.BufferGeometry(),
  nodeGeometry = new THREE.BufferGeometry(),
  edgeColorsStartTimes, edgeColorsStartTimesBuffer,
  edgeVertices, lastOccupiedEdgeVertexIndex,
  nodePositionBuffer, edgeVerticesBuffer, nodeSizesColorsBuffer,
  force = forceSimulation(),
  emptyNode = new THREE.Vector2(),
  links, nodeData, edgeData,
  cycleSID = null,
  updateLinksSID = null, updateLinksNodeIndex = 0,
  maxFollowedByLength = 0, minFollowedByLength = Infinity,
  nodeSizeScale = scaleLinear().range([5, 25]).clamp(true),
  peakTime = 250.0, totalTime = 350.0,
  canvasLeft = 0, canvasTop = 0, match, activeNode = null,
  lineSegments, points

scene.add(camera)
camera.position.z = 1000

const nodeMaterial = new THREE.ShaderMaterial({
  vertexShader: document.getElementById("node-vertexshader").textContent,
  fragmentShader: document.getElementById("node-fragmentshader").textContent,
  transparent: true
})

const edgeMaterial = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0.0 },
    peakTime: { value: peakTime },
    totalTime: { value: totalTime },
    defaultOpacity: { value: 0.08 },
    color: {
      type: 'c',
      value: new THREE.Color(0xABABBF)
    }
  },
  vertexShader: document.getElementById("edge-vertexshader").textContent,
  fragmentShader: document.getElementById("edge-fragmentshader").textContent,
  transparent: true
})

renderer.setSize(width, height)
renderer.setPixelRatio(window.devicePixelRatio)

const updateMinMaxFollowedBy = length => {
  if(length > maxFollowedByLength) maxFollowedByLength = length
  if(length < minFollowedByLength) minFollowedByLength = length
}
const { top, left } = document.querySelector("#webgl-canvas").getBoundingClientRect()

const loop = () => {
  const d = Date.now() - start

  const shouldUpdate = Math.random() < 0.5 // perf

  edgeMaterial.uniforms['uTime'].value = d

  // initNetworkConnectivity(Nodes)
  // initDiversityChart(Nodes)
  links = []
  for(let i=0; i<Nodes.length; i++) {
    let n = Nodes[i]
    if(n.following.length) {
      for(let j=0; j<n.following.length; j++) {
        let target
        for(let k=0; k<Nodes.length; k++) {
          if(Nodes[k].id === n.following[j].id) {
            target = Nodes[k]
            break
          }
        }
        links.push({ source: n, target })
      }
    }
  }

  for(let i=0; i<links.length; i++) {
    const link = links[i]
    let source, target
    if(link) {
      source = link.source
      target = link.target
    } else {
      source = emptyNode
      target = emptyNode
    }

    // wrap the below in conditional, e.g. source.index === 0, to highlight only one node

    if(i < lastOccupiedEdgeVertexIndex) {
      edgeVertices[i * 2 * 3] = source.x - width / 2
      edgeVertices[i * 2 * 3 + 1] = -(source.y - height / 2)
      edgeVertices[i * 2 * 3 + 3] = target.x - width / 2
      edgeVertices[i * 2 * 3 + 4] = -(target.y - height / 2)
    }

    if(source.newlyFollowing && source.newlyFollowing.length !== source.following.length) {
      const newlyFollowingIDs = source.newlyFollowing.map(d => d.id)

      if(newlyFollowingIDs.indexOf(target.id) > -1) {
        if((d - edgeColorsStartTimes[i * 2 * 2 + 1] > cycleDur) && (d - edgeColorsStartTimes[i * 2 * 2 + 3] > cycleDur)) {
          // source
          edgeColorsStartTimes[i * 2 * 2 + 1] = d - peakTime
          // target
          edgeColorsStartTimes[i * 2 * 2 + 3] = d
        }
      }
    }
  }

  if(lastOccupiedEdgeVertexIndex > links.length) {
    for(let i=links.length; i<lastOccupiedEdgeVertexIndex; i++) {
      edgeVertices[i * 2 * 3] = 0
      edgeVertices[i * 2 * 3 + 1] = 0
      edgeVertices[i * 2 * 3 + 3] = 0
      edgeVertices[i * 2 * 3 + 4] = 0
    }
  }

  lastOccupiedEdgeVertexIndex = links.length

  const diff = d - lastCycleTime
  const targetIndex = Math.max(0, Math.min(Math.round((diff / cycleDur) * Nodes.length), Nodes.length))

  if(targetIndex < updateLinksNodeIndex) { updateLinksNodeIndex = 0 } // wrap around

  for(let i=updateLinksNodeIndex; i<targetIndex; i++) {
    let node = Nodes[i]
    node.adjustFollowing()
    setFollowedBy(node)
  }

  updateLinksNodeIndex = targetIndex

  if(shouldUpdate) {
    force.force("link").links(links)
    force.alphaTarget(0.1).restart()
    quadtree = d3quadtree().extent([[-1, -1], [width, height]])
    minFollowedByLength = Infinity
    maxFollowedByLength = 0
  }

  for(let i=0; i < Nodes.length; i++) {
    let node = Nodes[i]
    nodePositions[i * 2] = node.x - width / 2
    nodePositions[i * 2 + 1] = -(node.y - height / 2)
    nodeSizesColors[i * 2] = nodeSizeScale(node.followedBy.length)
    if(node.belief === "conservative") { // red
      nodeSizesColors[i * 2 + 1] = decodeFloat(254, 25, 83, 254)
    } else if(node.belief === "liberal") { // blue
      nodeSizesColors[i * 2 + 1] = decodeFloat(0, 190, 254, 254)
    } else { // purple
      nodeSizesColors[i * 2 + 1] = decodeFloat(254, 254, 254, 254)
    }

    if(activeNode && node.id === activeNode.id) {
      halo.style.transform = `translate3d(${canvasLeft + node.x - 6}px, ${canvasTop + node.y - 6}px, 0)`
    }

    if(shouldUpdate) {
      quadtree.add([node.x, node.y, node])
      // initNetworkConnectivity(Nodes)
      updateMinMaxFollowedBy(node.followedBy.length)
    }
  }

  nodeSizeScale.domain([minFollowedByLength, maxFollowedByLength])
  edgeVerticesBuffer.needsUpdate = true
  edgeColorsStartTimesBuffer.needsUpdate = true
  nodePositionBuffer.needsUpdate = true
  nodeSizesColorsBuffer.needsUpdate = true
  renderer.render(scene, camera)

  rafID = requestAnimationFrame(loop)
}

const initialize = () => {
  canvasTop = top
  canvasLeft = left

  nodePositions = new Float32Array(Nodes.length * 2)
  nodeSizesColors = new Float32Array(Nodes.length * 2)
  edgeVertices = new Float32Array(edgeData.length * 2 * 6)
  edgeColorsStartTimes = new Float32Array(edgeData.length * 2 * 4)

  nodePositionBuffer = new THREE.BufferAttribute(nodePositions, 2)
  nodeGeometry.addAttribute("position", nodePositionBuffer)
  nodeSizesColorsBuffer = new THREE.BufferAttribute(nodeSizesColors, 2)
  nodeGeometry.addAttribute("sizeColor", nodeSizesColorsBuffer)

  edgeVerticesBuffer = new THREE.BufferAttribute(edgeVertices, 3)
  edgeColorsStartTimesBuffer = new THREE.BufferAttribute(edgeColorsStartTimes, 2)

  edgeGeometry.addAttribute("position", edgeVerticesBuffer)
  edgeGeometry.addAttribute("colorTime", edgeColorsStartTimesBuffer)

  lineSegments = new THREE.LineSegments(edgeGeometry, edgeMaterial)
  scene.add(lineSegments)
  points = new THREE.Points(nodeGeometry, nodeMaterial)
  scene.add(points)

  force.nodes(Nodes)
    .force("link", forceLink().id(d => d.id))
    .force("charge", forceManyBody().strength(-10).distanceMax(200))
    .force("center", forceCenter(width / 2, height / 2))
    .force("vertical", forceY().strength(0.01))
    .force("horizontal", forceX().strength(0.01))
    .velocityDecay(0.6)

  links = edgeData

  links.forEach(l => {
    const source = Nodes.find(n => n.id === +l.source)
    const target = Nodes.find(n => n.id === +l.target)
    target.following = target.following.concat(source)
  })

  Nodes.forEach(n => n.init())
  // initFlot(Nodes[20]);
}

const play = () => {
  cycleSID = setInterval(() => {
    lastCycleTime = Date.now() - start
    cycle()
  }, cycleDur)

  rafID = requestAnimationFrame(loop)
}

const pause = () => {
  window.clearInterval(cycleSID)
  window.cancelAnimationFrame(rafID)
}

const stop = () => {
  pause()

  mediator.publish("stopped")
}

const revealHalo = () => {
  halo.classList.add("active")
}

const removeHalo = () => {
  halo.classList.remove("active")
}

document.addEventListener("mousemove", e => {
  e.preventDefault()
  popoverElement.style.left = e.pageX + 'px'
  popoverElement.style.top = e.pageY + 'px'

  match = quadtree.find(e.pageX - canvasLeft, e.pageY - canvasTop, 3)
  if(match) {
    const diversity = match[2].diversity
    popoverElement.style.display = 'block'
    popoverID.innerHTML = match[2].id
    popoverDiversity.innerHTML = 'diversity: ' + diversity.toFixed(2)
    if(diversity > match[2].desiredDiversity) {
      popoverElement.setAttribute("data-satisfied", true)
    } else {
      popoverElement.setAttribute("data-satisfied", false)
    }
  } else {
    popoverElement.style.display = 'none'
  }
})

document.addEventListener("click", e => {
  e.preventDefault()
  if(match) {
    if(activeNode && activeNode.id === match[2].id) {
      activeNode = null
      removeHalo()
    } else {
      activeNode = match[2]
      initFlot(activeNode)
      revealHalo()
    }
  }
})

mediator.subscribe("converged", () => {
  stop()
  renderer.domElement.classList.add("flash")
  setTimeout(() => {
    renderer.domElement.classList.remove("flash")
  }, 800)
})

mediator.subscribe("play", play)

mediator.subscribe("pause", pause)

mediator.subscribe("selectDataset", dataset => {
  window.clearInterval(cycleSID)
  window.cancelAnimationFrame(rafID)

  Promise.all([dataset.nodes, dataset.edges].map(getData))
    .then(data => {
      scene.remove(lineSegments)
      scene.remove(points)

      nodeData = shuffle(data[0])

      nodeData.splice(roundDown(nodeData.length, 3)) // nodes length must be multiple of 3

      edgeData = shuffle(data[1].filter(d =>
        [+d.source, +d.target].every(id => nodeData.find(n => n.node_id === id))))

      edgeData.splice(roundDown(edgeData.length, 3))

      lastCycleTime = 0
      start = Date.now()
      initializeNodes(nodeData, dataset.beliefs)
      initialize()

      mediator.publish("data-initialized")
    })
})
<commit_msg>ensure were disposing of objects<commit_after>import { shuffle } from 'underscore'
import helpers from './helpers/helpers'
const { flatten, sampleArray, roundDown, decodeFloat } = helpers
import { scaleOrdinal, schemeCategory10, scaleLinear } from 'd3-scale'
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY } from 'd3-force'
import { quadtree as d3quadtree } from 'd3-quadtree'
import { select, selectAll, event } from 'd3-selection'
import { drag as d3drag } from 'd3-drag'
import { range } from 'd3-array'
import { getData } from './api'
import "../main.scss"
import { Nodes, initializeNodes, setFollowedBy, initializeFollowings, cycle } from './nodes'
import { initFlot, initNetworkConnectivity, initDiversityChart, initNodeDiversityChart } from './charts.js'
import { cycleDur, width, height } from './config.js'
import './datasetPicker'
import './visualization'
import mediator from './mediator'
import './editableParameters/diversitySlider'
import './editableParameters/controls'

let start, lastCycleTime = 0, rafID = null,
  halo = document.querySelector("#halo"),
  popoverElement = document.querySelector("#popover"),
  popoverID = popoverElement.querySelector(".node_id"),
  popoverDiversity = popoverElement.querySelector('.node_diversity'),
  quadtree = d3quadtree(),
  renderer = new THREE.WebGLRenderer({ alpha: true, canvas: document.querySelector("#webgl-canvas") }),
  scene = new THREE.Scene(),
  camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 10000),
  nodePositions, nodeSizesColors,
  edgeGeometry, nodeGeometry,
  edgeColorsStartTimes, edgeColorsStartTimesBuffer,
  edgeVertices, lastOccupiedEdgeVertexIndex,
  nodePositionBuffer, edgeVerticesBuffer, nodeSizesColorsBuffer,
  force = forceSimulation(),
  emptyNode = new THREE.Vector2(),
  links, nodeData, edgeData,
  cycleSID = null,
  updateLinksSID = null, updateLinksNodeIndex = 0,
  maxFollowedByLength = 0, minFollowedByLength = Infinity,
  nodeSizeScale = scaleLinear().range([5, 25]).clamp(true),
  peakTime = 250.0, totalTime = 350.0,
  canvasLeft = 0, canvasTop = 0, match, activeNode = null,
  lineSegments, points,
  nodeMaterial, edgeMaterial

scene.add(camera)
camera.position.z = 1000

renderer.setSize(width, height)
renderer.setPixelRatio(window.devicePixelRatio)

const updateMinMaxFollowedBy = length => {
  if(length > maxFollowedByLength) maxFollowedByLength = length
  if(length < minFollowedByLength) minFollowedByLength = length
}
const { top, left } = document.querySelector("#webgl-canvas").getBoundingClientRect()

const loop = () => {
  const d = Date.now() - start

  const shouldUpdate = Math.random() < 0.5 // perf

  edgeMaterial.uniforms['uTime'].value = d

  // initNetworkConnectivity(Nodes)
  // initDiversityChart(Nodes)
  links = []
  for(let i=0; i<Nodes.length; i++) {
    let n = Nodes[i]
    if(n.following.length) {
      for(let j=0; j<n.following.length; j++) {
        let target
        for(let k=0; k<Nodes.length; k++) {
          if(Nodes[k].id === n.following[j].id) {
            target = Nodes[k]
            break
          }
        }
        links.push({ source: n, target })
      }
    }
  }

  for(let i=0; i<links.length; i++) {
    const link = links[i]
    let source, target
    if(link) {
      source = link.source
      target = link.target
    } else {
      source = emptyNode
      target = emptyNode
    }

    // wrap the below in conditional, e.g. source.index === 0, to highlight only one node

    if(i < lastOccupiedEdgeVertexIndex) {
      edgeVertices[i * 2 * 3] = source.x - width / 2
      edgeVertices[i * 2 * 3 + 1] = -(source.y - height / 2)
      edgeVertices[i * 2 * 3 + 3] = target.x - width / 2
      edgeVertices[i * 2 * 3 + 4] = -(target.y - height / 2)
    }

    if(source.newlyFollowing && source.newlyFollowing.length !== source.following.length) {
      const newlyFollowingIDs = source.newlyFollowing.map(d => d.id)

      if(newlyFollowingIDs.indexOf(target.id) > -1) {
        if((d - edgeColorsStartTimes[i * 2 * 2 + 1] > cycleDur) && (d - edgeColorsStartTimes[i * 2 * 2 + 3] > cycleDur)) {
          // source
          edgeColorsStartTimes[i * 2 * 2 + 1] = d - peakTime
          // target
          edgeColorsStartTimes[i * 2 * 2 + 3] = d
        }
      }
    }
  }

  if(lastOccupiedEdgeVertexIndex > links.length) {
    for(let i=links.length; i<lastOccupiedEdgeVertexIndex; i++) {
      edgeVertices[i * 2 * 3] = 0
      edgeVertices[i * 2 * 3 + 1] = 0
      edgeVertices[i * 2 * 3 + 3] = 0
      edgeVertices[i * 2 * 3 + 4] = 0
    }
  }

  lastOccupiedEdgeVertexIndex = links.length

  const diff = d - lastCycleTime
  const targetIndex = Math.max(0, Math.min(Math.round((diff / cycleDur) * Nodes.length), Nodes.length))

  if(targetIndex < updateLinksNodeIndex) { updateLinksNodeIndex = 0 } // wrap around

  for(let i=updateLinksNodeIndex; i<targetIndex; i++) {
    let node = Nodes[i]
    node.adjustFollowing()
    setFollowedBy(node)
  }

  updateLinksNodeIndex = targetIndex

  if(shouldUpdate) {
    force.force("link").links(links)
    force.alphaTarget(0.1).restart()
    quadtree = d3quadtree().extent([[-1, -1], [width, height]])
    minFollowedByLength = Infinity
    maxFollowedByLength = 0
  }

  for(let i=0; i < Nodes.length; i++) {
    let node = Nodes[i]
    nodePositions[i * 2] = node.x - width / 2
    nodePositions[i * 2 + 1] = -(node.y - height / 2)
    nodeSizesColors[i * 2] = nodeSizeScale(node.followedBy.length)
    if(node.belief === "conservative") { // red
      nodeSizesColors[i * 2 + 1] = decodeFloat(254, 25, 83, 254)
    } else if(node.belief === "liberal") { // blue
      nodeSizesColors[i * 2 + 1] = decodeFloat(0, 190, 254, 254)
    } else { // purple
      nodeSizesColors[i * 2 + 1] = decodeFloat(254, 254, 254, 254)
    }

    if(activeNode && node.id === activeNode.id) {
      halo.style.transform = `translate3d(${canvasLeft + node.x - 6}px, ${canvasTop + node.y - 6}px, 0)`
    }

    if(shouldUpdate) {
      quadtree.add([node.x, node.y, node])
      // initNetworkConnectivity(Nodes)
      updateMinMaxFollowedBy(node.followedBy.length)
    }
  }

  nodeSizeScale.domain([minFollowedByLength, maxFollowedByLength])
  edgeVerticesBuffer.needsUpdate = true
  edgeColorsStartTimesBuffer.needsUpdate = true
  nodePositionBuffer.needsUpdate = true
  nodeSizesColorsBuffer.needsUpdate = true
  renderer.render(scene, camera)

  rafID = requestAnimationFrame(loop)
}

const initialize = () => {
  canvasTop = top
  canvasLeft = left

  edgeGeometry = new THREE.BufferGeometry()
  nodeGeometry = new THREE.BufferGeometry()

  nodeMaterial = new THREE.ShaderMaterial({
    vertexShader: document.getElementById("node-vertexshader").textContent,
    fragmentShader: document.getElementById("node-fragmentshader").textContent,
    transparent: true
  })

  edgeMaterial = new THREE.ShaderMaterial({
    uniforms: {
      uTime: { value: 0.0 },
      peakTime: { value: peakTime },
      totalTime: { value: totalTime },
      defaultOpacity: { value: 0.08 },
      color: {
        type: 'c',
        value: new THREE.Color(0xABABBF)
      }
    },
    vertexShader: document.getElementById("edge-vertexshader").textContent,
    fragmentShader: document.getElementById("edge-fragmentshader").textContent,
    transparent: true
  })

  nodePositions = new Float32Array(Nodes.length * 2)
  nodeSizesColors = new Float32Array(Nodes.length * 2)
  edgeVertices = new Float32Array(edgeData.length * 2 * 6)
  edgeColorsStartTimes = new Float32Array(edgeData.length * 2 * 4)

  nodePositionBuffer = new THREE.BufferAttribute(nodePositions, 2)
  nodeGeometry.addAttribute("position", nodePositionBuffer)
  nodeSizesColorsBuffer = new THREE.BufferAttribute(nodeSizesColors, 2)
  nodeGeometry.addAttribute("sizeColor", nodeSizesColorsBuffer)

  edgeVerticesBuffer = new THREE.BufferAttribute(edgeVertices, 3)
  edgeColorsStartTimesBuffer = new THREE.BufferAttribute(edgeColorsStartTimes, 2)

  edgeGeometry.addAttribute("position", edgeVerticesBuffer)
  edgeGeometry.addAttribute("colorTime", edgeColorsStartTimesBuffer)

  lineSegments = new THREE.LineSegments(edgeGeometry, edgeMaterial)
  scene.add(lineSegments)
  points = new THREE.Points(nodeGeometry, nodeMaterial)
  scene.add(points)

  force.nodes(Nodes)
    .force("link", forceLink().id(d => d.id))
    .force("charge", forceManyBody().strength(-10).distanceMax(200))
    .force("center", forceCenter(width / 2, height / 2))
    .force("vertical", forceY().strength(0.01))
    .force("horizontal", forceX().strength(0.01))
    .velocityDecay(0.6)

  links = edgeData

  links.forEach(l => {
    const source = Nodes.find(n => n.id === +l.source)
    const target = Nodes.find(n => n.id === +l.target)
    target.following = target.following.concat(source)
  })

  Nodes.forEach(n => n.init())
  // initFlot(Nodes[20]);
}

const play = () => {
  cycleSID = setInterval(() => {
    lastCycleTime = Date.now() - start
    cycle()
  }, cycleDur)

  rafID = requestAnimationFrame(loop)
}

const pause = () => {
  window.clearInterval(cycleSID)
  window.cancelAnimationFrame(rafID)
}

const stop = () => {
  pause()

  mediator.publish("stopped")
}

const revealHalo = () => {
  halo.classList.add("active")
}

const removeHalo = () => {
  halo.classList.remove("active")
}

document.addEventListener("mousemove", e => {
  e.preventDefault()
  popoverElement.style.left = e.pageX + 'px'
  popoverElement.style.top = e.pageY + 'px'

  match = quadtree.find(e.pageX - canvasLeft, e.pageY - canvasTop, 3)
  if(match) {
    const diversity = match[2].diversity
    popoverElement.style.display = 'block'
    popoverID.innerHTML = match[2].id
    popoverDiversity.innerHTML = 'diversity: ' + diversity.toFixed(2)
    if(diversity > match[2].desiredDiversity) {
      popoverElement.setAttribute("data-satisfied", true)
    } else {
      popoverElement.setAttribute("data-satisfied", false)
    }
  } else {
    popoverElement.style.display = 'none'
  }
})

document.addEventListener("click", e => {
  e.preventDefault()
  if(match) {
    if(activeNode && activeNode.id === match[2].id) {
      activeNode = null
      removeHalo()
    } else {
      activeNode = match[2]
      initFlot(activeNode)
      revealHalo()
    }
  }
})

mediator.subscribe("converged", () => {
  stop()
  renderer.domElement.classList.add("flash")
  setTimeout(() => {
    renderer.domElement.classList.remove("flash")
  }, 800)
})

mediator.subscribe("play", play)

mediator.subscribe("pause", pause)

mediator.subscribe("selectDataset", dataset => {
  window.clearInterval(cycleSID)
  window.cancelAnimationFrame(rafID)

  Promise.all([dataset.nodes, dataset.edges].map(getData))
    .then(data => {
      scene.remove(lineSegments)
      scene.remove(points)
      if(lineSegments) {
        edgeGeometry.dispose()
        nodeGeometry.dispose()
        nodeMaterial.dispose()
        edgeMaterial.dispose()
      }

      nodeData = shuffle(data[0])

      nodeData.splice(roundDown(nodeData.length, 3)) // nodes length must be multiple of 3

      edgeData = shuffle(data[1].filter(d =>
        [+d.source, +d.target].every(id => nodeData.find(n => n.node_id === id))))

      edgeData.splice(roundDown(edgeData.length, 3))

      lastCycleTime = 0
      start = Date.now()
      initializeNodes(nodeData, dataset.beliefs)
      initialize()

      mediator.publish("data-initialized")
    })
})
<|endoftext|>


<commit_before>'use strict';

(function () {

  var _ = require('underscore');
  var request = require('superagent');
  var cheerio = require('cheerio');
  var parseDomain = require('parse-domain');
  var wordCount = require('word-count');
  var utils = require('./utils');

  var error = utils.error;

  var MATCHES_NO_PROTOCOL = /^\/\//;
  var MATCHES_RELATIVE_URL = /\.?\//;

  var baseDomain, urls, json, depth, format, callback;

  function getData (html) {
    var $ = cheerio.load(html);

    var links = _.chain($('a[href]').toArray().map(function (el) {
      var element = $(el);

      return element.attr('href');
    }))
    .groupBy(_.identity)
    .map(function (values, key) {
      return {
        url: key,
        references: values.length
      };
    })
    .sortBy('url')
    .groupBy(function (link) {
      if (link.url.indexOf('#') === 0) {
        return 'same-page';
      }

      if (link.url.indexOf('tel:') === 0) {
        return 'phone';
      }

      if (link.url.indexOf('mailto:') === 0) {
        return 'email';
      }

      var linkDomain = parseDomain(link.url.replace(MATCHES_NO_PROTOCOL, ''));

      if (!linkDomain) {
        var relativeUrl = MATCHES_RELATIVE_URL.exec(link.url);
        return relativeUrl && relativeUrl.index === 0 ? 'internal' : 'unknown';
      }

      return linkDomain.domain === baseDomain.domain &&
        linkDomain.tld === baseDomain.tld ? 'internal' : 'external';
    })
    .value();

    _.each(links.internal, function (link) {
      urls.push(link.url);
    });

    return {
      title: $('title').text() || null,
      wordCount: wordCount($('body').text()),
      charset: $('meta[charset]').attr('charset') || null,
      meta: _.object($('meta[name],meta[property]').toArray().map(function (el) {
        var element = $(el);

        return [element.attr('name') || element.attr('property'), element.attr('content')];
      })),
      links: {
        internal: links.internal || [],
        external: links.external || [],
        'same-page': links['same-page'] || [],
        email: links.email || [],
        phone: links.phone || [],
        unknown: links.unknown || []
      }
    };
  }

  function continueCrawl (url, currentDepth) {
    if (typeof currentDepth === 'undefined') {
      currentDepth = 0;
    }

    request
    .get(url)
    .accept('text/html')
    .send()
    .end(function (err, res) {
      if (err) {
        error(err);
      } else {
        if (res.type === 'text/html' && res.text && !(url in json)) {
          json[url] = getData(res.text);
        }

        if (urls.length && currentDepth < depth) {
          while (urls.length) {
            continueCrawl(urls.shift(), currentDepth + 1);
          }
        } else if (typeof process === 'object') {
          process.stdout.write(JSON.stringify(json, null, 2) + '\n'); // eslint-disable-line no-undef
        } if (typeof callback === 'function') {
          callback(JSON.stringify(json, null, 2) + '\n');
        }
      }
    });
  }

  function crawl (options, inputCallback) {
    if (!options.url) {
      error('No url specified');
    }

    baseDomain = parseDomain(options.url);

    if (!baseDomain) {
      error('Invalid domain');
    }

    urls = [options.url];
    json = {};
    depth = options.depth;
    format = options.format;

    callback = inputCallback;

    continueCrawl(urls.shift());

  }

  module.exports = {
    crawl: crawl
  };

})();
<commit_msg>Missing else<commit_after>'use strict';

(function () {

  var _ = require('underscore');
  var request = require('superagent');
  var cheerio = require('cheerio');
  var parseDomain = require('parse-domain');
  var wordCount = require('word-count');
  var utils = require('./utils');

  var error = utils.error;

  var MATCHES_NO_PROTOCOL = /^\/\//;
  var MATCHES_RELATIVE_URL = /\.?\//;

  var baseDomain, urls, json, depth, format, callback;

  function getData (html) {
    var $ = cheerio.load(html);

    var links = _.chain($('a[href]').toArray().map(function (el) {
      var element = $(el);

      return element.attr('href');
    }))
    .groupBy(_.identity)
    .map(function (values, key) {
      return {
        url: key,
        references: values.length
      };
    })
    .sortBy('url')
    .groupBy(function (link) {
      if (link.url.indexOf('#') === 0) {
        return 'same-page';
      }

      if (link.url.indexOf('tel:') === 0) {
        return 'phone';
      }

      if (link.url.indexOf('mailto:') === 0) {
        return 'email';
      }

      var linkDomain = parseDomain(link.url.replace(MATCHES_NO_PROTOCOL, ''));

      if (!linkDomain) {
        var relativeUrl = MATCHES_RELATIVE_URL.exec(link.url);
        return relativeUrl && relativeUrl.index === 0 ? 'internal' : 'unknown';
      }

      return linkDomain.domain === baseDomain.domain &&
        linkDomain.tld === baseDomain.tld ? 'internal' : 'external';
    })
    .value();

    _.each(links.internal, function (link) {
      urls.push(link.url);
    });

    return {
      title: $('title').text() || null,
      wordCount: wordCount($('body').text()),
      charset: $('meta[charset]').attr('charset') || null,
      meta: _.object($('meta[name],meta[property]').toArray().map(function (el) {
        var element = $(el);

        return [element.attr('name') || element.attr('property'), element.attr('content')];
      })),
      links: {
        internal: links.internal || [],
        external: links.external || [],
        'same-page': links['same-page'] || [],
        email: links.email || [],
        phone: links.phone || [],
        unknown: links.unknown || []
      }
    };
  }

  function continueCrawl (url, currentDepth) {
    if (typeof currentDepth === 'undefined') {
      currentDepth = 0;
    }

    request
    .get(url)
    .accept('text/html')
    .send()
    .end(function (err, res) {
      if (err) {
        error(err);
      } else {
        if (res.type === 'text/html' && res.text && !(url in json)) {
          json[url] = getData(res.text);
        }

        if (urls.length && currentDepth < depth) {
          while (urls.length) {
            continueCrawl(urls.shift(), currentDepth + 1);
          }
        } else if (typeof process === 'object') {
          process.stdout.write(JSON.stringify(json, null, 2) + '\n'); // eslint-disable-line no-undef
        } else if (typeof callback === 'function') {
          callback(JSON.stringify(json, null, 2) + '\n');
        }
      }
    });
  }

  function crawl (options, inputCallback) {
    if (!options.url) {
      error('No url specified');
    }

    baseDomain = parseDomain(options.url);

    if (!baseDomain) {
      error('Invalid domain');
    }

    urls = [options.url];
    json = {};
    depth = options.depth;
    format = options.format;

    callback = inputCallback;

    continueCrawl(urls.shift());

  }

  module.exports = {
    crawl: crawl
  };

})();
<|endoftext|>