@pdfking/pdfengine

Types

types

PdfEngineOptions

type PdfEngineOptions = {
  /** How much more a pdf being read as it downloads is pulled in before the
   * reader looks again for the page it wants */
  chunk_size: Bytes
  /** The bleed a page is rasterized with round its trim: a raster is the trim
   * grown by it, whatever the page's own boxes say */
  file_bleed: Margins<Distance>
  /** Directory path for file caching */
  file_cache: string
  /** DPI for rasterizing PDF pages */
  image_dpi: Pixel
  /** Longest edge of a rasterized page, in pixels: lowers image_dpi for pages
   * that would exceed it. Past ~2k a page preview costs memory and upload time
   * for detail no screen shows (300dpi A4 is 2480x3508).
   */
  max_size: Pixel
  /** Stroke width for vector rendering */
  stroke_width: Distance
  /** Chunk size for tracking operations */
  tracking_chunk_size: Bytes
  /** Unit to pixel conversion factor */
  unit_to_px: Pixel
}

PDF engine configuration options โ€” defaults in CreatePdfEngineOptions.

PageOrder

type PageOrder = { page_order: number }

BinaryPagePdf

type BinaryPagePdf = Omit<SinglePageTask, "colors" | "transparency"> & {
  colors?: string[]
  enum: VirtualFileType.BINARY_PAGE_PDF
  page_count: PageNumber
  page_number: PageNumber
  source: VirtualFile
  source_page: PageNumber
  transparency?: boolean
} & MetaFile &
  PageOrder

MultiPagePdf

type MultiPagePdf = {
  content?: RemoteFile
  enum: VirtualFileType.MULTI_PAGE_PDF
  pages: SinglePagePdf[]
} & MetaFile

PlottedPagePdf

type PlottedPagePdf = {
  content?: RemoteFile
  context: FilePlottingContext
  enum: VirtualFileType.PLOTTED_PAGE_PDF
  guid: StringGUID
  page: Page
  page_count: PageNumber
  page_number: PageNumber
} & Media &
  MetaFile &
  PageOrder

RemoteFile

type RemoteFile = {
  enum: VirtualFileType.REMOTE_FILE
  guid: StringGUID
  // What the bytes behind the mirrors are, when somebody has already looked:
  // a file written by this engine knows its own size, etag and write time, a
  // url somebody pasted knows none of it until it is fetched. Absent or whole
  // โ€” never half of it, so a reader that has it needs nothing else.
  info?: FileInfo
  mirrors: string[]
  type: ContentType
} & MetaFile

SinglePageImage

type SinglePageImage = SinglePageTask & {
  dpi: Pixel
  enum: VirtualFileType.SINGLE_PAGE_IMAGE
  source: SinglePagePdf
} & MetaFile

ZipFile

type ZipFile = {
  content?: RemoteFile
  enum: VirtualFileType.ZIP_FILE
  files: Record<FileName, VirtualFile>
} & MetaFile

GeneratedContent

type GeneratedContent = Partial<MetaFile> & {
  type: ContentType
  emit?: (task: TrackingStatus) => void
  guid?: StringGUID
}

What has to be settled about content before it is written: what it is, who hears about a write worth waiting for, and what it will be called.

The name is the one thing a caller may already know โ€” a task names what it produces after the file it makes (see FileContentGUID), so a re-run lands the bytes back where every reference to them still points. A caller with nothing to name it by leaves it out and gets a uuid.

MetaFile

type MetaFile = {
  file_meta: Record<string, string>
}

SinglePagePdf

type SinglePagePdf = BinaryPagePdf | PlottedPagePdf

SinglePageTask

type SinglePageTask = {
  content: RemoteFile
  // what the page draws with: rgb/cmyk/gray/lab and spot colorants by name โ€”
  // a plot's, the colors it sets and its media's as split reads them
  colors: string[]
  // whether anything it draws is transparent: a color below full alpha, or a
  // media placed that is
  transparency: boolean
} & Media

VirtualFile

type VirtualFile = BinaryPagePdf | MultiPagePdf | PlottedPagePdf | RemoteFile | SinglePageImage | ZipFile

NetworkTrackingCallback

type NetworkTrackingCallback = (info: { done: number; total: number }) => void

Callback for tracking network transfer progress.

Receives current progress info (bytes done and total).

Example
import type { NetworkTrackingCallback } from "@pdfking/pdfengine"

