async buildPaintImageXObject()

in src/core/evaluator.js [568:849]


  async buildPaintImageXObject({
    resources,
    image,
    isInline = false,
    operatorList,
    cacheKey,
    localImageCache,
    localColorSpaceCache,
  }) {
    const { maxImageSize, ignoreErrors, isOffscreenCanvasSupported } =
      this.options;

    const { dict } = image;
    const imageRef = dict.objId;
    const w = dict.get("W", "Width");
    const h = dict.get("H", "Height");

    if (!(w && typeof w === "number") || !(h && typeof h === "number")) {
      warn("Image dimensions are missing, or not numbers.");
      return;
    }
    if (maxImageSize !== -1 && w * h > maxImageSize) {
      const msg = "Image exceeded maximum allowed size and was removed.";

      if (!ignoreErrors) {
        throw new Error(msg);
      }
      warn(msg);
      return;
    }

    let optionalContent;
    if (dict.has("OC")) {
      optionalContent = await this.parseMarkedContentProps(
        dict.get("OC"),
        resources
      );
    }

    const imageMask = dict.get("IM", "ImageMask") || false;
    let imgData, fn, args;
    if (imageMask) {
      // This depends on a tmpCanvas being filled with the
      // current fillStyle, such that processing the pixel
      // data can't be done here. Instead of creating a
      // complete PDFImage, only read the information needed
      // for later.
      imgData = await PDFImage.createMask({
        image,
        isOffscreenCanvasSupported:
          isOffscreenCanvasSupported && !this.parsingType3Font,
      });

      if (imgData.isSingleOpaquePixel) {
        // Handles special case of mainly LaTeX documents which use image
        // masks to draw lines with the current fill style.
        fn = OPS.paintSolidColorImageMask;
        args = [];
        operatorList.addImageOps(fn, args, optionalContent);

        if (cacheKey) {
          const cacheData = { fn, args, optionalContent };
          localImageCache.set(cacheKey, imageRef, cacheData);

          if (imageRef) {
            this._regionalImageCache.set(
              /* name = */ null,
              imageRef,
              cacheData
            );
          }
        }
        return;
      }

      if (this.parsingType3Font) {
        // NOTE: Compared to other image resources we don't bother caching
        // Type3-glyph image masks, since we've not come across any cases
        // where that actually helps.
        // In Type3-glyphs image masks are "always" inline resources,
        // they're usually fairly small and aren't being re-used either.
        if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
          assert(
            imgData.data instanceof Uint8Array,
            "Type3 glyph image mask must be a TypedArray."
          );
        }
        args = compileType3Glyph(imgData);

        if (args) {
          operatorList.addImageOps(OPS.constructPath, args, optionalContent);
          return;
        }
        warn("Cannot compile Type3 glyph.");

        // If compilation failed, or was disabled, fallback to using an inline
        // image mask; this case should be extremely rare.
        operatorList.addImageOps(
          OPS.paintImageMaskXObject,
          [imgData],
          optionalContent
        );
        return;
      }

      const objId = `mask_${this.idFactory.createObjId()}`;
      operatorList.addDependency(objId);

      imgData.dataLen = imgData.bitmap
        ? imgData.width * imgData.height * 4
        : imgData.data.length;
      this._sendImgData(objId, imgData);

      fn = OPS.paintImageMaskXObject;
      args = [
        {
          data: objId,
          width: imgData.width,
          height: imgData.height,
          interpolate: imgData.interpolate,
          count: 1,
        },
      ];
      operatorList.addImageOps(fn, args, optionalContent);

      if (cacheKey) {
        const cacheData = { objId, fn, args, optionalContent };
        localImageCache.set(cacheKey, imageRef, cacheData);

        if (imageRef) {
          this._regionalImageCache.set(/* name = */ null, imageRef, cacheData);
        }
      }
      return;
    }

    const SMALL_IMAGE_DIMENSIONS = 200;
    const hasMask = dict.has("SMask") || dict.has("Mask");
    // Inlining small images into the queue as RGB data
    if (isInline && w + h < SMALL_IMAGE_DIMENSIONS && !hasMask) {
      try {
        const imageObj = new PDFImage({
          xref: this.xref,
          res: resources,
          image,
          isInline,
          pdfFunctionFactory: this._pdfFunctionFactory,
          globalColorSpaceCache: this.globalColorSpaceCache,
          localColorSpaceCache,
        });
        // We force the use of RGBA_32BPP images here, because we can't handle
        // any other kind.
        imgData = await imageObj.createImageData(
          /* forceRGBA = */ true,
          /* isOffscreenCanvasSupported = */ false
        );
        operatorList.addImageOps(
          OPS.paintInlineImageXObject,
          [imgData],
          optionalContent
        );
      } catch (reason) {
        const msg = `Unable to decode inline image: "${reason}".`;

        if (!ignoreErrors) {
          throw new Error(msg);
        }
        warn(msg);
      }
      return;
    }

    // If there is no imageMask, create the PDFImage and a lot
    // of image processing can be done here.
    let objId = `img_${this.idFactory.createObjId()}`,
      cacheGlobally = false,
      globalCacheData = null;

    if (this.parsingType3Font) {
      objId = `${this.idFactory.getDocId()}_type3_${objId}`;
    } else if (cacheKey && imageRef) {
      cacheGlobally = this.globalImageCache.shouldCache(
        imageRef,
        this.pageIndex
      );

      if (cacheGlobally) {
        assert(!isInline, "Cannot cache an inline image globally.");

        objId = `${this.idFactory.getDocId()}_${objId}`;
      }
    }

    // Ensure that the dependency is added before the image is decoded.
    operatorList.addDependency(objId);

    fn = OPS.paintImageXObject;
    args = [objId, w, h];
    operatorList.addImageOps(fn, args, optionalContent, hasMask);

    if (cacheGlobally) {
      globalCacheData = {
        objId,
        fn,
        args,
        optionalContent,
        hasMask,
        byteSize: 0, // Temporary entry, to avoid `setData` returning early.
      };

      if (this.globalImageCache.hasDecodeFailed(imageRef)) {
        this.globalImageCache.setData(imageRef, globalCacheData);

        this._sendImgData(objId, /* imgData = */ null, cacheGlobally);
        return;
      }

      // For large (at least 500x500) or more complex images that we'll cache
      // globally, check if the image is still cached locally on the main-thread
      // to avoid having to re-parse the image (since that can be slow).
      if (w * h > 250000 || hasMask) {
        const localLength = await this.handler.sendWithPromise("commonobj", [
          objId,
          "CopyLocalImage",
          { imageRef },
        ]);

        if (localLength) {
          this.globalImageCache.setData(imageRef, globalCacheData);
          this.globalImageCache.addByteSize(imageRef, localLength);
          return;
        }
      }
    }

    PDFImage.buildImage({
      xref: this.xref,
      res: resources,
      image,
      isInline,
      pdfFunctionFactory: this._pdfFunctionFactory,
      globalColorSpaceCache: this.globalColorSpaceCache,
      localColorSpaceCache,
    })
      .then(async imageObj => {
        imgData = await imageObj.createImageData(
          /* forceRGBA = */ false,
          isOffscreenCanvasSupported
        );
        imgData.dataLen = imgData.bitmap
          ? imgData.width * imgData.height * 4
          : imgData.data.length;
        imgData.ref = imageRef;

        if (cacheGlobally) {
          this.globalImageCache.addByteSize(imageRef, imgData.dataLen);
        }
        return this._sendImgData(objId, imgData, cacheGlobally);
      })
      .catch(reason => {
        warn(`Unable to decode image "${objId}": "${reason}".`);

        if (imageRef) {
          this.globalImageCache.addDecodeFailed(imageRef);
        }
        return this._sendImgData(objId, /* imgData = */ null, cacheGlobally);
      });

    if (cacheKey) {
      const cacheData = { objId, fn, args, optionalContent, hasMask };
      localImageCache.set(cacheKey, imageRef, cacheData);

      if (imageRef) {
        this._regionalImageCache.set(/* name = */ null, imageRef, cacheData);

        if (cacheGlobally) {
          assert(globalCacheData, "The global cache-data must be available.");
          this.globalImageCache.setData(imageRef, globalCacheData);
        }
      }
    }
  }