@pdfking/pdfengine

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): Pixel

ConvertPixels

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 = {}
): PlottedPagePdf

PathContentType

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>): RemoteFile

GenerateFile

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

  • url URL to fetch
  • options Progress tracking options with download callback

Returns Promise resolving to Response with tracked stream

Example
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