runPdfRenderWorker function
void
runPdfRenderWorker()
Runs the render worker inside a dedicated Web Worker. A consuming web app ships a tiny worker script that calls this:
// web/pdf_render_worker.dart
import 'package:dart_pdf_editor/render_worker_web.dart';
void main() => runPdfRenderWorker();
compiled with dart compile js web/pdf_render_worker.dart -o web/pdf_render_worker.dart.js and served alongside the app; the app then
sets pdfRenderWorkerScriptUrl = 'pdf_render_worker.dart.js' before opening
a viewer. See doc/render_worker_web.md for the full wiring.
Protocol (mirrors the native isolate backend):
{kind:'init', bytes:ArrayBuffer|SharedArrayBuffer, shared}→ opens the document, replies{kind:'ready', shared}.{kind:'record', id, page, annotations}→ replies{kind:'result', id, buffer:ArrayBuffer|null}(null = the page can't be offloaded; the main thread renders it locally).{kind:'cancel'}→ sets the active cancellation token so the in-flight interpreter walk abandons early, replying withbuffer:null.
Implementation
void runPdfRenderWorker() {
final scope = globalContext as web.DedicatedWorkerGlobalScope;
PdfDocument? document;
PdfCancellationToken? activeToken;
// The handler MUST stay synchronous (return void): `.toJS` cannot convert a
// Future-returning function, so an `async` handler fails `dart compile js`
// ("invalid types in its function signature: Future<Null> Function(...)").
// The cancellable record below therefore runs in a fire-and-forget inner
// async closure instead of making the handler itself async.
scope.onmessage = ((web.MessageEvent event) {
final data = event.data as JSObject?;
if (data == null) return;
final kind = (data.getProperty('kind'.toJS) as JSString?)?.toDart;
if (kind == 'init') {
// Extract AND open inside the try: a malformed transfer (the cast or the
// ArrayBuffer view can throw on some hosts) must NOT skip the 'ready'
// reply below, or the main thread waits on it forever. A null document
// simply declines every page to a local render.
var shared = false;
try {
shared =
(data.getProperty('shared'.toJS) as JSBoolean?)?.toDart ?? false;
final buffer = data.getProperty('bytes'.toJS) as JSObject;
final bytes = shared
? _jsUint8View(buffer).toDart
: (buffer as JSArrayBuffer).toDart.asUint8List();
document = PdfDocument.open(bytes);
} catch (_) {
document = null; // bad transfer / broken document → local renders
}
// ALWAYS reply ready, even on failure, so the client never hangs.
scope.postMessage(JSObject()
..setProperty('kind'.toJS, 'ready'.toJS)
..setProperty('shared'.toJS, shared.toJS));
return;
}
if (kind == 'cancel') {
activeToken?.cancelled = true;
return;
}
if (kind != 'record') return;
final id = (data.getProperty('id'.toJS) as JSNumber).toDartInt;
final page = (data.getProperty('page'.toJS) as JSNumber).toDartInt;
final annotations =
(data.getProperty('annotations'.toJS) as JSBoolean).toDart;
final imagePixelRatio =
(data.getProperty('imageRatio'.toJS) as JSNumber?)?.toDartDouble;
// Default true so an older client that doesn't send the flag still decodes.
final decodeImages =
(data.getProperty('decodeImages'.toJS) as JSBoolean?)?.toDart ?? true;
final commandLimit =
(data.getProperty('commandLimit'.toJS) as JSNumber?)?.toDartInt;
final regionLeft =
(data.getProperty('regionLeft'.toJS) as JSNumber?)?.toDartDouble;
final regionBottom =
(data.getProperty('regionBottom'.toJS) as JSNumber?)?.toDartDouble;
final regionRight =
(data.getProperty('regionRight'.toJS) as JSNumber?)?.toDartDouble;
final regionTop =
(data.getProperty('regionTop'.toJS) as JSNumber?)?.toDartDouble;
final imageDecodeRegion = regionLeft != null &&
regionBottom != null &&
regionRight != null &&
regionTop != null
? PdfRect(regionLeft, regionBottom, regionRight, regionTop)
: null;
final token = PdfCancellationToken();
activeToken = token;
// Fire-and-forget: launch the cancellable walk without awaiting it here, so
// the message handler returns void (see the note above) while a subsequent
// 'cancel' message can still flip token.cancelled mid-walk.
() async {
Uint8List? out;
String? error;
final doc = document;
try {
if (doc != null) {
out = await _recordPageAsync(doc, page, annotations, imagePixelRatio,
decodeImages, commandLimit, imageDecodeRegion, token);
}
} on PdfCancelledException {
out = null;
} catch (e, st) {
out = null; // any failure → the main thread renders this page locally
error = '$e\n$st';
}
// Only clear the active token if it is still ours — a newer record may
// have replaced it while this one was running.
if (identical(activeToken, token)) activeToken = null;
final result = JSObject()
..setProperty('kind'.toJS, 'result'.toJS)
..setProperty('id'.toJS, id.toJS);
if (out == null) {
result.setProperty('buffer'.toJS, null);
if (error != null) result.setProperty('error'.toJS, error.toJS);
scope.postMessage(result);
} else {
// Copy to an exact-length buffer, then transfer it (zero-copy).
final jsBuffer = Uint8List.fromList(out).buffer.toJS;
result.setProperty('buffer'.toJS, jsBuffer);
scope.postMessage(result, <JSAny>[jsBuffer].toJS);
}
}();
}).toJS;
}