mozilla/pdf.js · error · Error

Bad end offset

Error message

Bad end offset: ${end}

What it means

Thrown by ChunkedStream.onReceiveData when a delivered chunk's end position (begin + chunk.byteLength) is neither aligned to chunkSize nor exactly equal to the document's total byte length. The library enforces this so its internal _loadedChunks set stays consistent with the chunk grid. A violation means the byte-range transport and the configured chunkSize disagree about chunk boundaries.

Solutions

  1. Align server Range responses to the rangeChunkSize value passed to getDocument so every served chunk starts and ends on a chunkSize boundary.
  2. Verify Content-Range and Content-Length headers agree with the byte range the client requested.
  3. If you implement a custom IPDFFileStreamReader/NetworkLayer, return chunks whose begin and end are multiples of chunkSize (the final chunk excepted).
  4. Disable range requests via getDocument({ disableRange: true }) when the server cannot honor aligned ranges.

Example fix

// before
stream.onReceiveData(begin, chunk); // begin=5000, chunkSize=65536

// after
const cs = stream.chunkSize;
const end = begin + chunk.byteLength;
if (begin % cs === 0 && (end % cs === 0 || end === stream.bytes.length)) {
  stream.onReceiveData(begin, chunk);
} else {
  console.warn('Ignoring mis-aligned chunk', { begin, end, cs });
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate chunk alignment before invoking onReceiveData
function safeReceiveData(stream, begin, chunk) {
  const cs = stream.chunkSize;
  const end = begin + chunk.byteLength;
  if (begin % cs !== 0) {
    console.warn('begin not aligned', { begin, cs });
    return false;
  }
  if (end % cs !== 0 && end !== stream.bytes.length) {
    console.warn('end not aligned', { end, cs, total: stream.bytes.length });
    return false;
  }
  stream.onReceiveData(begin, chunk);
  return true;
}

Prevention

When it happens

Trigger: Calling onReceiveData(begin, chunk) where (begin + chunk.byteLength) % chunkSize !== 0 and the end is not the final byte. Occurs when a Range-request server returns responses whose sizes differ from the client's rangeChunkSize, when Content-Range/Content-Length disagree, or when a custom network/stream-reader layer passes mis-sized chunks.

Common situations: Self-hosted PDF behind a CDN or reverse proxy that re-chunks responses; mismatched rangeChunkSize between client and origin server; multi-part/byteranges responses served in arbitrary pieces; a server that ignores Range headers and streams the whole file split arbitrarily.

Related errors


AI-assisted analysis of mozilla/pdf.js@5903d58d58 (2026-08-13). Data as JSON: /api/errors/f59c889a50c49a00. Report an issue: GitHub.

Appendix: source

Thrown at src/core/chunked_stream.js:71

  get numChunksLoaded() {
    return this._loadedChunks.size;
  }

  get isDataLoaded() {
    return this.numChunksLoaded === this.numChunks;
  }

  onReceiveData(begin, chunk) {
    const chunkSize = this.chunkSize;
    if (begin % chunkSize !== 0) {
      throw new Error(`Bad begin offset: ${begin}`);
    }

    // Using `this.length` is inaccurate here since `this.start` can be moved
    // (see the `moveStart` method).
    const end = begin + chunk.byteLength;
    if (end % chunkSize !== 0 && end !== this.bytes.length) {
      throw new Error(`Bad end offset: ${end}`);
    }

    if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
      assert(
        chunk instanceof ArrayBuffer,
        "onReceiveData - expected an ArrayBuffer."
      );
    }
    this.bytes.set(new Uint8Array(chunk), begin);
    const beginChunk = Math.floor(begin / chunkSize);
    const endChunk = Math.floor((end - 1) / chunkSize) + 1;

    for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
      // Since a value can only occur *once* in a `Set`, there's no need to
      // manually check `Set.prototype.has()` before adding the value here.
      this._loadedChunks.add(curChunk);
    }
  }

View on GitHub (pinned to 5903d58d58)