beginSMaskMode() {
    if (this.inSMaskMode) {
      throw new Error("beginSMaskMode called while already in smask mode");
    }
    const drawnWidth = this.ctx.canvas.width;
    const drawnHeight = this.ctx.canvas.height;
    const cacheId = "smaskGroupAt" + this.groupLevel;
    const scratchCanvas = this.cachedCanvases.getCanvas(
      cacheId,
      drawnWidth,
      drawnHeight
    );
    this.suspendedCtx = this.ctx;
    const ctx = (this.ctx = scratchCanvas.context);
    ctx.setTransform(this.suspendedCtx.getTransform());
    copyCtxState(this.suspendedCtx, ctx);
    mirrorContextOperations(ctx, this.suspendedCtx);

    this.setGState([["BM", "source-over"]]);
  }
Soft mask mode takes the current main drawing canvas and replaces it with
a temporary canvas. Any drawing operations that happen on the temporary
canvas need to be composed with the main canvas that was suspended (see
`compose()`). The temporary canvas also duplicates many of its operations
on the suspended canvas to keep them in sync, so that when the soft mask
mode ends any clipping paths or transformations will still be active and in
the right order on the canvas' graphics state stack.