readSourceFully function

Future<Uint8List> readSourceFully(
  1. PdfByteSource source, {
  2. void onProgress(
    1. int received,
    2. int? total
    )?,
  3. int chunk = 8 << 20,
  4. PdfCancelToken? cancelToken,
})

Reads a whole PdfByteSource into one contiguous buffer, reporting progress as it goes.

This is the background "finish the download" half of progressive open: PdfDocument.openSource paints the first page(s) from a sparse buffer of just the bytes it needs (zeros in the free space the parser never reads), then a host reads the rest with this and swaps the full buffer in. Unlike that first-paint buffer, the bytes returned here are the complete file - what the edit session, signing, and the render workers need.

Progress is reported to onProgress after each read as (received, total); total is the document length when the source knows it, else null. Pass a cancelToken to abort in flight - a fired token makes this throw PdfHttpCancelledException between reads (and any read the source itself aborts propagates). chunk bounds each read (and so the progress granularity).

A known-length source is read forward in chunk windows until it is exhausted; an unknown-length one is chunked until a short/empty read signals the end.

Implementation

Future<Uint8List> readSourceFully(
  PdfByteSource source, {
  void Function(int received, int? total)? onProgress,
  int chunk = 8 << 20,
  PdfCancelToken? cancelToken,
}) async {
  assert(chunk > 0);
  final len = await source.length;
  if (len != null && len >= 0) {
    final out = Uint8List(len);
    var pos = 0;
    while (pos < len) {
      cancelToken?.throwIfCancelled();
      final end = pos + chunk < len ? pos + chunk : len;
      final data = await source.readRange(pos, end);
      if (data.isEmpty) break;
      // Never trust a source to honour the requested length: a misbehaving one
      // can answer with more bytes than asked for (a 200-style full body).
      // Clamp so setRange can't run past the buffer.
      final count = data.length < len - pos ? data.length : len - pos;
      out.setRange(pos, pos + count, data);
      pos += count;
      onProgress?.call(pos, len);
    }
    return pos == len ? out : Uint8List.sublistView(out, 0, pos);
  }
  // Unknown length: chunk until a short read signals EOF.
  final builder = BytesBuilder(copy: false);
  var pos = 0;
  while (true) {
    cancelToken?.throwIfCancelled();
    final data = await source.readRange(pos, pos + chunk);
    if (data.isEmpty) break;
    builder.add(data);
    pos += data.length;
    onProgress?.call(pos, null);
    if (data.length < chunk) break;
  }
  return builder.toBytes();
}