const track: NetworkTrackingCallback = ({ done, total }) => {
  console.log(`${done}/${total} bytes`)
}
track({ done: 512, total: 1024 }) // 512/1024 bytes

NetworkProgress

type NetworkProgress = {
  download?: NetworkTrackingCallback
  upload?: NetworkTrackingCallback
}

Progress tracking configuration for network requests.

Optional callbacks for monitoring upload and download progress.

Example
import type { NetworkProgress } from "@pdfking/pdfengine"
import { ProgressFetch } from "@pdfking/pdfengine"

const progress: NetworkProgress = {
  download: ({ done, total }) => console.log(`Downloaded: ${done}/${total}`)
}
await (await ProgressFetch("data:text/plain,hello", progress)).text() // "hello"

Matrix

type Matrix<T extends number> = [number, number, number, number, T, T]

An affine transform in PDF's own layout, [a b c d e f]:

x' = aยทx + cยทy + e
y' = bยทx + dยทy + f

The same six numbers PDF's cm, SVG's matrix() and IDML's ItemTransform take, so every backend can hand one straight to its output.

T is the unit the transform moves things in โ€” Matrix<Distance> in the plot's own space, Matrix<Pixel> once a backend has converted. Only e and f carry it: aโ€“d scale and turn, so they are ratios, and a Distance times one of them is still a Distance. Writing them as T too would be a lie the compiler cannot catch, which is exactly the mistake this type exists to prevent.

Layer

type Layer = {
  /** shown when the pdf opens */
  visible: boolean
  printable: boolean
  name?: string
  locked: boolean
  idml_color?: string
  idml_merge_with?: LayerID
}

ExtendedColor

type ExtendedColor = Color & {
  idml_model?: "Process" | "Spot" | "Registration"
  idml_reserved?: "Paper" | "Black" | "Registration" | "Cyan" | "Magenta" | "Yellow"
  // A pdf plots it as an ink of its own, a Separation of this name, its CMYK
  // only what shows wherever the ink is not. Apart from `idml_model`: a Spot
  // swatch drawn on a printed layer must stay process in the pdf.
  spot_name?: string
}

PageNumber

type PageNumber = Float<"PageNumber", "i64">

ColorID

type ColorID = String<"ColorID">

PathPoint

type PathPoint<T = Distance> = Point<T> & {
  before: Point<T>
  after: Point<T>
}

A path's anchor and the Bรฉzier handles of the segments either side of it: before pulls the segment arriving, after the one leaving. Both are absolute points in the anchor's own space, not offsets; a handle on its anchor pulls nothing, and a segment with both of its handles on their anchors is a straight line.

Media

type Media<T = Distance> = {
  trim: Rectangle<T>
  bleed: Margins<T>
}

A page as whoever places it sees it, the same shape a plotted page has: its TrimBox โ€” the graphic and the cut โ€” and the bleed round it, each edge by its own. An uploaded page's bleed is how far its MediaBox reaches past the trim; a raster's is what it was drawn with. Where the boxes sit in the pdf stays with pdfium, which reads them off the page itself.

GroupTransform

type GroupTransform = Partial<{
  origin: Point<Distance>
  rotation: Degree
  scale: Point<Percentage>
  translate: Point<Distance>
}>

A transform as an author spells it, for GroupMatrix to turn into a matrix: whatever it leaves out is the identity.

LayerMap

type LayerMap = Record<LayerID, Layer>

ColorMap

type ColorMap = Record<ColorID, ExtendedColor>

MediaEntry

type MediaEntry = {
  matrix: Matrix<Distance>
}

What a file_map entry carries besides its page: the file's own matrix, how the caller scales a file into every box it fills. Written about the page's centre โ€” the origin is the centre of whichever box shows it โ€” and applied before the media's own media_matrix.

LayerID

type LayerID = String<"LayerID">

RequestRaster

type RequestRaster = (
  filename: FileName,
  page_number: PageNumber
) => Promise<string | undefined>

PDFReaderOptions

type PDFReaderOptions = {
  // how much more a reader that reads as the bytes arrive pulls in before it
  // looks again
  chunk_size: Bytes
  icc?: string
  password?: string
  size: Bytes
  stream: StreamInput
  type: string
}

BasePlotObjectData

type BasePlotObjectData = {
  layer: LayerID
}

PrintLayer

