SharePointVault ~/gotchas/spfx ⌕ Search 404 pages, error messages, scripts…Ctrl K ★ Star
M↓ README.mdM↓ decompressionstream-trailing-bytes-pdf-streams.md
sharepoint-vault›gotchas›spfx›decompressionstream-trailing-bytes-pdf-streams.mdhistoryedit on GitHub
● GOTCHAspfx· 2 min read

DecompressionStream and trailing bytes in PDF streams

applies to: DecompressionStream('deflate') in Chrome and Node 24 (both measured 2026-10-28) – any SPFx or browser code that inspects PDF files without a libraryreviewed: 2026-10-28#spfx#pdf#compression#web-streams#browser

A PDF stream's bytes sit between stream\n and endstream, but writers put a line break (\n or \r\n) before endstream – the PDF specification asks for one. DecompressionStream('deflate') treats those bytes as junk after the end of the data and the whole read fails – in Chrome new Response(stream).arrayBuffer() rejects with TypeError: Failed to fetch, in Node with TypeError: Trailing junk found after the end of the compressed stream. You lose even the data that was already inflated. Trim trailing CR/LF before inflating, read with getReader() chunk by chunk, and if the error arrives after some data, keep what you have.

Obsah proudu PDF leží mezi stream\n a endstream, ale před endstream zapisovače dávají konec řádku – specifikace PDF ho žádá. DecompressionStream('deflate') ho bere jako bajty za koncem dat a celé čtení spadne – v Chrome TypeError: Failed to fetch z arrayBuffer(), v Node TypeError: Trailing junk found after the end of the compressed stream. Přijdete i o to, co se už rozbalilo. Před rozbalením ořízněte koncové CR/LF, čtěte po kouscích přes getReader() a když chyba přijde až po datech, platí to, co už přišlo.

Symptom

A check "is this PDF a scan without text?" looked for fonts or text operators (/Font, Tj, TJ) inside compressed content streams. Every stream came back empty, so a text PDF whose fonts live only in compressed object streams was reported as "unknown", and a real scan was never recognised as a scan.

Cause

4 0 obj << /Length 25 /Filter /FlateDecode >>
stream
<25 bytes of zlib data>
endstream

Slicing from after stream\n to the index of endstream includes the line break before endstream. The zlib data ends before it; the decompressor sees extra bytes and errors. Response.arrayBuffer() reports only the error, never the partial result.

Fix

async function inflate(bytes: Uint8Array): Promise<Uint8Array | undefined> {
  let end = bytes.length;
  while (end > 0 && (bytes[end - 1] === 0x0a || bytes[end - 1] === 0x0d)) end--;
  const parts: Uint8Array[] = [];
  let total = 0;
  try {
    const reader = new Blob([bytes.subarray(0, end)]).stream()
      .pipeThrough(new DecompressionStream('deflate')).getReader();
    for (;;) {
      const { done, value } = await reader.read();
      if (done) break;
      if (value) { parts.push(value); total += value.length; }
    }
  } catch { if (!total) return undefined; }   // error after data: keep the data
  const out = new Uint8Array(total);
  let o = 0;
  for (const p of parts) { out.set(p, o); o += p.length; }
  return out;
}

Trimming alone covers the usual case; reading in chunks also covers streams whose /Length is shorter than the slice for other reasons.

How to verify

Compress a short content stream with CompressionStream('deflate'), append \r\n, and decompress it with new Response(...).arrayBuffer(). It rejects. Without the \r\n it returns the original bytes.

Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub
last reviewed: 2026-10-28last updated: 2026-10-28historyedit on GitHubview source