Skip to main content

Module source

Module source 

Source
Expand description

The object-source abstraction shared by the synchronous and asynchronous APIs.

pdfboss keeps exactly one implementation of every algorithm that reads a document — text extraction, rasterization — and two ways of delivering bytes to it. ObjectSource is what a caller holding the whole file provides. AsyncObjectSource is what a caller streaming from a file or a network provides. Algorithms are written once against the asynchronous trait and reach synchronous callers through Immediate and block_on.

block_on is a complete single-threaded driver: it polls, and on Poll::Pending parks the calling thread until a waker unparks it. A future built over Immediate resolves every leaf against a std::future::Ready and so completes on its first poll, never reaching the parking path — but that is an optimisation, not a precondition. Any future whose wakeup does not depend on an external reactor is driven correctly; one that does will park forever, so see block_on’s own documentation before handing it a future from elsewhere.

Nothing here needs an async runtime: Future, Pin, Box, Arc, Wake and std::future::ready are all in the standard library, so pdfboss-core stays free of executor dependencies.

§Signing a shared algorithm

Every algorithm written against AsyncObjectSource follows the same three rules. They are not stylistic: each one is what makes a single implementation serve both a synchronous and an asynchronous caller.

  1. Entry points take the source by valuesrc: S, never &S. A future holding &'a S is Send but never 'static, and the asynchronous consumers (spawning onto a runtime, crossing into the Python bindings) need both. It costs nothing: an asynchronous document is an Arc handle, and Immediate over a borrowed document is Copy.

    An entry point is a function a consumer awaits directly. Helpers inside one take &S, because the entry point owns the source and a future may borrow across its own awaits freely — the 'static question is settled at the outermost boundary, not at every internal call. That does put S: Sync on the entry point’s future being Send, which costs nothing either: resolve_with already requires Sync of every genuinely asynchronous source. One entry point calling another passes &src, which works because &S is itself an AsyncObjectSource.

  2. Put no Send or Sync bound on the function. Auto traits are inferred per instantiation, so one function yields a Send future over an asynchronous source and a non-Send future over Immediate<&Document> — which is correct, because block_on drives the latter on the calling thread and never sends it anywhere.

  3. Call src.resolve(o), not resolve_with, unless the algorithm genuinely needs S: Sync for other reasons. resolve_with requires Sync and so excludes Immediate<&Document>; reaching for it out of habit silently breaks the synchronous path.

crate::document::page_content_with is the reference example.

Structs§

Immediate
Presents a synchronous ObjectSource as an AsyncObjectSource whose futures are already complete.

Constants§

MAX_RESOLVE_DEPTH
Reference-chase depth limit for every reference chase in the crate: resolve_sync_with, resolve_with, the provided ObjectSource::resolve and crate::document::Document::resolve.

Traits§

AsyncObjectSource
Reads indirect objects and decodes streams, awaiting whatever I/O that takes.
ObjectSource
Reads indirect objects and decodes streams, with the whole file already available.

Functions§

block_on
Runs future to completion on the current thread.
resolve_sync_with
Chases reference chains against a synchronous source, depth-capped at MAX_RESOLVE_DEPTH.
resolve_with
Chases reference chains against an asynchronous source, depth-capped at MAX_RESOLVE_DEPTH.

Type Aliases§

BoxFuture
A boxed future, as returned by every AsyncObjectSource method.