type PrintLayer = EnumUnion<{
  overlay: {
    order: number
    guide_color?: CMYKColor
    print_color: CMYKColor
    print_roughness?: number
  }
  print: {
    order: number
  }
  // Covers the whole sheet (lamination): drawn from no file, the media's own
  // extent is the mask.
  coating: {
    order: number
    print_color: CMYKColor
    print_roughness?: number
  }
}>

PDFConvertFunction

type PDFConvertFunction = (
  input: PDFReaderOptions,
  resolution: Pixel
) => Promise<Required<PDFSplitResult>>

PlotPath

type PlotPath = EnumType<
  PlotFunction.PLOT_PATH,
  ConcretePlotObjectData & {
    closed: boolean
    points: PathPoint[]
  }
>

CompletePage

type CompletePage = Readonly<
  {
    /** How far the page is plotted beyond its box, side by side. */
    bleed: Margins<Distance>
    shapes: PlotShape[]
  } & SpatialRectangle<Distance>
>

PlotRect

type PlotRect = EnumType<
  PlotFunction.PLOT_RECT,
  ConcretePlotObjectData & {
    position: SpatialRectangle<Distance>
  }
>

PDFSplitResult

type PDFSplitResult = {
  stream: StreamInput
  // what the page draws with: rgb/cmyk/gray/lab and spot colorants by
  // name, each once โ€” only a backend that reads the content knows
  colors?: string[]
  // whether anything it draws is transparent: without, gs -dNOTRANSPARENCY
  // draws it the same โ€” known as colors is
  transparency?: boolean
} & Media<Pixel>

PlotGroup

type PlotGroup = EnumType<
  PlotFunction.PLOT_GROUP,
  Omit<BasePlotObjectData, "layer"> & {
    matrix: Matrix<Distance>
    shapes: PlotShape[]
  }
>

An affine transform around a list of shapes โ€” the only way anything rotates, scales or moves as a unit.

matrix maps a point of a child, written in the group's own coordinates, into the space the group sits in. Groups nest: a child group's matrix applies before its parent's. Build one with GroupMatrix.

No layer: each child keeps its own, so one group may span layers.

PDFPageMeta

type PDFPageMeta = Media<Pixel> & {
  colors?: string[]
  transparency?: boolean
  page_number: PageNumber
  page_count: PageNumber
}

ConcretePlotObjectData

type ConcretePlotObjectData = BasePlotObjectData & {
  fill_color: ColorID
  stroke_color: ColorID
  stroke_width: Distance
}

PlottingContext

type PlottingContext<T = unknown> = {
  color_map: ColorMap
  /** Media pages by `PlotMedia.filename` then `page_number`. A missing page
   * plots blank. */
  file_map: Record<FileName, Record<PageNumber, T>>
  font_family: string
  font_size: Pixel
  horizontal_align: HorizontalAlign
  image_dpi: Pixel
  layer_map: LayerMap
  page_bleed: Margins<Distance>
  stroke_width: Distance
  unit_to_px: Pixel
  vertical_align: VerticalAlign
}

PDFMediaEntry

type PDFMediaEntry = MediaEntry & { stream: StreamInput }

A media page as the PDF plotters read it: the single-page PDF stream and the file's matrix.

PlotText

type PlotText = EnumType<
  PlotFunction.PLOT_TEXT,
  ConcretePlotObjectData & {
    font_name: string
    font_size: Pixel
    horizontal_align: HorizontalAlign
    position: SpatialRectangle<Distance>
    text: string
    text_color: ColorID
    vertical_align: VerticalAlign
  }
>

PageShape

type PageShape = PlotShape extends infer T
  ? T extends PlotGroup
    ? Partial<Omit<PlotGroup, "shapes">> & Pick<PlotGroup, "enum"> & { shapes: PageShape[] }
    : T extends PlotShape
      ? Partial<T> & Pick<T, "enum">
      : never
  : never

A shape with only its enum required: whatever it leaves out is completed with the defaults when the page is plotted. A group's children likewise.

PlotMedia

type PlotMedia = EnumType<
  PlotFunction.PLOT_MEDIA,
  BasePlotObjectData & {
    media_matrix: Matrix<Distance>
    position: SpatialRectangle<Distance>
    mask: Margins<Distance>
    filename: FileName
    page_number: PageNumber
    print_layer: PrintLayer
  }
>

A page of a source file placed in a box.

The source page's trim box centre lands on the position centre, and the page then goes through media_matrix, written in the same space position is. That is how the page is scaled, turned, mirrored or moved inside its box.

