Runtime
types
VirtualFileType
const enum VirtualFileType {
MULTI_PAGE_PDF = "pdf",
REMOTE_FILE = "url",
SINGLE_PAGE_IMAGE = "img",
BINARY_PAGE_PDF = "page",
PLOTTED_PAGE_PDF = "plot",
ZIP_FILE = "zip",
}PAGE_ORDER_STRIDE
const PAGE_ORDER_STRIDE = 1_000_000
TrackedTaskName
const enum TrackedTaskName {
COMPRESS = "COMPRESS",
DOWNLOAD = "DOWNLOAD",
GENERATE_FILE = "GENERATE_FILE",
FETCH = "FETCH",
MERGE_PDF = "MERGE_PDF",
PLOT_PDF = "PLOT_PDF",
RASTERIZE_PDF = "RASTERIZE_PDF",
SPLIT_PDF = "SPLIT_PDF",
UPLOAD = "UPLOAD",
}ErrorName
const enum ErrorName {
BROWSER_ERROR = "BROWSER_ERROR",
CACHE_EXPIRED = "CACHE_EXPIRED",
FILE_NOT_FOUND = "FILE_NOT_FOUND",
FILE_NOT_PDF = "FILE_NOT_PDF",
GENERIC_ERROR = "GENERIC_ERROR",
PERMISSION_ERROR = "PERMISSION_ERROR",
}HorizontalAlign
const enum HorizontalAlign {
CENTER = "center",
LEFT = "left",
RIGHT = "right",
}PlotFunction
const enum PlotFunction {
PLOT_GROUP = "plot_group",
PLOT_MEDIA = "plot_media",
PLOT_PATH = "plot_path",
PLOT_RECT = "plot_rect",
PLOT_TEXT = "plot_text",
}VerticalAlign
const enum VerticalAlign {
BOTTOM = "bottom",
CENTER = "center",
TOP = "top",
}ToPixels
const ToPixels = ({ unit_to_px }: { unit_to_px: Pixel }, value: Distance): PixelConvertPixels
const ConvertPixels = <T extends Record<string, Pixel>>(
options: { unit_to_px: Pixel },
rect: T
): { [K in keyof T]: Distance }ConvertDistances
const ConvertDistances = <T extends Record<string, Distance>>(
options: { unit_to_px: Pixel },
rect: T
): { [K in keyof T]: Pixel }MatrixApply
const MatrixApply = <T extends number>(
[a, b, c, d, e, f]: Matrix<T>,
{ x, y }: Point<T>
): Point<T>The point p seen through matrix, in the matrix's own space.
DrawPath
const DrawPath = (points: Sequence<Point<Distance>>): PathPoint[]
points joined by straight lines: each one's handles on itself.
MatrixIdentity
const MatrixIdentity = <T extends number>(): Matrix<T>
The transform that moves nothing, in whatever space the caller works in.
A function rather than a constant because the translation carries a unit and a zero has to be a zero of that unit.
CreateContext
const CreateContext = <T>(context: Partial<PlottingContext<T>>): PlottingContext<T>
TurnBox
const TurnBox = ( box: SpatialRectangle<Distance>, rotation: Degree, origin: Point<Distance>, translate?: Point<Distance> ): SpatialRectangle<Distance>
The box box lands in, turned rotation counter-clockwise about origin
and moved by translate, through the same matrix a group carries.
MatrixCompose
const MatrixCompose = <T extends number>( [a1, b1, c1, d1, e1, f1]: Matrix<T>, [a2, b2, c2, d2, e2, f2]: Matrix<T> ): Matrix<T>
inner then outer — the matrix mapping a point of the inner space all the
way out. Composing a child group's matrix with its parent's is exactly this,
in that order.
Both sides must already be in the same space: a–d are ratios, so the
product is a ratio, and the translation stays in T.
Cross
const Cross = ( arms: Distance | Margins<Distance>, center: Point<Distance> = ZERO_POINT ): PathPoint[][]
A + on center: the line across and the line up, each arm as long as
arms gives it — the same all round, or edge by edge.
MatrixBox
const MatrixBox = ( matrix: Matrix<Distance>, box: SpatialRectangle<Distance> ): SpatialRectangle<Distance>
The axis-aligned box box lands in, seen through matrix: its corners
taken through it and squared back up. A quarter turn keeps it exact; any
other angle gives its bounding box.
Oval
const Oval = (center: Point<Distance>, radius: Point<Distance> | Distance): PathPoint[]
The ellipse round center of radius across and up — the same both ways,
a circle: four anchors, each with both handles, to plot with closed: true.
MatrixToPixels
const MatrixToPixels = (
context: { unit_to_px: Pixel },
[a, b, c, d, e, f]: Matrix<Distance>
): Matrix<Pixel>A transform written in the plot's own units, in the backend's pixels.
Only the translation converts: a–d are ratios and mean the same thing in
either space. A backend needs this before composing with anything it measured
itself, and every one of them emits pixels in the end.
MediaEntryMatrix
const MediaEntryMatrix = (
entry: MediaEntry | undefined,
{ position, media_matrix }: Pick<PlotMedia, "position" | "media_matrix">
): Matrix<Distance>A media's whole matrix: its file entry's, moved onto the box's centre, then
its own media_matrix. No entry, just its own.
RotateShape
const RotateShape = (shape: PlotShape, rotation: Degree): PlotShape
Turn a shape counter-clockwise around its own centre.
Shapes carry no rotation of their own, so this is how anything gets turned:
the result is a PlotGroup pivoting on the shape's centre. One rule for every
shape and every angle — an angle equivalent to zero is the only shortcut, and
gives the shape straight back.
ArcPoints
const ArcPoints = ( center: Point<Distance>, radius: Distance, start: Degree, sweep: Degree, radius_y: Distance = radius ): PathPoint[]
The arc of radius round center from start, sweep degrees
counterclockwise (clockwise when negative), as cubic Béziers of at most 90°
each: a handle k = 4/3·tan(θ/4)·r along the tangent either side of every
anchor. The first point's before and the last's after sit on their
anchors.
With radius_y it is an ellipse's: the circle's arc stretched along y, which
stretches its handles the same way, so the curves stay exact.
ShapeBounds
const ShapeBounds: (shape: PlotShape) => SpatialRectangle<Distance>
The axis-aligned box a shape occupies in the space it is written in.
A group's box is its children's, seen through its own transform — which is also why a rotated group's box is bigger than its contents. Media measures its mask, not its position: that is what ends up visible. A path's box holds its Bézier handles too.
ClipPolygon
const ClipPolygon = <T extends number>( points: Point<T>[], box: SpatialRectangle<T> ): Point<T>[]
The polygon points cut to box (Sutherland–Hodgman): a polygon of
fewer than three points when nothing of it falls within. A concave one cut
in two comes back as one, the halves joined along the box's side.
GroupMatrix
const GroupMatrix = ({
origin = ZERO_POINT,
rotation = ZERO_DEGREE,
scale = NO_SCALE,
translate = ZERO_POINT,
}: GroupTransform = {}): Matrix<Distance>A group's transform as one matrix:
p' = translate + origin + R(rotation) · S(scale) · (p − origin)
Scale applies first, then the rotation, both about origin, then the
translation. Angles are counter-clockwise, matching the page's y-up space.
Every field defaults to identity, so GroupMatrix({ rotation }) is a plain
turn about the origin and GroupMatrix() is the identity.
CreateCompletePage
const CreateCompletePage = (page: Page, context: PlottingContext): CompletePage
ClipShapes
const ClipShapes = ({
shapes,
box,
}: {
shapes: PageShape[]
box: SpatialRectangle<Distance>
}): PageShape[]shapes cut to box, as a clip would show them: rects and closed paths
to the part of them inside it, a curve sampled first; groups resolved, so
a turned rect is cut as the shape it turns into. What has no area to cut
(a text, a media, an open line) is kept whole.
methods
FileContentGUID
const FileContentGUID: (file: VirtualFile) => StringGUID
Which bytes a file is, from what it is made of — never from a plan.
A file with bytes of its own is named by them; a lazy one by its parts: a merge by its pages in order, a zip by its members' names and identities, a plot by the guid whoever made its shapes gave it. The task that produces a lazy file's bytes writes them under this name (see FilePlan), so a re-run lands where the first run did. The enum leads every hash so that different files over the same parts are different names.
FileNodeGUID
const FileNodeGUID = (file: VirtualFile & Partial<PageOrder>): StringGUID
A file's identity as a plan literal — what FileLiteral and
FileArray pass instead of letting the planner hash the value's JSON.
FileContentGUID already answers "which bytes is this": a guid for a
real file, its parts' guids for a lazy one. Working that out by
re-serializing the file walks everything underneath it, which for a merged
document is the entire document — once per page, at every level it is
embedded, which is how a 426-page imposition reached half a gigabyte of JSON
per hash.
page_order rides along because the engine relabels it when it places a
page in a merge (see SplitNode/mergeNode): two literals over the same bytes
at different positions are genuinely different nodes, and their hashes reach
the enclosing task's cache key, so collapsing them would let one merge be
served another merge's ordering from cache. It lives here rather than in
FileContentGUID because position is a property of this node, not of the
file — the blob-url and download callers key on content alone and should
keep sharing a hit across positions.
FileLiteral
const FileLiteral = <T extends VirtualFile & Partial<PageOrder>>(plan: Plan, file: T)
FileArray
const FileArray = <T extends VirtualFile & Partial<PageOrder>>(plan: Plan, files: T[])
FilePlan
const FilePlan = dispatch as <F extends PlannedFile>( file: F, planner: Planner, options: PdfEngineOptions ) => AtomFunction<FilePlanOutput<F>>
The plan that produces a file's bytes, built from the file when they are
asked for — never stored on it. The task it ends in writes them under the
file's FileContentGUID.
FileContentType
const FileContentType = FullMethodDispatcher<[VirtualFile], ContentType>({
[VirtualFileType.MULTI_PAGE_PDF]: Constant(ContentType.PDF),
[VirtualFileType.REMOTE_FILE]: file => file.type,
// Rasterized (webp/png by platform) or separated (tiff): whatever was stored
[VirtualFileType.SINGLE_PAGE_IMAGE]: file => file.content.type,
[VirtualFileType.BINARY_PAGE_PDF]: Constant(ContentType.PDF),
[VirtualFileType.PLOTTED_PAGE_PDF]: Constant(ContentType.PDF),
[VirtualFileType.ZIP_FILE]: Constant(ContentType.ZIP),
})files
CreatePlottedFile
const CreatePlottedFile = (
options: PdfEngineOptions,
plot: Page,
context: FilePlottingContext = {}
): PlottedPagePdfPathContentType
const PathContentType = ( names: Sequence<string>, fallback: ContentType = ContentType.BYTES ): ContentType
The content type of the first recognizable name in names — a path, url or
filename, matched on its extension alone.
Always answers: a name whose extension says nothing is opaque bytes, which is
exactly what it is. Callers that know better pass their own fallback.
CreateRemoteFile
const CreateRemoteFile = ({
file_meta,
guid,
mirrors,
type,
}: Partial<RemoteFile>): RemoteFileGenerateFile
const GenerateFile = async (
{ file_cache, tracking_chunk_size }: PdfEngineOptions,
stream: StreamInput,
{ type, emit = Void, guid = GuidToString(UUID()), file_meta = {} }: GeneratedContent
): Promise<RemoteFile>Content, written to the store and named by the url it answers to there.
What comes back is a remote file like any other: the store's own url is its
mirror, so anything holding it reads the bytes through the loader rather
than through the engine, and there is nothing left to find out about them —
the write already said everything info holds, which is what spares a
reader the download a mirror it knew nothing about would have cost.
The name is the FileContentGUID of the file the caller makes, when it has one: a task re-executed after its bytes were evicted writes them under the same name, so every stale reference embedded in a cached response is good again the moment the re-run lands — recovery is a property of the naming rather than a repair anybody performs. A caller making no file — an upload, a paste — has no identity to offer, and gets a uuid: two writes are two entries, and bytes that happen to match are still somebody else's file. Never a hash of the content: what a file is called says where it is kept, not what is in it.
RequestFile
const RequestFile = ( engine: Engine, options: PdfEngineOptions, file: VirtualFile, emit: Emit = Void ): Promise<StandardResponse>
Any VirtualFile as the response that serves it — produced on demand where the file is a recipe rather than content (see FilePlan).
The body stays a stream: a merge of a hundred pages reads each page as it
comes, and a file too big to hold is read in pieces by whoever can (see
ResizeUInt8). info is everything the engine knows about the bytes: its
etag is the file's guid, its type the file's own, whatever the serving
response's headers say.
network
ProgressFetch
const ProgressFetch = (
url: string,
{ download }: Omit<NetworkProgress, "upload"> = {}
): Promise<StandardResponse>Fetch with download progress tracking.
Wraps UrlLoad to track download progress via callback.
Parameters
urlURL to fetchoptionsProgress tracking options with download callback
Returns Promise resolving to Response with tracked stream
import { ProgressFetch } from "@pdfking/pdfengine"
const response = await ProgressFetch("data:text/plain,hello world", {
download: ({ done, total }) => console.log(`${done}/${total}`)
})
await response.text() // "hello world"ProgressFetchXHR
const ProgressFetchXHR = (
url: string,
{
data,
headers,
withCredentials,
}: {
data?: any
headers?: HeadersInit
withCredentials?: boolean
} = {},
{ download, upload }: NetworkProgress = {}
): Promise<Response>engine
PdfEngineFromExecutor
const PdfEngineFromExecutor = ( executor: Executor<TasksType, ExecutionContext>, config: Partial<PdfEngineOptions> ): PdfEngine