# modules/png_support.js

    /* global jsPDF, Deflater, PNG */
    /**
     * @license
     *
     * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
     *
     * Permission is hereby granted, free of charge, to any person obtaining
     * a copy of this software and associated documentation files (the
     * "Software"), to deal in the Software without restriction, including
     * without limitation the rights to use, copy, modify, merge, publish,
     * distribute, sublicense, and/or sell copies of the Software, and to
     * permit persons to whom the Software is furnished to do so, subject to
     * the following conditions:
     *
     * The above copyright notice and this permission notice shall be
     * included in all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     * ====================================================================
     */

    /**
    * jsPDF PNG PlugIn
    * @name png_support
    * @module
    */
    (function (jsPDFAPI, global) {
      'use strict'

      /*
       * @see http://www.w3.org/TR/PNG-Chunks.html
       *
       Color    Allowed      Interpretation
       Type     Bit Depths

         0       1,2,4,8,16  Each pixel is a grayscale sample.

         2       8,16        Each pixel is an R,G,B triple.

         3       1,2,4,8     Each pixel is a palette index;
                             a PLTE chunk must appear.

         4       8,16        Each pixel is a grayscale sample,
                             followed by an alpha sample.

         6       8,16        Each pixel is an R,G,B triple,
                             followed by an alpha sample.
      */

      /*
       * PNG filter method types
       *
       * @see http://www.w3.org/TR/PNG-Filters.html
       * @see http://www.libpng.org/pub/png/book/chapter09.html
       *
       * This is what the value 'Predictor' in decode params relates to
       *
       * 15 is "optimal prediction", which means the prediction algorithm can change from line to line.
       * In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte.
       *
         0       None
         1       Sub
         2       Up
         3       Average
         4       Paeth
       */

      var doesNotHavePngJS = function () {
        return typeof global.PNG !== 'function' || typeof global.FlateStream !== 'function';
      };

      var canCompress = function (value) {
        return value !== jsPDFAPI.image_compression.NONE && hasCompressionJS();
      };

      var hasCompressionJS = function () {
        return (typeof Deflater === 'function');
      };

      var compressBytes = function (bytes, lineLength, colorsPerPixel, compression) {
        var level = 5;
        var filter_method = filterUp;

        switch (compression) {
          case jsPDFAPI.image_compression.FAST:
            level = 3;
            filter_method = filterSub;
            break;

          case jsPDFAPI.image_compression.MEDIUM:
            level = 6;
            filter_method = filterAverage;
            break;

          case jsPDFAPI.image_compression.SLOW:
            level = 9;
            filter_method = filterPaeth;
            break;
        }

        bytes = applyPngFilterMethod(bytes, lineLength, colorsPerPixel, filter_method);

        var header = new Uint8Array(createZlibHeader(level));
        var checksum = jsPDF.API.adler32cs.fromBuffer(bytes.buffer);

        var deflate = new Deflater(level);
        var a = deflate.append(bytes);
        var cBytes = deflate.flush();

        var len = header.length + a.length + cBytes.length;

        var cmpd = new Uint8Array(len + 4);
        cmpd.set(header);
        cmpd.set(a, header.length);
        cmpd.set(cBytes, header.length + a.length);

        cmpd[len++] = (checksum >>> 24) & 0xff;
        cmpd[len++] = (checksum >>> 16) & 0xff;
        cmpd[len++] = (checksum >>> 8) & 0xff;
        cmpd[len++] = checksum & 0xff;

        return jsPDFAPI.__addimage__.arrayBufferToBinaryString(cmpd);
      };

      var createZlibHeader = function (level) {
        /*
         * @see http://www.ietf.org/rfc/rfc1950.txt for zlib header
         */
        var hdr = 30720;
        var flevel = Math.min(3, ((level - 1) & 0xff) >> 1);

        hdr |= (flevel << 6);
        hdr |= 0;//FDICT
        hdr += 31 - (hdr % 31);

        return [120, (hdr & 0xff) & 0xff];
      };

      var applyPngFilterMethod = function (bytes, lineLength, colorsPerPixel, filter_method) {
        var lines = bytes.length / lineLength,
          result = new Uint8Array(bytes.length + lines),
          filter_methods = getFilterMethods(),
          line, prevLine, offset;

        for (var i = 0; i < lines; i += 1) {
          offset = i * lineLength;
          line = bytes.subarray(offset, offset + lineLength);

          if (filter_method) {
            result.set(filter_method(line, colorsPerPixel, prevLine), offset + i);
          } else {
            var len = filter_methods.length,
              results = [];

            for (var j; j < len; j += 1) {
              results[j] = filter_methods[j](line, colorsPerPixel, prevLine);
            }

            var ind = getIndexOfSmallestSum(results.concat());

            result.set(results[ind], offset + i);
          }

          prevLine = line;
        }

        return result;
      };

      var filterNone = function (line) {
        /*var result = new Uint8Array(line.length + 1);
        result[0] = 0;
        result.set(line, 1);*/

        var result = Array.apply([], line);
        result.unshift(0);

        return result;
      };

      var filterSub = function (line, colorsPerPixel) {
        var result = [],
          len = line.length,
          left;

        result[0] = 1;

        for (var i = 0; i < len; i += 1) {
          left = line[i - colorsPerPixel] || 0;
          result[i + 1] = (line[i] - left + 0x0100) & 0xff;
        }

        return result;
      };

      var filterUp = function (line, colorsPerPixel, prevLine) {
        var result = [],
          len = line.length,
          up;

        result[0] = 2;

        for (var i = 0; i < len; i += 1) {
          up = prevLine && prevLine[i] || 0;
          result[i + 1] = (line[i] - up + 0x0100) & 0xff;
        }

        return result;
      };

      var filterAverage = function (line, colorsPerPixel, prevLine) {
        var result = [],
          len = line.length,
          left,
          up;

        result[0] = 3;

        for (var i = 0; i < len; i += 1) {
          left = line[i - colorsPerPixel] || 0;
          up = prevLine && prevLine[i] || 0;
          result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff;
        }

        return result;
      };

      var filterPaeth = function (line, colorsPerPixel, prevLine) {
        var result = [],
          len = line.length,
          left,
          up,
          upLeft,
          paeth;

        result[0] = 4;

        for (var i = 0; i < len; i += 1) {
          left = line[i - colorsPerPixel] || 0;
          up = prevLine && prevLine[i] || 0;
          upLeft = prevLine && prevLine[i - colorsPerPixel] || 0;
          paeth = paethPredictor(left, up, upLeft);
          result[i + 1] = (line[i] - paeth + 0x0100) & 0xff;
        }

        return result;
      };

      var paethPredictor = function (left, up, upLeft) {
        if (left === up && up === upLeft) {
          return left;
        }
        var pLeft = Math.abs(up - upLeft),
        pUp = Math.abs(left - upLeft),
        pUpLeft = Math.abs(left + up - upLeft - upLeft);
        return (pLeft <= pUp && pLeft <= pUpLeft) ? left : (pUp <= pUpLeft) ? up : upLeft;
      };

      var getFilterMethods = function () {
        return [filterNone, filterSub, filterUp, filterAverage, filterPaeth];
      };

      var getIndexOfSmallestSum = function (arrays) {
        var sum = [];

        for (var i = 0; i < arrays.length; i += 1) {
          sum.push(arrays[i].reduce(function (pv, cv) {return pv + Math.abs(cv);}, 0));
        }

        return sum.indexOf(Math.min.apply(null, sum));
      };

      var getPredictorFromCompression = function (compression) {
        var predictor;
        switch (compression) {
          case jsPDFAPI.image_compression.FAST:
            predictor = 11;
            break;

          case jsPDFAPI.image_compression.MEDIUM:
            predictor = 13;
            break;

          case jsPDFAPI.image_compression.SLOW:
            predictor = 14;
            break;

          default:
            predictor = 12;
            break;
        }
        return predictor;
      };

      /**
      * @name processPNG
      * @function
      * @ignore
      */
      jsPDFAPI.processPNG = function (imageData, index, alias, compression) {
        'use strict';

        var colorSpace,
          filter = this.decode.FLATE_DECODE,
          bitsPerComponent,
          image, decodeParameters = '', trns,
          colors, pal, smask,
          pixels, len, alphaData, imgData, hasColors, pixel,
          i, n;

        if (this.__addimage__.isArrayBuffer(imageData))
          imageData = new Uint8Array(imageData);

        if (this.__addimage__.isArrayBufferView(imageData)) {

          if (doesNotHavePngJS()) {
            throw new Error("PNG support requires png.js and zlib.js");
          }

          image = new PNG(imageData);
          imageData = image.imgData;
          bitsPerComponent = image.bits;
          colorSpace = image.colorSpace;
          colors = image.colors;

          /*
           * colorType 6 - Each pixel is an R,G,B triple, followed by an alpha sample.
           *
           * colorType 4 - Each pixel is a grayscale sample, followed by an alpha sample.
           *
           * Extract alpha to create two separate images, using the alpha as a sMask
           */
          if ([4, 6].indexOf(image.colorType) !== -1) {

            /*
             * processes 8 bit RGBA and grayscale + alpha images
             */
            if (image.bits === 8) {

              pixels = image.pixelBitlength == 32 ? new Uint32Array(image.decodePixels().buffer) : image.pixelBitlength == 16 ? new Uint16Array(image.decodePixels().buffer) : new Uint8Array(image.decodePixels().buffer);
              len = pixels.length;
              imgData = new Uint8Array(len * image.colors);
              alphaData = new Uint8Array(len);
              var pDiff = image.pixelBitlength - image.bits;
              i = 0;
              n = 0;
              var pbl;

              for (; i < len; i++) {
                pixel = pixels[i];
                pbl = 0;

                while (pbl < pDiff) {

                  imgData[n++] = (pixel >>> pbl) & 0xff;
                  pbl = pbl + image.bits;
                }

                alphaData[i] = (pixel >>> pbl) & 0xff;
              }
            }

            /*
             * processes 16 bit RGBA and grayscale + alpha images
             */
            if (image.bits === 16) {
              pixels = new Uint32Array(image.decodePixels().buffer);
              len = pixels.length;
              imgData = new Uint8Array((len * (32 / image.pixelBitlength)) * image.colors);
              alphaData = new Uint8Array(len * (32 / image.pixelBitlength));
              hasColors = image.colors > 1;
              i = 0;
              n = 0;
              var a = 0;

              while (i < len) {
                pixel = pixels[i++];

                imgData[n++] = (pixel >>> 0) & 0xFF;

                if (hasColors) {
                  imgData[n++] = (pixel >>> 16) & 0xFF;

                  pixel = pixels[i++];
                  imgData[n++] = (pixel >>> 0) & 0xFF;
                }

                alphaData[a++] = (pixel >>> 16) & 0xFF;
              }
              bitsPerComponent = 8;
            }

            if (canCompress(compression)) {
              imageData = compressBytes(imgData, image.width * image.colors, image.colors, compression);
              smask = compressBytes(alphaData, image.width, 1, compression);
            } else {
              imageData = imgData;
              smask = alphaData;
              filter = undefined;
            }
          }

          /*
           * Indexed png. Each pixel is a palette index.
           */
          if (image.colorType === 3) {

            colorSpace = this.color_spaces.INDEXED;
            pal = image.palette;

            if (image.transparency.indexed) {

              var trans = image.transparency.indexed;
              var total = 0;
              i = 0;
              len = trans.length;

              for (; i < len; ++i) {
                total += trans[i];
              }

              total = total / 255;

              /*
               * a single color is specified as 100% transparent (0),
               * so we set trns to use a /Mask with that index
               */
              if (total === len - 1 && trans.indexOf(0) !== -1) {
                trns = [trans.indexOf(0)];

                /*
                 * there's more than one colour within the palette that specifies
                 * a transparency value less than 255, so we unroll the pixels to create an image sMask
                 */
              } else if (total !== len) {
                pixels = image.decodePixels();
                alphaData = new Uint8Array(pixels.length);
                i = 0;
                len = pixels.length;

                for (; i < len; i++) {
                  alphaData[i] = trans[pixels[i]];
                }

                smask = compressBytes(alphaData, image.width, 1);
              }
            }
          }

          var predictor = getPredictorFromCompression(compression);

          if (filter === this.decode.FLATE_DECODE) {
            decodeParameters = '/Predictor ' + predictor + ' ';
          }
          decodeParameters += '/Colors ' + colors + ' /BitsPerComponent ' + bitsPerComponent + ' /Columns ' + image.width;

          if (this.__addimage__.isArrayBuffer(imageData) || this.__addimage__.isArrayBufferView(imageData)) {
            imageData = this.__addimage__.arrayBufferToBinaryString(imageData);
          }

          if (smask && this.__addimage__.isArrayBuffer(smask) || this.__addimage__.isArrayBufferView(smask)) {
            smask = this.__addimage__.arrayBufferToBinaryString(smask);
          }

          return { alias: alias, data: imageData, index: index, filter: filter, decodeParameters: decodeParameters, transparency: trns, palette: pal, sMask: smask, predictor: predictor, width: image.width, height: image.height, bitsPerComponent: bitsPerComponent, colorSpace: colorSpace };
        }
      }

    })(jsPDF.API, typeof self !== "undefined" && self || typeof window !== "undefined" && window || typeof global !== "undefined" && global || Function('return typeof this === "object" && this.content')() || Function('return this')());
    // `self` is undefined in Firefox for Android content script context
    // while `this` is nsIContentFrameMessageManager
    // with an attribute `content` that corresponds to the window