text: /**
   * @licstart The following is the entire license notice for the
   * Javascript code in this page
   *
   * Copyright 2021 Mozilla Foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * @licend The above is the entire license notice for the
   * Javascript code in this page
   */
  "use strict";
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.PDFImage = void 0;
  
  var _util = require("../shared/util.js");
  
  var _primitives = require("./primitives.js");
  
  var _colorspace = require("./colorspace.js");
  
  var _decode_stream = require("./decode_stream.js");
  
  var _jpeg_stream = require("./jpeg_stream.js");
  
  var _jpx = require("./jpx.js");
  
  function decodeAndClamp(value, addend, coefficient, max) {
    value = addend + value * coefficient;
  
    if (value < 0) {
      value = 0;
    } else if (value > max) {
      value = max;
    }
  
    return value;
  }
  
  function resizeImageMask(src, bpc, w1, h1, w2, h2) {
    const length = w2 * h2;
    let dest;
  
    if (bpc <= 8) {
      dest = new Uint8Array(length);
    } else if (bpc <= 16) {
      dest = new Uint16Array(length);
    } else {
      dest = new Uint32Array(length);
    }
  
    const xRatio = w1 / w2;
    const yRatio = h1 / h2;
    let i,
        j,
        py,
        newIndex = 0,
        oldIndex;
    const xScaled = new Uint16Array(w2);
    const w1Scanline = w1;
  
    for (i = 0; i < w2; i++) {
      xScaled[i] = Math.floor(i * xRatio);
    }
  
    for (i = 0; i < h2; i++) {
      py = Math.floor(i * yRatio) * w1Scanline;
  
      for (j = 0; j < w2; j++) {
        oldIndex = py + xScaled[j];
        dest[newIndex++] = src[oldIndex];
      }
    }
  
    return dest;
  }
  
  class PDFImage {
    constructor({
      xref,
      res,
      image,
      isInline = false,
      smask = null,
      mask = null,
      isMask = false,
      pdfFunctionFactory,
      localColorSpaceCache
    }) {
      this.image = image;
      const dict = image.dict;
      const filter = dict.get("Filter");
  
      if ((0, _primitives.isName)(filter)) {
        switch (filter.name) {
          case "JPXDecode":
            const jpxImage = new _jpx.JpxImage();
            jpxImage.parseImageProperties(image.stream);
            image.stream.reset();
            image.width = jpxImage.width;
            image.height = jpxImage.height;
            image.bitsPerComponent = jpxImage.bitsPerComponent;
            image.numComps = jpxImage.componentsCount;
            break;
  
          case "JBIG2Decode":
            image.bitsPerComponent = 1;
            image.numComps = 1;
            break;
        }
      }
  
      let width = dict.get("Width", "W");
      let height = dict.get("Height", "H");
  
      if (Number.isInteger(image.width) && image.width > 0 && Number.isInteger(image.height) && image.height > 0 && (image.width !== width || image.height !== height)) {
        (0, _util.warn)("PDFImage - using the Width/Height of the image data, " + "rather than the image dictionary.");
        width = image.width;
        height = image.height;
      }
  
      if (width < 1 || height < 1) {
        throw new _util.FormatError(`Invalid image width: ${width} or height: ${height}`);
      }
  
      this.width = width;
      this.height = height;
      this.interpolate = dict.get("Interpolate", "I");
      this.imageMask = dict.get("ImageMask", "IM") || false;
      this.matte = dict.get("Matte") || false;
      let bitsPerComponent = image.bitsPerComponent;
  
      if (!bitsPerComponent) {
        bitsPerComponent = dict.get("BitsPerComponent", "BPC");
  
        if (!bitsPerComponent) {
          if (this.imageMask) {
            bitsPerComponent = 1;
          } else {
            throw new _util.FormatError(`Bits per component missing in image: ${this.imageMask}`);
          }
        }
      }
  
      this.bpc = bitsPerComponent;
  
      if (!this.imageMask) {
        let colorSpace = dict.getRaw("ColorSpace") || dict.getRaw("CS");
  
        if (!colorSpace) {
          (0, _util.info)("JPX images (which do not require color spaces)");
  
          switch (image.numComps) {
            case 1:
              colorSpace = _primitives.Name.get("DeviceGray");
              break;
  
            case 3:
              colorSpace = _primitives.Name.get("DeviceRGB");
              break;
  
            case 4:
              colorSpace = _primitives.Name.get("DeviceCMYK");
              break;
  
            default:
              throw new Error(`JPX images with ${image.numComps} ` + "color components not supported.");
          }
        }
  
        this.colorSpace = _colorspace.ColorSpace.parse({
          cs: colorSpace,
          xref,
          resources: isInline ? res : null,
          pdfFunctionFactory,
          localColorSpaceCache
        });
        this.numComps = this.colorSpace.numComps;
      }
  
      this.decode = dict.getArray("Decode", "D");
      this.needsDecode = false;
  
      if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent) || isMask && !_colorspace.ColorSpace.isDefaultDecode(this.decode, 1))) {
        this.needsDecode = true;
        const max = (1 << bitsPerComponent) - 1;
        this.decodeCoefficients = [];
        this.decodeAddends = [];
        const isIndexed = this.colorSpace && this.colorSpace.name === "Indexed";
  
        for (let i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
          const dmin = this.decode[i];
          const dmax = this.decode[i + 1];
          this.decodeCoefficients[j] = isIndexed ? (dmax - dmin) / max : dmax - dmin;
          this.decodeAddends[j] = isIndexed ? dmin : max * dmin;
        }
      }
  
      if (smask) {
        this.smask = new PDFImage({
          xref,
          res,
          image: smask,
          isInline,
          pdfFunctionFactory,
          localColorSpaceCache
        });
      } else if (mask) {
        if ((0, _primitives.isStream)(mask)) {
          const maskDict = mask.dict,
                imageMask = maskDict.get("ImageMask", "IM");
  
          if (!imageMask) {
            (0, _util.warn)("Ignoring /Mask in image without /ImageMask.");
          } else {
            this.mask = new PDFImage({
              xref,
              res,
              image: mask,
              isInline,
              isMask: true,
              pdfFunctionFactory,
              localColorSpaceCache
            });
          }
        } else {
          this.mask = mask;
        }
      }
    }
  
    static async buildImage({
      xref,
      res,
      image,
      isInline = false,
      pdfFunctionFactory,
      localColorSpaceCache
    }) {
      const imageData = image;
      let smaskData = null;
      let maskData = null;
      const smask = image.dict.get("SMask");
      const mask = image.dict.get("Mask");
  
      if (smask) {
        smaskData = smask;
      } else if (mask) {
        if ((0, _primitives.isStream)(mask) || Array.isArray(mask)) {
          maskData = mask;
        } else {
          (0, _util.warn)("Unsupported mask format.");
        }
      }
  
      return new PDFImage({
        xref,
        res,
        image: imageData,
        isInline,
        smask: smaskData,
        mask: maskData,
        pdfFunctionFactory,
        localColorSpaceCache
      });
    }
  
    static createMask({
      imgArray,
      width,
      height,
      imageIsFromDecodeStream,
      inverseDecode,
      interpolate
    }) {
      const computedLength = (width + 7 >> 3) * height;
      const actualLength = imgArray.byteLength;
      const haveFullData = computedLength === actualLength;
      let data, i;
  
      if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
        data = imgArray;
      } else if (!inverseDecode) {
        data = new Uint8ClampedArray(actualLength);
        data.set(imgArray);
      } else {
        data = new Uint8ClampedArray(computedLength);
        data.set(imgArray);
  
        for (i = actualLength; i < computedLength; i++) {
          data[i] = 0xff;
        }
      }
  
      if (inverseDecode) {
        for (i = 0; i < actualLength; i++) {
          data[i] ^= 0xff;
        }
      }
  
      return {
        data,
        width,
        height,
        interpolate
      };
    }
  
    get drawWidth() {
      return Math.max(this.width, this.smask && this.smask.width || 0, this.mask && this.mask.width || 0);
    }
  
    get drawHeight() {
      return Math.max(this.height, this.smask && this.smask.height || 0, this.mask && this.mask.height || 0);
    }
  
    decodeBuffer(buffer) {
      const bpc = this.bpc;
      const numComps = this.numComps;
      const decodeAddends = this.decodeAddends;
      const decodeCoefficients = this.decodeCoefficients;
      const max = (1 << bpc) - 1;
      let i, ii;
  
      if (bpc === 1) {
        for (i = 0, ii = buffer.length; i < ii; i++) {
          buffer[i] = +!buffer[i];
        }
  
        return;
      }
  
      let index = 0;
  
      for (i = 0, ii = this.width * this.height; i < ii; i++) {
        for (let j = 0; j < numComps; j++) {
          buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], decodeCoefficients[j], max);
          index++;
        }
      }
    }
  
    getComponents(buffer) {
      const bpc = this.bpc;
  
      if (bpc === 8) {
        return buffer;
      }
  
      const width = this.width;
      const height = this.height;
      const numComps = this.numComps;
      const length = width * height * numComps;
      let bufferPos = 0;
      let output;
  
      if (bpc <= 8) {
        output = new Uint8Array(length);
      } else if (bpc <= 16) {
        output = new Uint16Array(length);
      } else {
        output = new Uint32Array(length);
      }
  
      const rowComps = width * numComps;
      const max = (1 << bpc) - 1;
      let i = 0,
          ii,
          buf;
  
      if (bpc === 1) {
        let mask, loop1End, loop2End;
  
        for (let j = 0; j < height; j++) {
          loop1End = i + (rowComps & ~7);
          loop2End = i + rowComps;
  
          while (i < loop1End) {
            buf = buffer[bufferPos++];
            output[i] = buf >> 7 & 1;
            output[i + 1] = buf >> 6 & 1;
            output[i + 2] = buf >> 5 & 1;
            output[i + 3] = buf >> 4 & 1;
            output[i + 4] = buf >> 3 & 1;
            output[i + 5] = buf >> 2 & 1;
            output[i + 6] = buf >> 1 & 1;
            output[i + 7] = buf & 1;
            i += 8;
          }
  
          if (i < loop2End) {
            buf = buffer[bufferPos++];
            mask = 128;
  
            while (i < loop2End) {
              output[i++] = +!!(buf & mask);
              mask >>= 1;
            }
          }
        }
      } else {
        let bits = 0;
        buf = 0;
  
        for (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;
          }
  
          const remainingBits = bits - bpc;
          let value = buf >> remainingBits;
  
          if (value < 0) {
            value = 0;
          } else if (value > max) {
            value = max;
          }
  
          output[i] = value;
          buf &= (1 << remainingBits) - 1;
          bits = remainingBits;
        }
      }
  
      return output;
    }
  
    fillOpacity(rgbaBuf, width, height, actualHeight, image) {
      const smask = this.smask;
      const mask = this.mask;
      let alphaBuf, sw, sh, i, ii, j;
  
      if (smask) {
        sw = smask.width;
        sh = smask.height;
        alphaBuf = new Uint8ClampedArray(sw * sh);
        smask.fillGrayBuffer(alphaBuf);
  
        if (sw !== width || sh !== height) {
          alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
        }
      } else if (mask) {
        if (mask instanceof PDFImage) {
          sw = mask.width;
          sh = mask.height;
          alphaBuf = new Uint8ClampedArray(sw * sh);
          mask.numComps = 1;
          mask.fillGrayBuffer(alphaBuf);
  
          for (i = 0, ii = sw * sh; i < ii; ++i) {
            alphaBuf[i] = 255 - alphaBuf[i];
          }
  
          if (sw !== width || sh !== height) {
            alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height);
          }
        } else if (Array.isArray(mask)) {
          alphaBuf = new Uint8ClampedArray(width * height);
          const numComps = this.numComps;
  
          for (i = 0, ii = width * height; i < ii; ++i) {
            let opacity = 0;
            const imageOffset = i * numComps;
  
            for (j = 0; j < numComps; ++j) {
              const color = image[imageOffset + j];
              const maskOffset = j * 2;
  
              if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
                opacity = 255;
                break;
              }
            }
  
            alphaBuf[i] = opacity;
          }
        } else {
          throw new _util.FormatError("Unknown mask format.");
        }
      }
  
      if (alphaBuf) {
        for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
          rgbaBuf[j] = alphaBuf[i];
        }
      } else {
        for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
          rgbaBuf[j] = 255;
        }
      }
    }
  
    undoPreblend(buffer, width, height) {
      const matte = this.smask && this.smask.matte;
  
      if (!matte) {
        return;
      }
  
      const matteRgb = this.colorSpace.getRgb(matte, 0);
      const matteR = matteRgb[0];
      const matteG = matteRgb[1];
      const matteB = matteRgb[2];
      const length = width * height * 4;
  
      for (let i = 0; i < length; i += 4) {
        const alpha = buffer[i + 3];
  
        if (alpha === 0) {
          buffer[i] = 255;
          buffer[i + 1] = 255;
          buffer[i + 2] = 255;
          continue;
        }
  
        const k = 255 / alpha;
        buffer[i] = (buffer[i] - matteR) * k + matteR;
        buffer[i + 1] = (buffer[i + 1] - matteG) * k + matteG;
        buffer[i + 2] = (buffer[i + 2] - matteB) * k + matteB;
      }
    }
  
    createImageData(forceRGBA = false) {
      const drawWidth = this.drawWidth;
      const drawHeight = this.drawHeight;
      const imgData = {
        width: drawWidth,
        height: drawHeight,
        interpolate: this.interpolate,
        kind: 0,
        data: null
      };
      const numComps = this.numComps;
      const originalWidth = this.width;
      const originalHeight = this.height;
      const bpc = this.bpc;
      const rowBytes = originalWidth * numComps * bpc + 7 >> 3;
      let imgArray;
  
      if (!forceRGBA) {
        let kind;
  
        if (this.colorSpace.name === "DeviceGray" && bpc === 1) {
          kind = _util.ImageKind.GRAYSCALE_1BPP;
        } else if (this.colorSpace.name === "DeviceRGB" && bpc === 8 && !this.needsDecode) {
          kind = _util.ImageKind.RGB_24BPP;
        }
  
        if (kind && !this.smask && !this.mask && drawWidth === originalWidth && drawHeight === originalHeight) {
          imgData.kind = kind;
          imgArray = this.getImageBytes(originalHeight * rowBytes);
  
          if (this.image instanceof _decode_stream.DecodeStream) {
            imgData.data = imgArray;
          } else {
            const newArray = new Uint8ClampedArray(imgArray.length);
            newArray.set(imgArray);
            imgData.data = newArray;
          }
  
          if (this.needsDecode) {
            (0, _util.assert)(kind === _util.ImageKind.GRAYSCALE_1BPP, "PDFImage.createImageData: The image must be grayscale.");
            const buffer = imgData.data;
  
            for (let i = 0, ii = buffer.length; i < ii; i++) {
              buffer[i] ^= 0xff;
            }
          }
  
          return imgData;
        }
  
        if (this.image instanceof _jpeg_stream.JpegStream && !this.smask && !this.mask) {
          let imageLength = originalHeight * rowBytes;
  
          switch (this.colorSpace.name) {
            case "DeviceGray":
              imageLength *= 3;
  
            case "DeviceRGB":
            case "DeviceCMYK":
              imgData.kind = _util.ImageKind.RGB_24BPP;
              imgData.data = this.getImageBytes(imageLength, drawWidth, drawHeight, true);
              return imgData;
          }
        }
      }
  
      imgArray = this.getImageBytes(originalHeight * rowBytes);
      const actualHeight = 0 | imgArray.length / rowBytes * drawHeight / originalHeight;
      const comps = this.getComponents(imgArray);
      let alpha01, maybeUndoPreblend;
  
      if (!forceRGBA && !this.smask && !this.mask) {
        imgData.kind = _util.ImageKind.RGB_24BPP;
        imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
        alpha01 = 0;
        maybeUndoPreblend = false;
      } else {
        imgData.kind = _util.ImageKind.RGBA_32BPP;
        imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
        alpha01 = 1;
        maybeUndoPreblend = true;
        this.fillOpacity(imgData.data, drawWidth, drawHeight, actualHeight, comps);
      }
  
      if (this.needsDecode) {
        this.decodeBuffer(comps);
      }
  
      this.colorSpace.fillRgb(imgData.data, originalWidth, originalHeight, drawWidth, drawHeight, actualHeight, bpc, comps, alpha01);
  
      if (maybeUndoPreblend) {
        this.undoPreblend(imgData.data, drawWidth, actualHeight);
      }
  
      return imgData;
    }
  
    fillGrayBuffer(buffer) {
      const numComps = this.numComps;
  
      if (numComps !== 1) {
        throw new _util.FormatError(`Reading gray scale from a color image: ${numComps}`);
      }
  
      const width = this.width;
      const height = this.height;
      const bpc = this.bpc;
      const rowBytes = width * numComps * bpc + 7 >> 3;
      const imgArray = this.getImageBytes(height * rowBytes);
      const comps = this.getComponents(imgArray);
      let i, length;
  
      if (bpc === 1) {
        length = width * height;
  
        if (this.needsDecode) {
          for (i = 0; i < length; ++i) {
            buffer[i] = comps[i] - 1 & 255;
          }
        } else {
          for (i = 0; i < length; ++i) {
            buffer[i] = -comps[i] & 255;
          }
        }
  
        return;
      }
  
      if (this.needsDecode) {
        this.decodeBuffer(comps);
      }
  
      length = width * height;
      const scale = 255 / ((1 << bpc) - 1);
  
      for (i = 0; i < length; ++i) {
        buffer[i] = scale * comps[i];
      }
    }
  
    getImageBytes(length, drawWidth, drawHeight, forceRGB = false) {
      this.image.reset();
      this.image.drawWidth = drawWidth || this.width;
      this.image.drawHeight = drawHeight || this.height;
      this.image.forceRGB = !!forceRGB;
      return this.image.getBytes(length, true);
    }
  
  }
  
  exports.PDFImage = PDFImage;