mask is what stays visible: position grown per edge, signed โ€” positive shows the page beyond its box (bleed), negative crops it. The mask belongs to the box, not to the page: media_matrix moves the page under it and leaves it where it is.

filename and page_number look the page up in the context's file_map.

PlotShape

type PlotShape = PlotGroup | PlotMedia | PlotPath | PlotRect | PlotText

PDFPlotRaster

type PDFPlotRaster = {
  dpi: Pixel
  bleed: Margins<Pixel>
}

A plot drawn straight to pixels instead of written as a pdf: at dpi, over the trim grown by bleed whatever the page's own bleed.

PDFMergeInput

type PDFMergeInput = PDFReaderOptions & { meta: PDFPageMeta }

PDFSplitFunction

type PDFSplitFunction = (
  input: PDFReaderOptions,
  rangeFn?: (page_count: number) => Sequence<number>
) => AsyncGenerator<PDFSplitResult>

PDFMergeFunction

type PDFMergeFunction = (pages: AsyncSequence<PDFMergeInput>) => AsyncGenerator<StreamInput>

PDFPlotResult

type PDFPlotResult = {
  stream: StreamInput
  type: ContentType.PDF | ImageContentType
  colors: string[]
  transparency: boolean
}

A plotted page: its single-page PDF โ€” or, plotted with a raster, its image in the format the platform's encoder wrote (webp in the browser, png on node) โ€” and what it draws with: rgb/cmyk for the colors set and the colors of the media placed, each once; and whether any of it is transparent, a color below full alpha or a media placed that is.

Page

type Page = Readonly<
  Partial<
    {
      bleed: Margins<Distance>
      /** The page's identity, when the caller already knows it: the engine
       * keys the plot on it instead of hashing the whole page and its context.
       * It must change whenever anything that draws the page does. */
      page_id: StringGUID
      shapes: PageShape[]
    } & SpatialRectangle<Distance>
  >
>

PDFPlotFunction

type PDFPlotFunction<T = PDFMediaEntry, R = PDFPlotResult> = (
  pages: CompletePage[],
  context: PlottingContext<T>,
  raster?: PDFPlotRaster
) => AsyncGenerator<R>

FileIdmlPlottingContext

type FileIdmlPlottingContext = Expand<Partial<Parameters<typeof IDMLPlot>[1]>>

FileMediaEntry

type FileMediaEntry = MediaEntry & { page: SinglePagePdf }

A media page as the engine plots it: the split page, fetched only when a plotted media references it, and the file's matrix.

FilePlottingContext

type FilePlottingContext = Expand<Partial<PlottingContext<FileMediaEntry>>>

PlotSpec

type PlotSpec = Readonly<[Page, FilePlottingContext?]>

methods

PlannedFile

type PlannedFile = Exclude<VirtualFile, RemoteFile>

Every file but a url has a plan: a url is only ever read.

FilePlanOutput

type FilePlanOutput<F extends PlannedFile> = F extends MultiPagePdf | ZipFile
  ? RemoteFile
  : F extends PlottedPagePdf | SinglePageImage
    ? SinglePageTask
    : F

What a file's plan yields: a merge or a zip its bytes, a plot or an image the page task holding them (a plot's with the colors it plotted), a split page itself.

engine

PdfEngine

type PdfEngine = {
  config: PdfEngineOptions
  CreateRemoteFile: (file: Partial<RemoteFile>) => RemoteFile
  GenerateFile: (stream: StreamInput, content: GeneratedContent) => Promise<RemoteFile>
  MergePdf: (files: Sequence<VirtualFile | PlotSpec>) => Promise<MultiPagePdf>
  PlotIdml: (plots: Page[], context?: Partial<FileIdmlPlottingContext>) => Promise<RemoteFile>
  PlotPdf: (plot: Page, context?: Partial<FilePlottingContext>) => Promise<PlottedPagePdf>
  RasterizePdf: (files: Sequence<VirtualFile>) => AsyncGenerator<SinglePageImage>
  RequestFile: (
    file: VirtualFile,
    emit?: (status: TrackingStatus) => void
  ) => Promise<StandardResponse>
  SplitPdf: (files: Sequence<VirtualFile>) => AsyncGenerator<SinglePagePdf>
  ZipFiles: (files: Sequence<[string, VirtualFile]>) => Promise<ZipFile>
}