diff mupdf-source/platform/wasm/lib/mupdf.ts @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/platform/wasm/lib/mupdf.ts	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,4492 @@
+// Copyright (C) 2004-2025 Artifex Software, Inc.
+//
+// This file is part of MuPDF WASM Library.
+//
+// MuPDF is free software: you can redistribute it and/or modify it under the
+// terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
+//
+// Alternative licensing terms are available from the licensor.
+// For commercial licensing, see <https://www.artifex.com/> or contact
+// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
+// CA 94129, USA, for further information.
+
+"use strict"
+
+import { Pointer } from "./mupdf-wasm.js"
+import libmupdf_wasm from "./mupdf-wasm.js"
+
+declare global {
+	var $libmupdf_wasm_Module: any
+}
+
+var node_fs: any = null
+if (typeof process !== "undefined" && process.versions && process.versions.node)
+	node_fs = await import("node:fs")
+
+const libmupdf = await libmupdf_wasm(globalThis["$libmupdf_wasm_Module"])
+
+libmupdf._wasm_init_context()
+
+function Malloc<T>(size: number) {
+	return libmupdf._wasm_malloc(size) as unknown as Pointer<T>
+}
+
+function Free(ptr: any) {
+	libmupdf._wasm_free(ptr as Pointer<"void">)
+}
+
+export const memento = {
+	listBlocks() {
+		libmupdf._wasm_Memento_listBlocks()
+	},
+	checkAllMemory() {
+		libmupdf._wasm_Memento_checkAllMemory()
+	},
+}
+
+/*
+--------------------------------------------------------------------------------
+
+How to call into WASM and convert values between JS and WASM (libmupdf) worlds:
+
+Passing values into WASM needs to either copy primitive values into WASM memory
+or passing around pointer values.
+
+	Wrap and/or copy non-Userdata values into WASM:
+
+		STRING(stringValue)
+		STRING2(stringValue) -- if you need to pass more than one string
+		MATRIX(matrixArray)
+		RECT(rectArray)
+		BUFFER(bufferValue)
+		etc.
+
+	Look up an enum value by string:
+
+		ENUM<EnumType>(string, listOfValidValues)
+
+	Pass the pointer when the value is a Userdata object:
+
+		userdataObject.pointer
+
+Convert WASM pointer into a JS value (for simple types like strings and matrices).
+
+	fromType(pointer)
+
+Wrap a WASM pointer in a new Userdata object (for complex types):
+
+	new Wrapper(pointer)
+
+PDFObjects are always bound to a PDFDocument, so must be accessed via a document:
+
+	doc._fromPDFObjectNew(new_ptr)
+	doc._fromPDFObjectKeep(borrowed_ptr)
+	doc._PDFOBJ(value)
+
+Type checking of input arguments at runtime.
+
+	checkType(value, "string")
+	checkType(value, Class)
+	checkRect(value)
+	checkMatrix(value)
+
+	This code needs to work type safely from plain Javascript too,
+	so do NOT rely on Typescript to do all the type checking.
+
+--------------------------------------------------------------------------------
+*/
+
+export type Matrix = [number, number, number, number, number, number]
+export type Rect = [number, number, number, number]
+export type Quad = [number, number, number, number, number, number, number, number]
+export type Point = [number, number]
+export type Color = [number] | [number, number, number] | [number, number, number, number]
+export type AnnotColor = [] | Color
+
+export type Rotate = 0 | 90 | 180 | 270
+
+export const Matrix = {
+	identity: [ 1, 0, 0, 1, 0, 0 ] as Matrix,
+	scale(sx: number, sy: number): Matrix {
+		return [ sx, 0, 0, sy, 0, 0 ]
+	},
+	translate(tx: number, ty: number): Matrix {
+		return [ 1, 0, 0, 1, tx, ty ]
+	},
+	rotate(d: number): Matrix {
+		while (d < 0)
+			d += 360
+		while (d >= 360)
+			d -= 360
+		let s = Math.sin((d * Math.PI) / 180)
+		let c = Math.cos((d * Math.PI) / 180)
+		return [ c, s, -s, c, 0, 0 ]
+	},
+	invert(m: Matrix): Matrix {
+		checkMatrix(m)
+		let det = m[0] * m[3] - m[1] * m[2]
+		if (det > -1e-23 && det < 1e-23)
+			return m
+		let rdet = 1 / det
+		let inva = m[3] * rdet
+		let invb = -m[1] * rdet
+		let invc = -m[2] * rdet
+		let invd = m[0] * rdet
+		let inve = -m[4] * inva - m[5] * invc
+		let invf = -m[4] * invb - m[5] * invd
+		return [ inva, invb, invc, invd, inve, invf ]
+	},
+	concat(one: Matrix, two: Matrix): Matrix {
+		checkMatrix(one)
+		checkMatrix(two)
+		return [
+			one[0] * two[0] + one[1] * two[2],
+			one[0] * two[1] + one[1] * two[3],
+			one[2] * two[0] + one[3] * two[2],
+			one[2] * two[1] + one[3] * two[3],
+			one[4] * two[0] + one[5] * two[2] + two[4],
+			one[4] * two[1] + one[5] * two[3] + two[5],
+		]
+	},
+}
+
+export const Rect = {
+	MIN_INF_RECT: 0x80000000,
+	MAX_INF_RECT: 0x7fffff80,
+	empty: [ 0x80000000, 0x80000000, 0x7fffff80, 0x7fffff80 ],
+	invalid: [ 0, 0, -1, -1 ],
+	infinite: [ 0x7fffff80, 0x7fffff80, 0x80000000, 0x80000000 ],
+	isEmpty: function (rect: Rect) {
+		checkRect(rect)
+		return rect[0] >= rect[2] || rect[1] >= rect[3]
+	},
+	isValid: function (rect: Rect) {
+		checkRect(rect)
+		return rect[0] <= rect[2] && rect[1] <= rect[3]
+	},
+	isInfinite: function (rect: Rect) {
+		checkRect(rect)
+		return (
+			rect[0] === Rect.MAX_INF_RECT &&
+			rect[1] === Rect.MAX_INF_RECT &&
+			rect[2] === Rect.MIN_INF_RECT &&
+			rect[3] === Rect.MIN_INF_RECT
+		)
+	},
+	transform: function (rect: Rect, matrix: Matrix): Rect {
+		checkRect(rect)
+		checkMatrix(matrix)
+		var t
+
+		if (Rect.isInfinite(rect))
+			return rect
+		if (!Rect.isValid(rect))
+			return rect
+
+		var ax0 = rect[0] * matrix[0]
+		var ax1 = rect[2] * matrix[0]
+		if (ax0 > ax1)
+			t = ax0, ax0 = ax1, ax1 = t
+
+		var cy0 = rect[1] * matrix[2]
+		var cy1 = rect[3] * matrix[2]
+		if (cy0 > cy1)
+			t = cy0, cy0 = cy1, cy1 = t
+
+		ax0 += cy0 + matrix[4]
+		ax1 += cy1 + matrix[4]
+
+		var bx0 = rect[0] * matrix[1]
+		var bx1 = rect[2] * matrix[1]
+		if (bx0 > bx1)
+			t = bx0, bx0 = bx1, bx1 = t
+
+		var dy0 = rect[1] * matrix[3]
+		var dy1 = rect[3] * matrix[3]
+		if (dy0 > dy1)
+			t = dy0, dy0 = dy1, dy1 = t
+
+		bx0 += dy0 + matrix[5]
+		bx1 += dy1 + matrix[5]
+
+		return [ ax0, bx0, ax1, bx1 ]
+	},
+}
+
+export function enableICC() {
+	libmupdf._wasm_enable_icc()
+}
+
+export function disableICC() {
+	libmupdf._wasm_disable_icc()
+}
+
+export function setUserCSS(text: string) {
+	libmupdf._wasm_set_user_css(STRING(text))
+}
+
+export function installLoadFontFunction(f: (name: string, script: string, bold: boolean, italic: boolean) => Font | null) {
+	$libmupdf_load_font_file_js = f
+}
+
+/* -------------------------------------------------------------------------- */
+
+// To pass Rect and Matrix as pointer arguments
+const _wasm_int = Malloc<"int">(4)
+const _wasm_point = Malloc<"fz_point">(4 * 6) >> 2
+const _wasm_rect = Malloc<"fz_rect">(4 * 8) >> 2
+const _wasm_matrix = Malloc<"fz_matrix">(4 * 6) >> 2
+const _wasm_color = Malloc<"number">(4 * 4) >> 2
+const _wasm_quad = Malloc<"fz_quad">(4 * 8) >> 2
+const _wasm_string: [ Pointer<"char">, Pointer<"char"> ] = [ 0 as Pointer<"char">, 0 as Pointer<"char"> ]
+
+function checkType(value: any, type: any) {
+	if (typeof type === "string" && typeof value !== type)
+		throw new TypeError("expected " + type)
+	if (typeof type === "function" && !(value instanceof type))
+		throw new TypeError("expected " + type.name)
+}
+
+function checkPoint(value: any): asserts value is Point {
+	if (!Array.isArray(value) || value.length !== 2)
+		throw new TypeError("expected point")
+}
+
+function checkRect(value: any): asserts value is Rect {
+	if (!Array.isArray(value) || value.length !== 4)
+		throw new TypeError("expected rectangle")
+}
+
+function checkMatrix(value: any): asserts value is Matrix {
+	if (!Array.isArray(value) || value.length !== 6)
+		throw new TypeError("expected matrix")
+}
+
+function checkQuad(value: any): asserts value is Quad {
+	if (!Array.isArray(value) || value.length !== 8)
+		throw new TypeError("expected quad")
+}
+
+function checkColor(value: any): asserts value is Color {
+	if (!Array.isArray(value) || (value.length !== 1 && value.length !== 3 && value.length !== 4))
+		throw new TypeError("expected color array")
+}
+
+function checkAnnotColor(value: any): asserts value is AnnotColor {
+	if (!Array.isArray(value) || (value.length !== 0 && value.length !== 1 && value.length !== 3 && value.length !== 4))
+		throw new TypeError("expected color array")
+}
+
+/** The types that can be automatically converted into a Buffer object */
+type AnyBuffer = Buffer | ArrayBuffer | Uint8Array | string
+
+function BUFFER(input: AnyBuffer) {
+	if (input instanceof Buffer)
+		return input.pointer
+	if (input instanceof ArrayBuffer || input instanceof Uint8Array)
+		return new Buffer(input).pointer
+	if (typeof input === "string")
+		return new Buffer(input).pointer
+	throw new TypeError("expected buffer")
+}
+
+function ENUM<T>(value: T, list: readonly T[]): number {
+	if (typeof value === "number") {
+		if (value >= 0 && value < list.length)
+			return value
+	}
+	if (typeof value === "string") {
+		let idx = list.indexOf(value)
+		if (idx >= 0)
+			return idx
+	}
+	throw new TypeError(`invalid enum value ("${value}"; expected ${list.join(", ")})`)
+}
+
+function allocateUTF8(str: string) {
+	var size = libmupdf.lengthBytesUTF8(str) + 1
+	var pointer = Malloc<"XXX">(size) as unknown as Pointer<"char">
+	libmupdf.stringToUTF8(str, pointer, size)
+	return pointer
+}
+
+function STRING_N(s: string, i: number) {
+	if (_wasm_string[i]) {
+		Free(_wasm_string[i])
+		_wasm_string[i] = 0 as Pointer<"char">
+	}
+	return _wasm_string[i] = allocateUTF8(s)
+}
+
+function STRING(s: string) {
+	return STRING_N(s, 0)
+}
+
+function STRING2(s: string) {
+	return STRING_N(s, 1)
+}
+
+function STRING_OPT(s: string | null | undefined) {
+	return typeof s === "string" ? STRING_N(s, 0) : 0 as Pointer<"char">
+}
+
+function STRING2_OPT(s: string | null | undefined) {
+	return typeof s === "string" ? STRING_N(s, 1) : 0 as Pointer<"char">
+}
+
+function POINT(p: Point) {
+	libmupdf.HEAPF32[_wasm_point + 0] = p[0]
+	libmupdf.HEAPF32[_wasm_point + 1] = p[1]
+	return _wasm_point << 2 as Pointer<"fz_point">
+}
+
+function POINT2(p: Point) {
+	libmupdf.HEAPF32[_wasm_point + 2] = p[0]
+	libmupdf.HEAPF32[_wasm_point + 3] = p[1]
+	return (_wasm_point + 2) << 2 as Pointer<"fz_point">
+}
+
+function POINT3(p: Point) {
+	libmupdf.HEAPF32[_wasm_point + 4] = p[0]
+	libmupdf.HEAPF32[_wasm_point + 5] = p[1]
+	return (_wasm_point + 4) << 2 as Pointer<"fz_point">
+}
+
+function RECT(r: Rect) {
+	libmupdf.HEAPF32[_wasm_rect + 0] = r[0]
+	libmupdf.HEAPF32[_wasm_rect + 1] = r[1]
+	libmupdf.HEAPF32[_wasm_rect + 2] = r[2]
+	libmupdf.HEAPF32[_wasm_rect + 3] = r[3]
+	return _wasm_rect << 2 as Pointer<"fz_rect">
+}
+
+function RECT2(r: Rect) {
+	libmupdf.HEAPF32[_wasm_rect + 4] = r[0]
+	libmupdf.HEAPF32[_wasm_rect + 5] = r[1]
+	libmupdf.HEAPF32[_wasm_rect + 6] = r[2]
+	libmupdf.HEAPF32[_wasm_rect + 7] = r[3]
+	return (_wasm_rect + 4) << 2 as Pointer<"fz_rect">
+}
+
+function MATRIX(m: Matrix) {
+	libmupdf.HEAPF32[_wasm_matrix + 0] = m[0]
+	libmupdf.HEAPF32[_wasm_matrix + 1] = m[1]
+	libmupdf.HEAPF32[_wasm_matrix + 2] = m[2]
+	libmupdf.HEAPF32[_wasm_matrix + 3] = m[3]
+	libmupdf.HEAPF32[_wasm_matrix + 4] = m[4]
+	libmupdf.HEAPF32[_wasm_matrix + 5] = m[5]
+	return _wasm_matrix << 2 as Pointer<"fz_matrix">
+}
+
+function QUAD(q: Quad) {
+	libmupdf.HEAPF32[_wasm_quad + 0] = q[0]
+	libmupdf.HEAPF32[_wasm_quad + 1] = q[1]
+	libmupdf.HEAPF32[_wasm_quad + 2] = q[2]
+	libmupdf.HEAPF32[_wasm_quad + 3] = q[3]
+	libmupdf.HEAPF32[_wasm_quad + 4] = q[4]
+	libmupdf.HEAPF32[_wasm_quad + 5] = q[5]
+	libmupdf.HEAPF32[_wasm_quad + 6] = q[6]
+	libmupdf.HEAPF32[_wasm_quad + 7] = q[7]
+	return _wasm_quad << 2 as Pointer<"fz_quad">
+}
+
+function COLOR(c?: AnnotColor) {
+	if (typeof c !== "undefined") {
+		switch (c.length) {
+		case 0:
+			break
+		case 1:
+			libmupdf.HEAPF32[_wasm_color + 0] = c[0]
+			break
+		case 3:
+			libmupdf.HEAPF32[_wasm_color + 0] = c[0]
+			libmupdf.HEAPF32[_wasm_color + 1] = c[1]
+			libmupdf.HEAPF32[_wasm_color + 2] = c[2]
+			break
+		case 4:
+			libmupdf.HEAPF32[_wasm_color + 0] = c[0]
+			libmupdf.HEAPF32[_wasm_color + 1] = c[1]
+			libmupdf.HEAPF32[_wasm_color + 2] = c[2]
+			libmupdf.HEAPF32[_wasm_color + 3] = c[3]
+			break
+		}
+	}
+	return _wasm_color << 2 as Pointer<"float">
+}
+
+/* -------------------------------------------------------------------------- */
+
+function fromColor(n: number): Color {
+	if (n === 1)
+		return [
+			libmupdf.HEAPF32[_wasm_color] as number
+		]
+	if (n === 3)
+		return [
+			libmupdf.HEAPF32[_wasm_color + 0] as number,
+			libmupdf.HEAPF32[_wasm_color + 1] as number,
+			libmupdf.HEAPF32[_wasm_color + 2] as number,
+		]
+	if (n === 4)
+		return [
+			libmupdf.HEAPF32[_wasm_color + 0] as number,
+			libmupdf.HEAPF32[_wasm_color + 1] as number,
+			libmupdf.HEAPF32[_wasm_color + 2] as number,
+			libmupdf.HEAPF32[_wasm_color + 3] as number,
+		]
+	throw new TypeError("invalid number of components for Color: " + n)
+}
+
+function fromAnnotColor(n: number): AnnotColor {
+	if (n === 0)
+		return []
+	return fromColor(n)
+}
+
+function fromColorArray(n: number, ptr: Pointer<"float">): number[] {
+	let addr = ptr >> 2
+	let color: number[] = []
+	for (let i = 0; i < n; ++i)
+		color.push(libmupdf.HEAPF32[addr + i] as number)
+	return color
+}
+
+function fromStringOrNull(ptr: Pointer<"char">): string | null {
+	if (ptr === 0)
+		return null
+	return libmupdf.UTF8ToString(ptr)
+}
+
+function fromString(ptr: Pointer<"char">): string {
+	return libmupdf.UTF8ToString(ptr)
+}
+
+function fromStringFree(ptr: Pointer<"char">): string {
+	let str = libmupdf.UTF8ToString(ptr)
+	Free(ptr)
+	return str
+}
+
+function fromPoint(ptr: Pointer<"fz_point">): Point {
+	let addr = ptr >> 2
+	return [
+		libmupdf.HEAPF32[addr + 0] as number,
+		libmupdf.HEAPF32[addr + 1] as number,
+	]
+}
+
+function fromRect(ptr: Pointer<"fz_rect">): Rect {
+	let addr = ptr >> 2
+	return [
+		libmupdf.HEAPF32[addr + 0] as number,
+		libmupdf.HEAPF32[addr + 1] as number,
+		libmupdf.HEAPF32[addr + 2] as number,
+		libmupdf.HEAPF32[addr + 3] as number,
+	]
+}
+
+function fromMatrix(ptr: Pointer<"fz_matrix">): Matrix {
+	let addr = ptr >> 2
+	return [
+		libmupdf.HEAPF32[addr + 0] as number,
+		libmupdf.HEAPF32[addr + 1] as number,
+		libmupdf.HEAPF32[addr + 2] as number,
+		libmupdf.HEAPF32[addr + 3] as number,
+		libmupdf.HEAPF32[addr + 4] as number,
+		libmupdf.HEAPF32[addr + 5] as number,
+	]
+}
+
+function fromQuad(ptr: Pointer<"fz_quad">): Quad {
+	let addr = ptr >> 2
+	return [
+		libmupdf.HEAPF32[addr + 0] as number,
+		libmupdf.HEAPF32[addr + 1] as number,
+		libmupdf.HEAPF32[addr + 2] as number,
+		libmupdf.HEAPF32[addr + 3] as number,
+		libmupdf.HEAPF32[addr + 4] as number,
+		libmupdf.HEAPF32[addr + 5] as number,
+		libmupdf.HEAPF32[addr + 6] as number,
+		libmupdf.HEAPF32[addr + 7] as number,
+	]
+}
+
+function fromBuffer(ptr: Pointer<"fz_buffer">): Uint8Array {
+	let data = libmupdf._wasm_buffer_get_data(ptr)
+	let size = libmupdf._wasm_buffer_get_len(ptr)
+	return libmupdf.HEAPU8.slice(data, data + size)
+}
+
+/* unused for now
+function rgbFromColor(c?: Color): [number, number, number] {
+	var r = 0, g = 0, b = 0
+	if (typeof c !== "undefined") {
+		switch (c.length) {
+		case 1:
+			r = g = b = c[0]
+			break
+		case 3:
+			r = c[0]
+			g = c[1]
+			b = c[2]
+			break
+		case 4:
+			r = 1 - Math.min(1, c[0] + c[3])
+			g = 1 - Math.min(1, c[1] + c[3])
+			b = 1 - Math.min(1, c[2] + c[3])
+			break
+		}
+	}
+	return [ r, g, b ]
+}
+
+function numberFromColor(c?: Color): number {
+	var [ r, g, b ] = rgbFromColor(c)
+	return (255 << 24) | (r << 16) | (g << 8) | b
+}
+*/
+
+function colorFromNumber(argb: number): Color {
+	var r = (argb >> 16) & 255
+	var g = (argb >> 8) & 255
+	var b = (argb) & 255
+	return [ r / 255, g / 255, b / 255 ]
+}
+
+/* -------------------------------------------------------------------------- */
+
+type SearchFunction = (
+	display_list: any,
+	needle: Pointer<"char">,
+	marks: Pointer<"int">,
+	hits: Pointer<"fz_quad">,
+	hit_max: number
+) => number
+
+function runSearch(searchFun: SearchFunction, searchThis: number, needle: string, max_hits = 500) {
+	checkType(needle, "string")
+	let hits = 0 as Pointer<"fz_quad">
+	let marks = 0 as Pointer<"int">
+	try {
+		hits = Malloc<"fz_quad">(32 * max_hits)
+		marks = Malloc<"int">(4 * max_hits)
+		let n = searchFun(searchThis as any, STRING(needle), marks, hits, max_hits)
+		let outer: Quad[][] = []
+		if (n > 0) {
+			let inner: Quad[] = []
+			for (let i = 0; i < n; ++i) {
+				let mark = libmupdf.HEAP32[(marks>>2) + i]
+				let quad = fromQuad(hits + i * 32 as Pointer<"fz_quad">)
+				if (i > 0 && mark) {
+					outer.push(inner)
+					inner = []
+				}
+				inner.push(quad)
+			}
+			outer.push(inner)
+		}
+		return outer
+	} finally {
+		Free(marks)
+		Free(hits)
+	}
+}
+
+/* -------------------------------------------------------------------------- */
+
+abstract class Userdata<B> {
+	private static _finalizer: FinalizationRegistry<number>
+
+	static readonly _drop: (pointer: any) => void
+
+	pointer: Pointer<B>
+
+	constructor(pointer: Pointer<B>) {
+		if (typeof pointer !== "number")
+			throw new Error("invalid pointer: " + typeof pointer)
+		if (pointer !== 0) {
+			let ctor = this.constructor as typeof Userdata
+			if (!ctor._finalizer)
+				ctor._finalizer = new FinalizationRegistry(ctor._drop)
+			ctor._finalizer.register(this, pointer, this)
+		}
+		this.pointer = pointer
+	}
+
+	destroy() {
+		if (this.pointer !== 0) {
+			let ctor = this.constructor as typeof Userdata
+			ctor._finalizer.unregister(this)
+			ctor._drop(this.pointer)
+		}
+		this.pointer = 0 as Pointer<B>
+	}
+
+	// Custom "console.log" formatting for Node
+	[Symbol.for("nodejs.util.inspect.custom")]() {
+		return this.toString()
+	}
+
+	toString() {
+		return `[${this.constructor.name} ${this.pointer}]`
+	}
+
+	valueOf() {
+		throw new Error("cannot convert Userdata to Javascript value")
+	}
+}
+
+export class Buffer extends Userdata<"fz_buffer"> {
+	static override readonly _drop = libmupdf._wasm_drop_buffer
+
+	/** New empty Buffer. */
+	constructor()
+
+	/** New Buffer initialized with string contents as UTF-8. */
+	constructor(data: string)
+
+	/** New Buffer initialized with typed array contents. */
+	constructor(data: ArrayBuffer | Uint8Array)
+
+	/** PRIVATE */
+	constructor(pointer: Pointer<"fz_buffer">)
+
+	constructor(arg?: Pointer<"fz_buffer"> | string | ArrayBuffer | Uint8Array) {
+		if (typeof arg === "undefined")
+			super(libmupdf._wasm_new_buffer(1024))
+
+		else if (typeof arg === "number")
+			super(arg)
+
+		else if (typeof arg === "string") {
+			let data_len = libmupdf.lengthBytesUTF8(arg)
+			let data_ptr = Malloc<"char">(data_len + 1)
+			libmupdf.stringToUTF8(arg, data_ptr, data_len + 1)
+			super(libmupdf._wasm_new_buffer_from_data(data_ptr, data_len))
+		}
+
+		else if (arg instanceof ArrayBuffer || arg instanceof Uint8Array) {
+			let data_len = arg.byteLength
+			let data_ptr = Malloc<"char">(data_len)
+			libmupdf.HEAPU8.set(new Uint8Array(arg), data_ptr)
+			super(libmupdf._wasm_new_buffer_from_data(data_ptr, data_len))
+		}
+	}
+
+	get length() {
+		return this.getLength()
+	}
+
+	set length(_: number) {
+		throw new TypeError("buffer length is read-only")
+	}
+
+	getLength() {
+		return libmupdf._wasm_buffer_get_len(this.pointer)
+	}
+
+	readByte(at: number) {
+		let data = libmupdf._wasm_buffer_get_data(this.pointer)
+		return libmupdf.HEAPU8[data + at] as number
+	}
+
+	write(s: string) {
+		libmupdf._wasm_append_string(this.pointer, STRING(s))
+	}
+
+	writeByte(b: number) {
+		libmupdf._wasm_append_byte(this.pointer, b)
+	}
+
+	writeLine(s: string) {
+		this.write(s)
+		this.writeByte(10)
+	}
+
+	writeBuffer(other: AnyBuffer) {
+		libmupdf._wasm_append_buffer(this.pointer, BUFFER(other))
+	}
+
+	asUint8Array() {
+		let data = libmupdf._wasm_buffer_get_data(this.pointer)
+		let size = libmupdf._wasm_buffer_get_len(this.pointer)
+		return libmupdf.HEAPU8.subarray(data, data + size)
+	}
+
+	slice(start: number, end?: number) {
+		if (typeof end === "undefined")
+			end = this.getLength()
+		return new Buffer(libmupdf._wasm_slice_buffer(this.pointer, start, end))
+	}
+
+	asString() {
+		return fromString(libmupdf._wasm_string_from_buffer(this.pointer))
+	}
+
+	save(filename: string) {
+		if (node_fs)
+			node_fs.writeFileSync(filename, this.asUint8Array())
+		else
+			throw new Error("missing 'fs' module")
+	}
+}
+
+export type ColorSpaceType =
+	"None" |
+	"Gray" |
+	"RGB" |
+	"BGR" |
+	"CMYK" |
+	"Lab" |
+	"Indexed" |
+	"Separation"
+
+export class ColorSpace extends Userdata<"fz_colorspace"> {
+	static override readonly _drop = libmupdf._wasm_drop_colorspace
+
+	static readonly COLORSPACE_TYPES: ColorSpaceType[] = [
+		"None",
+		"Gray",
+		"RGB",
+		"BGR",
+		"CMYK",
+		"Lab",
+		"Indexed",
+		"Separation"
+	]
+
+	// Create ColorSpace from ICC profile.
+	constructor(profile: AnyBuffer, name: string)
+
+	// PRIVATE
+	constructor(pointer: Pointer<"fz_colorspace">)
+
+	constructor(from: Pointer<"fz_colorspace"> | AnyBuffer, name?: string) {
+		if (typeof from === "number") {
+			super(from)
+		} else {
+			if (typeof from === "string") {
+				if (node_fs)
+					from = node_fs.readFileSync(from)
+				else
+					throw new Error("missing 'fs' module")
+			}
+			super(libmupdf._wasm_new_icc_colorspace(STRING_OPT(name), BUFFER(from as AnyBuffer)))
+		}
+	}
+
+	getName() {
+		return fromString(libmupdf._wasm_colorspace_get_name(this.pointer))
+	}
+
+	getType() {
+		return ColorSpace.COLORSPACE_TYPES[libmupdf._wasm_colorspace_get_type(this.pointer)] || "None"
+	}
+
+	getNumberOfComponents() {
+		return libmupdf._wasm_colorspace_get_n(this.pointer)
+	}
+
+	isGray(): boolean { return this.getType() === "Gray" }
+	isRGB(): boolean { return this.getType() === "RGB" }
+	isCMYK(): boolean { return this.getType() === "CMYK" }
+	isIndexed(): boolean { return this.getType() === "Indexed" }
+	isLab(): boolean { return this.getType() === "Lab" }
+	isDeviceN(): boolean { return this.getType() === "Separation" }
+	isSubtractive(): boolean { return this.getType() === "CMYK" || this.getType() === "Separation" }
+
+	override toString() {
+		return "[ColorSpace " + this.getName() + "]"
+	}
+
+	static readonly DeviceGray = new ColorSpace(libmupdf._wasm_device_gray())
+	static readonly DeviceRGB = new ColorSpace(libmupdf._wasm_device_rgb())
+	static readonly DeviceBGR = new ColorSpace(libmupdf._wasm_device_bgr())
+	static readonly DeviceCMYK = new ColorSpace(libmupdf._wasm_device_cmyk())
+	static readonly Lab = new ColorSpace(libmupdf._wasm_device_lab())
+}
+
+export type FontSimpleEncoding = "Latin" | "Greek" | "Cyrillic"
+
+export type FontCJKOrdering = 0 | 1 | 2 | 3
+
+export type FontCJKLanguage =
+	"Adobe-CNS1" |
+	"Adobe-GB1" |
+	"Adobe-Japan1" |
+	"Adobe-Korea1" |
+	"zh-Hant" |
+	"zh-TW" |
+	"zh-HK" |
+	"zh-Hans" |
+	"zh-CN" |
+	"ja" |
+	"ko"
+
+export class Font extends Userdata<"fz_font"> {
+	static override readonly _drop = libmupdf._wasm_drop_font
+
+	static readonly SIMPLE_ENCODING: FontSimpleEncoding[] = [
+		"Latin",
+		"Greek",
+		"Cyrillic"
+	]
+
+	static readonly ADOBE_CNS = 0
+	static readonly ADOBE_GB = 1
+	static readonly ADOBE_JAPAN = 2
+	static readonly ADOBE_KOREA = 3
+
+	static readonly CJK_ORDERING_BY_LANG: Record<FontCJKLanguage,FontCJKOrdering> = {
+		"Adobe-CNS1": 0,
+		"Adobe-GB1": 1,
+		"Adobe-Japan1": 2,
+		"Adobe-Korea1": 3,
+		"zh-Hant": 0,
+		"zh-TW": 0,
+		"zh-HK": 0,
+		"zh-Hans": 1,
+		"zh-CN": 1,
+		"ja": 2,
+		"ko": 3,
+	}
+
+	constructor(name: string)
+	constructor(name: string, filename: string, subfont?: number)
+	constructor(name: string, data: AnyBuffer, subfont?: number)
+	constructor(pointer: Pointer<"fz_font">)
+
+	constructor(name_or_pointer: Pointer<"fz_font"> | string, data?: AnyBuffer, subfont=0) {
+		let pointer = 0 as Pointer<"fz_font">
+		if (typeof name_or_pointer === "number") {
+			pointer = libmupdf._wasm_keep_font(name_or_pointer)
+		} else {
+			if (typeof data === "string") {
+				if (node_fs)
+					data = node_fs.readFileSync(data)
+				else
+					throw new Error("missing 'fs' module")
+			}
+			if (data)
+				pointer = libmupdf._wasm_new_font_from_buffer(STRING(name_or_pointer), BUFFER(data), subfont)
+			else if (name_or_pointer === "zh-Hant")
+				pointer = libmupdf._wasm_new_cjk_font(Font.ADOBE_CNS)
+			else if (name_or_pointer === "zh-Hans")
+				pointer = libmupdf._wasm_new_cjk_font(Font.ADOBE_GB)
+			else if (name_or_pointer === "ja")
+				pointer = libmupdf._wasm_new_cjk_font(Font.ADOBE_JAPAN)
+			else if (name_or_pointer === "ko")
+				pointer = libmupdf._wasm_new_cjk_font(Font.ADOBE_KOREA)
+			else
+				pointer = libmupdf._wasm_new_base14_font(STRING(name_or_pointer))
+		}
+		super(pointer)
+	}
+
+	getName() {
+		return fromString(libmupdf._wasm_font_get_name(this.pointer))
+	}
+
+	encodeCharacter(uni: number | string) {
+		if (typeof uni === "string")
+			uni = uni.charCodeAt(0)
+		return libmupdf._wasm_encode_character(this.pointer, uni)
+	}
+
+	advanceGlyph(gid: number, wmode = 0) {
+		return libmupdf._wasm_advance_glyph(this.pointer, gid, wmode)
+	}
+
+	isMono() {
+		return !!libmupdf._wasm_font_is_monospaced(this.pointer)
+	}
+
+	isSerif() {
+		return !!libmupdf._wasm_font_is_serif(this.pointer)
+	}
+
+	isBold() {
+		return !!libmupdf._wasm_font_is_bold(this.pointer)
+	}
+
+	isItalic() {
+		return !!libmupdf._wasm_font_is_italic(this.pointer)
+	}
+}
+
+export class Image extends Userdata<"fz_image"> {
+	static override readonly _drop = libmupdf._wasm_drop_image
+
+	constructor(pointer: Pointer<"fz_image">)
+	constructor(filename: string)
+	constructor(data: AnyBuffer)
+	constructor(pixmap: Pixmap, mask?: Image)
+
+	constructor(data: Pointer<"fz_image"> | Pixmap | AnyBuffer, mask?: Image) {
+		let pointer = 0 as Pointer<"fz_image">
+		if (typeof data === "number") {
+			pointer = libmupdf._wasm_keep_image(data)
+		} else if (data instanceof Pixmap) {
+			pointer = libmupdf._wasm_new_image_from_pixmap(
+				data.pointer,
+				mask ? mask.pointer : 0 as Pointer<"fz_image">
+			)
+		} else {
+			if (typeof data === "string") {
+				if (node_fs)
+					data = node_fs.readFileSync(data)
+				else
+					throw new Error("missing 'fs' module")
+			}
+			pointer = libmupdf._wasm_new_image_from_buffer(BUFFER(data as Uint8Array))
+		}
+		super(pointer)
+	}
+
+	getWidth() {
+		return libmupdf._wasm_image_get_w(this.pointer)
+	}
+
+	getHeight() {
+		return libmupdf._wasm_image_get_h(this.pointer)
+	}
+
+	getNumberOfComponents() {
+		return libmupdf._wasm_image_get_n(this.pointer)
+	}
+
+	getBitsPerComponent() {
+		return libmupdf._wasm_image_get_bpc(this.pointer)
+	}
+
+	getXResolution() {
+		return libmupdf._wasm_image_get_xres(this.pointer)
+	}
+
+	getYResolution() {
+		return libmupdf._wasm_image_get_yres(this.pointer)
+	}
+
+	getImageMask() {
+		return !!libmupdf._wasm_image_get_imagemask(this.pointer)
+	}
+
+	getColorSpace() {
+		let cs = libmupdf._wasm_image_get_colorspace(this.pointer)
+		if (cs)
+			return new ColorSpace(libmupdf._wasm_keep_colorspace(cs))
+		return null
+	}
+
+	getMask() {
+		let mask = libmupdf._wasm_image_get_mask(this.pointer)
+		if (mask)
+			return new Image(libmupdf._wasm_keep_image(mask))
+		return null
+	}
+
+	toPixmap() {
+		return new Pixmap(libmupdf._wasm_get_pixmap_from_image(this.pointer))
+	}
+}
+
+export type LineCap = "Butt" | "Round" | "Square" | "Triangle"
+export type LineJoin = "Miter" | "Round" | "Bevel" | "MiterXPS"
+
+// TODO: convert StrokeState from plain JS object to match mutool run ffi_pushstroke/ffi_tostroke
+
+export type StrokeStateData  = {
+	lineCap: LineCap,
+	lineJoin: LineJoin,
+	lineWidth: number,
+	miterLimit: number,
+	dashPhase?: number,
+	dashes?: number[],
+}
+
+export class StrokeState extends Userdata<"fz_stroke_state"> {
+	static override readonly _drop = libmupdf._wasm_drop_stroke_state
+
+	static readonly LINE_CAP: LineCap[] = [
+		"Butt",
+		"Round",
+		"Square",
+		"Triangle"
+	]
+
+	static readonly LINE_JOIN: LineJoin[] = [
+		"Miter",
+		"Round",
+		"Bevel",
+		"MiterXPS"
+	]
+
+	constructor(data: Pointer<"fz_stroke_state"> | StrokeStateData) {
+		if (typeof data === "number") {
+			super(data)
+			return this
+		}
+		super(libmupdf._wasm_new_stroke_state(data?.dashes?.length ?? 0))
+		let lineCap = ENUM<LineCap>(data.lineCap, StrokeState.LINE_CAP)
+		let lineJoin = ENUM<LineJoin>(data.lineJoin, StrokeState.LINE_JOIN)
+		libmupdf._wasm_stroke_state_set_start_cap(this.pointer, lineCap)
+		libmupdf._wasm_stroke_state_set_dash_cap(this.pointer, lineCap)
+		libmupdf._wasm_stroke_state_set_end_cap(this.pointer, lineCap)
+		libmupdf._wasm_stroke_state_set_linejoin(this.pointer, lineJoin)
+		libmupdf._wasm_stroke_state_set_linewidth(this.pointer, data.lineWidth)
+		libmupdf._wasm_stroke_state_set_miterlimit(this.pointer, data.miterLimit)
+		libmupdf._wasm_stroke_state_set_dash_phase(this.pointer, data.dashPhase ?? 0)
+		if (data.dashes) {
+			for (let i = 0; i < data.dashes.length; ++i)
+				libmupdf._wasm_stroke_state_set_dash_item(this.pointer, i, data.dashes[i] ?? 0)
+		}
+	}
+
+	getLineCap() {
+		return libmupdf._wasm_stroke_state_get_start_cap(this.pointer)
+	}
+
+	getLineJoin() {
+		return libmupdf._wasm_stroke_state_get_linejoin(this.pointer)
+	}
+
+	getLineWidth() {
+		return libmupdf._wasm_stroke_state_get_linewidth(this.pointer)
+	}
+
+	getMiterLimit() {
+		return libmupdf._wasm_stroke_state_get_miterlimit(this.pointer)
+	}
+
+	getDashPhase() {
+		return libmupdf._wasm_stroke_state_get_dash_phase(this.pointer)
+	}
+
+	getDashes(): number[] | null {
+		var n = libmupdf._wasm_stroke_state_get_dash_len(this.pointer)
+		if (n > 0) {
+			var out: number[] = []
+			for (let i = 0; i < n; ++i)
+				out[i] = libmupdf._wasm_stroke_state_get_dash_item(this.pointer, i)
+			return out
+		}
+		return null
+	}
+}
+
+interface PathWalker {
+	moveTo?(x: number, y: number): void
+	lineTo?(x: number, y: number): void
+	curveTo?(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void
+	closePath?(): void
+}
+
+export class Path extends Userdata<"fz_path"> {
+	static override readonly _drop = libmupdf._wasm_drop_path
+
+	constructor(pointer?: Pointer<"fz_path">) {
+		if (typeof pointer === "number")
+			super(pointer)
+		else
+			super(libmupdf._wasm_new_path())
+	}
+
+	getBounds(strokeState: StrokeState, transform: Matrix) {
+		if (strokeState !== null)
+			checkType(strokeState, StrokeState)
+		checkMatrix(transform)
+		return fromRect(libmupdf._wasm_bound_path(this.pointer, strokeState?.pointer, MATRIX(transform)))
+	}
+
+	moveTo(x: number, y: number) {
+		checkType(x, "number")
+		checkType(y, "number")
+		libmupdf._wasm_moveto(this.pointer, x, y)
+	}
+
+	lineTo(x: number, y: number) {
+		checkType(x, "number")
+		checkType(y, "number")
+		libmupdf._wasm_lineto(this.pointer, x, y)
+	}
+
+	curveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) {
+		checkType(x1, "number")
+		checkType(y1, "number")
+		checkType(x2, "number")
+		checkType(y2, "number")
+		checkType(x3, "number")
+		checkType(y3, "number")
+		libmupdf._wasm_curveto(this.pointer, x1, y1, x2, y2, x3, y3)
+	}
+
+	curveToV(cx: number, cy: number, ex: number, ey: number) {
+		checkType(cx, "number")
+		checkType(cy, "number")
+		checkType(ex, "number")
+		checkType(ey, "number")
+		libmupdf._wasm_curvetov(this.pointer, cx, cy, ex, ey)
+	}
+
+	curveToY(cx: number, cy: number, ex: number, ey: number) {
+		checkType(cx, "number")
+		checkType(cy, "number")
+		checkType(ex, "number")
+		checkType(ey, "number")
+		libmupdf._wasm_curvetoy(this.pointer, cx, cy, ex, ey)
+	}
+
+	closePath() {
+		libmupdf._wasm_closepath(this.pointer)
+	}
+
+	rect(x1: number, y1: number, x2: number, y2: number) {
+		checkType(x1, "number")
+		checkType(y1, "number")
+		checkType(x2, "number")
+		checkType(y2, "number")
+		libmupdf._wasm_rectto(this.pointer, x1, y1, x2, y2)
+	}
+
+	transform(matrix: Matrix) {
+		checkMatrix(matrix)
+		libmupdf._wasm_transform_path(this.pointer, MATRIX(matrix))
+	}
+
+	walk(walker: PathWalker) {
+		let id = $libmupdf_path_id++
+		$libmupdf_path_table.set(id, walker)
+		libmupdf._wasm_walk_path(this.pointer, id)
+		$libmupdf_path_table.delete(id)
+	}
+}
+
+interface TextWalker {
+	beginSpan?(font: Font, trm: Matrix, wmode: number, bidi: number, markupDirection: number, language: string): void
+	showGlyph?(font: Font, trm: Matrix, glyph: number, unicode: number, wmode: number, bidi: number): void
+	endSpan?(): void
+}
+
+export class Text extends Userdata<"fz_text"> {
+	static override readonly _drop = libmupdf._wasm_drop_text
+
+	constructor(pointer?: Pointer<"fz_text">) {
+		if (typeof pointer === "number")
+			super(pointer)
+		else
+			super(libmupdf._wasm_new_text())
+	}
+
+	getBounds(strokeState: StrokeState, transform: Matrix) {
+		if (strokeState !== null)
+			checkType(strokeState, StrokeState)
+		checkMatrix(transform)
+		return fromRect(libmupdf._wasm_bound_text(this.pointer, strokeState?.pointer, MATRIX(transform)))
+	}
+
+	showGlyph(font: Font, trm: Matrix, gid: number, uni: number, wmode = 0) {
+		checkType(font, Font)
+		checkMatrix(trm)
+		checkType(gid, "number")
+		checkType(uni, "number")
+		libmupdf._wasm_show_glyph(
+			this.pointer,
+			font.pointer,
+			MATRIX(trm),
+			gid,
+			uni,
+			wmode
+		)
+	}
+
+	showString(font: Font, trm: Matrix, str: string, wmode = 0) {
+		checkType(font, Font)
+		checkMatrix(trm)
+		checkType(str, "string")
+		return fromMatrix(
+			libmupdf._wasm_show_string(
+				this.pointer,
+				font.pointer,
+				MATRIX(trm),
+				STRING(str),
+				wmode
+			)
+		)
+	}
+
+	walk(walker: TextWalker) {
+		let id = $libmupdf_text_id++
+		$libmupdf_text_table.set(id, walker)
+		libmupdf._wasm_walk_text(this.pointer, id)
+		$libmupdf_text_table.delete(id)
+	}
+}
+
+export class DisplayList extends Userdata<"fz_display_list"> {
+	static override readonly _drop = libmupdf._wasm_drop_display_list
+
+	constructor(pointer: Pointer<"fz_display_list">)
+	constructor(mediabox: Rect)
+
+	constructor(arg1: Pointer<"fz_display_list"> | Rect) {
+		let pointer = 0 as Pointer<"fz_display_list">
+		if (typeof arg1 === "number") {
+			pointer = arg1
+		} else {
+			checkRect(arg1)
+			pointer = libmupdf._wasm_new_display_list(RECT(arg1))
+		}
+		super(pointer)
+	}
+
+	getBounds() {
+		return fromRect(libmupdf._wasm_bound_display_list(this.pointer))
+	}
+
+	toPixmap(matrix: Matrix, colorspace: ColorSpace, alpha = false) {
+		checkMatrix(matrix)
+		checkType(colorspace, ColorSpace)
+		return new Pixmap(
+			libmupdf._wasm_new_pixmap_from_display_list(
+				this.pointer,
+				MATRIX(matrix),
+				colorspace.pointer,
+				alpha
+			)
+		)
+	}
+
+	toStructuredText(options = "") {
+		checkType(options, "string")
+		return new StructuredText(libmupdf._wasm_new_stext_page_from_display_list(this.pointer, STRING(options)))
+	}
+
+	run(device: Device, matrix: Matrix) {
+		checkType(device, Device)
+		checkMatrix(matrix)
+		libmupdf._wasm_run_display_list(this.pointer, device.pointer, MATRIX(matrix))
+	}
+
+	search(needle: string, max_hits = 500) {
+		return runSearch(libmupdf._wasm_search_display_list, this.pointer, needle, max_hits)
+	}
+}
+
+export class Pixmap extends Userdata<"fz_pixmap"> {
+	static override readonly _drop = libmupdf._wasm_drop_pixmap
+
+	constructor(pointer: Pointer<"fz_pixmap">)
+	constructor(colorspace: ColorSpace, bbox: Rect, alpha: boolean)
+
+	constructor(arg1: Pointer<"fz_pixmap"> | ColorSpace, bbox?: Rect, alpha = false) {
+		if (typeof arg1 === "number") {
+			super(arg1)
+		}
+		if (arg1 instanceof ColorSpace) {
+			checkRect(bbox)
+			super(libmupdf._wasm_new_pixmap_with_bbox(arg1.pointer, RECT(bbox), alpha))
+		}
+		if (arg1 === null) {
+			checkRect(bbox)
+			super(libmupdf._wasm_new_pixmap_with_bbox(0 as Pointer<"fz_colorspace">, RECT(bbox), alpha))
+		}
+	}
+
+	getBounds() {
+		let x = libmupdf._wasm_pixmap_get_x(this.pointer)
+		let y = libmupdf._wasm_pixmap_get_y(this.pointer)
+		let w = libmupdf._wasm_pixmap_get_w(this.pointer)
+		let h = libmupdf._wasm_pixmap_get_h(this.pointer)
+		return [ x, y, x + w, y + h ]
+	}
+
+	clear(value?: number) {
+		if (typeof value === "undefined")
+			libmupdf._wasm_clear_pixmap(this.pointer)
+		else
+			libmupdf._wasm_clear_pixmap_with_value(this.pointer, value)
+	}
+
+	getWidth() {
+		return libmupdf._wasm_pixmap_get_w(this.pointer)
+	}
+	getHeight() {
+		return libmupdf._wasm_pixmap_get_h(this.pointer)
+	}
+	getX() {
+		return libmupdf._wasm_pixmap_get_x(this.pointer)
+	}
+	getY() {
+		return libmupdf._wasm_pixmap_get_y(this.pointer)
+	}
+	getStride() {
+		return libmupdf._wasm_pixmap_get_stride(this.pointer)
+	}
+	getNumberOfComponents() {
+		return libmupdf._wasm_pixmap_get_n(this.pointer)
+	}
+	getAlpha() {
+		return libmupdf._wasm_pixmap_get_alpha(this.pointer)
+	}
+	getXResolution() {
+		return libmupdf._wasm_pixmap_get_xres(this.pointer)
+	}
+	getYResolution() {
+		return libmupdf._wasm_pixmap_get_yres(this.pointer)
+	}
+
+	setResolution(x: number, y: number) {
+		libmupdf._wasm_pixmap_set_xres(this.pointer, x)
+		libmupdf._wasm_pixmap_set_yres(this.pointer, y)
+	}
+
+	getColorSpace() {
+		let cs = libmupdf._wasm_pixmap_get_colorspace(this.pointer)
+		if (cs)
+			return new ColorSpace(libmupdf._wasm_keep_colorspace(cs))
+		return null
+	}
+
+	getPixels() {
+		let s = libmupdf._wasm_pixmap_get_stride(this.pointer)
+		let h = libmupdf._wasm_pixmap_get_h(this.pointer)
+		let p = libmupdf._wasm_pixmap_get_samples(this.pointer)
+		return new Uint8ClampedArray(libmupdf.HEAPU8.buffer, p, s * h)
+	}
+
+	asPNG() {
+		let buf = libmupdf._wasm_new_buffer_from_pixmap_as_png(this.pointer)
+		try {
+			return fromBuffer(buf)
+		} finally {
+			libmupdf._wasm_drop_buffer(buf)
+		}
+	}
+
+	asPSD() {
+		let buf = libmupdf._wasm_new_buffer_from_pixmap_as_psd(this.pointer)
+		try {
+			return fromBuffer(buf)
+		} finally {
+			libmupdf._wasm_drop_buffer(buf)
+		}
+	}
+
+	asPAM() {
+		let buf = libmupdf._wasm_new_buffer_from_pixmap_as_pam(this.pointer)
+		try {
+			return fromBuffer(buf)
+		} finally {
+			libmupdf._wasm_drop_buffer(buf)
+		}
+	}
+
+	asJPEG(quality: number, invert_cmyk=false) {
+		let buf = libmupdf._wasm_new_buffer_from_pixmap_as_jpeg(this.pointer, quality, invert_cmyk)
+		try {
+			return fromBuffer(buf)
+		} finally {
+			libmupdf._wasm_drop_buffer(buf)
+		}
+	}
+
+	invert() {
+		libmupdf._wasm_invert_pixmap(this.pointer)
+	}
+
+	invertLuminance() {
+		libmupdf._wasm_invert_pixmap_luminance(this.pointer)
+	}
+
+	gamma(p: number) {
+		libmupdf._wasm_gamma_pixmap(this.pointer, p)
+	}
+
+	tint(black: number | Color, white: number | Color) {
+		let black_hex = 0x000000
+		let white_hex = 0xffffff
+		if (typeof black === "number")
+			black_hex = black
+		else if (black instanceof Array && black.length === 3)
+			black_hex = ( ( (black[0] * 255) << 16 ) | ( (black[1] * 255) << 8 ) | ( (black[2] * 255) ) )
+		if (typeof white === "number")
+			white_hex = white
+		else if (white instanceof Array && white.length === 3)
+			white = ( ( (white[0] * 255) << 16 ) | ( (white[1] * 255) << 8 ) | ( (white[2] * 255) ) )
+		libmupdf._wasm_tint_pixmap(this.pointer, black_hex, white_hex)
+	}
+
+	convertToColorSpace(colorspace: ColorSpace, keepAlpha=false) {
+		checkType(colorspace, ColorSpace)
+		checkType(keepAlpha, "boolean")
+		return new Pixmap(libmupdf._wasm_convert_pixmap(this.pointer, colorspace.pointer, keepAlpha))
+	}
+
+	warp(points: Point[], width: number, height: number) {
+		let quad = points.flat()
+		checkQuad(quad)
+		checkType(width, "number")
+		checkType(height, "number")
+		return new Pixmap(libmupdf._wasm_warp_pixmap(this.pointer, QUAD(quad), width, height))
+	}
+}
+
+export class Shade extends Userdata<"fz_shade"> {
+	static override readonly _drop = libmupdf._wasm_drop_shade
+	getBounds() {
+		return fromRect(libmupdf._wasm_bound_shade(this.pointer))
+	}
+}
+
+interface StructuredTextWalker {
+	onImageBlock?(bbox: Rect, transform: Matrix, image: Image): void
+	beginTextBlock?(bbox: Rect): void
+	beginLine?(bbox: Rect, wmode: number, direction: Point): void
+	onChar?(c: string, origin: Point, font: Font, size: number, quad: Quad, color: Color): void
+	endLine?(): void
+	endTextBlock?(): void
+}
+
+type SelectMode = "chars" | "words" | "lines"
+
+export class StructuredText extends Userdata<"fz_stext_page"> {
+	static override readonly _drop = libmupdf._wasm_drop_stext_page
+
+	static readonly SELECT_MODE: SelectMode[] = [
+		"chars",
+		"words",
+		"lines"
+	]
+
+	static readonly SELECT_CHARS = "chars"
+	static readonly SELECT_WORDS = "words"
+	static readonly SELECT_LINES = "lines"
+
+	walk(walker: StructuredTextWalker) {
+		let block = libmupdf._wasm_stext_page_get_first_block(this.pointer)
+		while (block) {
+			let block_type = libmupdf._wasm_stext_block_get_type(block)
+			let block_bbox = fromRect(libmupdf._wasm_stext_block_get_bbox(block))
+
+			if (block_type === 1) {
+				if (walker.onImageBlock) {
+					let matrix = fromMatrix(libmupdf._wasm_stext_block_get_transform(block))
+					let image = new Image(libmupdf._wasm_stext_block_get_image(block))
+					walker.onImageBlock(block_bbox, matrix, image)
+				}
+			} else {
+				if (walker.beginTextBlock)
+					walker.beginTextBlock(block_bbox)
+
+				let line = libmupdf._wasm_stext_block_get_first_line(block)
+				while (line) {
+					let line_bbox = fromRect(libmupdf._wasm_stext_line_get_bbox(line))
+					let line_wmode = libmupdf._wasm_stext_line_get_wmode(line)
+					let line_dir = fromPoint(libmupdf._wasm_stext_line_get_dir(line))
+
+					if (walker.beginLine)
+						walker.beginLine(line_bbox, line_wmode, line_dir)
+
+					if (walker.onChar) {
+						let ch = libmupdf._wasm_stext_line_get_first_char(line)
+						while (ch) {
+							let ch_rune = String.fromCharCode(libmupdf._wasm_stext_char_get_c(ch))
+							let ch_origin = fromPoint(libmupdf._wasm_stext_char_get_origin(ch))
+							let ch_font = new Font(libmupdf._wasm_stext_char_get_font(ch))
+							let ch_size = libmupdf._wasm_stext_char_get_size(ch)
+							let ch_quad = fromQuad(libmupdf._wasm_stext_char_get_quad(ch))
+							let ch_color = colorFromNumber(libmupdf._wasm_stext_char_get_argb(ch))
+
+							walker.onChar(ch_rune, ch_origin, ch_font, ch_size, ch_quad, ch_color)
+
+							ch = libmupdf._wasm_stext_char_get_next(ch)
+						}
+					}
+
+					if (walker.endLine)
+						walker.endLine()
+
+					line = libmupdf._wasm_stext_line_get_next(line)
+				}
+
+				if (walker.endTextBlock)
+					walker.endTextBlock()
+			}
+
+			block = libmupdf._wasm_stext_block_get_next(block)
+		}
+	}
+
+	asJSON(scale = 1) {
+		return fromStringFree(libmupdf._wasm_print_stext_page_as_json(this.pointer, scale))
+	}
+
+	asHTML(id: number) {
+		return fromStringFree(libmupdf._wasm_print_stext_page_as_html(this.pointer, id))
+	}
+
+	asText() {
+		return fromStringFree(libmupdf._wasm_print_stext_page_as_text(this.pointer))
+	}
+
+	snap(p: Point, q: Point, mode: SelectMode): Quad {
+		let mm = ENUM<SelectMode>(mode, StructuredText.SELECT_MODE)
+		return fromQuad(libmupdf._wasm_snap_selection(this.pointer, POINT(p), POINT2(q), mm))
+	}
+
+	copy(p: Point, q: Point): string {
+		return fromStringFree(libmupdf._wasm_copy_selection(this.pointer, POINT(p), POINT2(q)))
+	}
+
+	highlight(p: Point, q: Point, max_hits = 100): Quad[] {
+		let hits = 0 as Pointer<"fz_quad">
+		let result: Quad[] = []
+		try {
+			hits = Malloc<"fz_quad">(32 * max_hits)
+			let n = libmupdf._wasm_highlight_selection(this.pointer, POINT(p), POINT2(q), hits, max_hits)
+			for (let i = 0; i < n; ++i)
+				result.push(fromQuad(hits + i * 32 as Pointer<"fz_quad">))
+		} finally {
+			Free(hits)
+		}
+		return result
+	}
+
+	search(needle: string, max_hits = 500) {
+		return runSearch(libmupdf._wasm_search_stext_page, this.pointer, needle, max_hits)
+	}
+}
+
+export type BlendMode =
+	"Normal" |
+	"Multiply" |
+	"Screen" |
+	"Overlay" |
+	"Darken" |
+	"Lighten" |
+	"ColorDodge" |
+	"ColorBurn" |
+	"HardLight" |
+	"SoftLight" |
+	"Difference" |
+	"Exclusion" |
+	"Hue" |
+	"Saturation" |
+	"Color" |
+	"Luminosity"
+
+export class Device extends Userdata<"fz_device"> {
+	static override readonly _drop = libmupdf._wasm_drop_device
+
+	static readonly BLEND_MODES: BlendMode[] = [
+		"Normal",
+		"Multiply",
+		"Screen",
+		"Overlay",
+		"Darken",
+		"Lighten",
+		"ColorDodge",
+		"ColorBurn",
+		"HardLight",
+		"SoftLight",
+		"Difference",
+		"Exclusion",
+		"Hue",
+		"Saturation",
+		"Color",
+		"Luminosity",
+	]
+
+	constructor(pointer: Pointer<"fz_device">)
+	constructor(callbacks: DeviceFunctions)
+
+	constructor(pointer_or_callbacks: Pointer<"fz_device"> | DeviceFunctions) {
+		if (typeof pointer_or_callbacks === "number")
+			super(pointer_or_callbacks)
+		else {
+			let id = $libmupdf_device_id++
+			$libmupdf_device_table.set(id, pointer_or_callbacks)
+			super(libmupdf._wasm_new_js_device(id))
+		}
+	}
+
+	fillPath(path: Path, evenOdd: boolean, ctm: Matrix, colorspace: ColorSpace, color: Color, alpha: number) {
+		checkType(path, Path)
+		checkMatrix(ctm)
+		checkType(colorspace, ColorSpace)
+		checkColor(color)
+		libmupdf._wasm_fill_path(this.pointer, path.pointer, evenOdd, MATRIX(ctm), colorspace.pointer, COLOR(color), alpha)
+	}
+
+	strokePath(path: Path, stroke: StrokeState, ctm: Matrix, colorspace: ColorSpace, color: Color, alpha: number) {
+		checkType(path, Path)
+		checkType(stroke, StrokeState)
+		checkMatrix(ctm)
+		checkType(colorspace, ColorSpace)
+		checkColor(color)
+		libmupdf._wasm_stroke_path(
+			this.pointer,
+			path.pointer,
+			stroke.pointer,
+			MATRIX(ctm),
+			colorspace.pointer,
+			COLOR(color),
+			alpha
+		)
+	}
+
+	clipPath(path: Path, evenOdd: boolean, ctm: Matrix) {
+		checkType(path, Path)
+		checkMatrix(ctm)
+		libmupdf._wasm_clip_path(this.pointer, path.pointer, evenOdd, MATRIX(ctm))
+	}
+
+	clipStrokePath(path: Path, stroke: StrokeState, ctm: Matrix) {
+		checkType(path, Path)
+		checkType(stroke, StrokeState)
+		checkMatrix(ctm)
+		libmupdf._wasm_clip_stroke_path(this.pointer, path.pointer, stroke.pointer, MATRIX(ctm))
+	}
+
+	fillText(text: Text, ctm: Matrix, colorspace: ColorSpace, color: Color, alpha: number) {
+		checkType(text, Text)
+		checkMatrix(ctm)
+		checkType(colorspace, ColorSpace)
+		checkColor(color)
+		libmupdf._wasm_fill_text(this.pointer, text.pointer, MATRIX(ctm), colorspace.pointer, COLOR(color), alpha)
+	}
+
+	strokeText(text: Text, stroke: StrokeState, ctm: Matrix, colorspace: ColorSpace, color: Color, alpha: number) {
+		checkType(text, Text)
+		checkType(stroke, StrokeState)
+		checkMatrix(ctm)
+		checkType(colorspace, ColorSpace)
+		checkColor(color)
+		libmupdf._wasm_stroke_text(
+			this.pointer,
+			text.pointer,
+			stroke.pointer,
+			MATRIX(ctm),
+			colorspace.pointer,
+			COLOR(color),
+			alpha
+		)
+	}
+
+	clipText(text: Text, ctm: Matrix) {
+		checkType(text, Text)
+		checkMatrix(ctm)
+		libmupdf._wasm_clip_text(this.pointer, text.pointer, MATRIX(ctm))
+	}
+
+	clipStrokeText(text: Text, stroke: StrokeState, ctm: Matrix) {
+		checkType(text, Text)
+		checkType(stroke, StrokeState)
+		checkMatrix(ctm)
+		libmupdf._wasm_clip_stroke_text(this.pointer, text.pointer, stroke.pointer, MATRIX(ctm))
+	}
+
+	ignoreText(text: Text, ctm: Matrix) {
+		checkType(text, Text)
+		checkMatrix(ctm)
+		libmupdf._wasm_ignore_text(this.pointer, text.pointer, MATRIX(ctm))
+	}
+
+	fillShade(shade: Shade, ctm: Matrix, alpha: number) {
+		checkType(shade, Shade)
+		checkMatrix(ctm)
+		libmupdf._wasm_fill_shade(this.pointer, shade.pointer, MATRIX(ctm), alpha)
+	}
+
+	fillImage(image: Image, ctm: Matrix, alpha: number) {
+		checkType(image, Image)
+		checkMatrix(ctm)
+		libmupdf._wasm_fill_image(this.pointer, image.pointer, MATRIX(ctm), alpha)
+	}
+
+	fillImageMask(image: Image, ctm: Matrix, colorspace: ColorSpace, color: Color, alpha: number) {
+		checkType(image, Image)
+		checkMatrix(ctm)
+		checkType(colorspace, ColorSpace)
+		checkColor(color)
+		libmupdf._wasm_fill_image_mask(this.pointer, image.pointer, MATRIX(ctm), colorspace.pointer, COLOR(color), alpha)
+	}
+
+	clipImageMask(image: Image, ctm: Matrix) {
+		checkType(image, Image)
+		checkMatrix(ctm)
+		libmupdf._wasm_clip_image_mask(this.pointer, image.pointer, MATRIX(ctm))
+	}
+
+	popClip() {
+		libmupdf._wasm_pop_clip(this.pointer)
+	}
+
+	beginMask(area: Rect, luminosity: boolean, colorspace: ColorSpace, color: Color) {
+		checkRect(area)
+		checkType(colorspace, ColorSpace)
+		checkColor(color)
+		libmupdf._wasm_begin_mask(this.pointer, RECT(area), luminosity, colorspace.pointer, COLOR(color))
+	}
+
+	endMask() {
+		libmupdf._wasm_end_mask(this.pointer)
+	}
+
+	beginGroup(area: Rect, colorspace: ColorSpace, isolated: boolean, knockout: boolean, blendmode: BlendMode, alpha: number) {
+		checkRect(area)
+		checkType(colorspace, ColorSpace)
+		let blendmode_ix = ENUM<BlendMode>(blendmode, Device.BLEND_MODES)
+		libmupdf._wasm_begin_group(this.pointer, RECT(area), colorspace.pointer, isolated, knockout, blendmode_ix, alpha)
+	}
+
+	endGroup() {
+		libmupdf._wasm_end_group(this.pointer)
+	}
+
+	beginTile(area: Rect, view: Rect, xstep: number, ystep: number, ctm: Matrix, id: number, doc_id: number) {
+		checkRect(area)
+		checkRect(view)
+		checkMatrix(ctm)
+		return libmupdf._wasm_begin_tile(this.pointer, RECT(area), RECT2(view), xstep, ystep, MATRIX(ctm), id, doc_id)
+	}
+
+	endTile() {
+		libmupdf._wasm_end_tile(this.pointer)
+	}
+
+	beginLayer(name: string) {
+		libmupdf._wasm_begin_layer(this.pointer, STRING(name))
+	}
+
+	endLayer() {
+		libmupdf._wasm_end_layer(this.pointer)
+	}
+
+	close() {
+		libmupdf._wasm_close_device(this.pointer)
+	}
+}
+
+export class DrawDevice extends Device {
+	constructor(matrix: Matrix, pixmap: Pixmap) {
+		checkMatrix(matrix)
+		checkType(pixmap, Pixmap)
+		super(libmupdf._wasm_new_draw_device(MATRIX(matrix), pixmap.pointer))
+	}
+}
+
+export class DisplayListDevice extends Device {
+	constructor(displayList: DisplayList) {
+		checkType(displayList, DisplayList)
+		super(libmupdf._wasm_new_display_list_device(displayList.pointer))
+	}
+}
+
+export class DocumentWriter extends Userdata<"fz_document_writer"> {
+	static override readonly _drop = libmupdf._wasm_drop_document_writer
+
+	constructor(buffer: Buffer, format: string, options: string) {
+		super(
+			libmupdf._wasm_new_document_writer_with_buffer(
+				BUFFER(buffer),
+				STRING(format),
+				STRING2(options)
+			)
+		)
+	}
+
+	beginPage(mediabox: Rect) {
+		checkRect(mediabox)
+		return new Device(
+			libmupdf._wasm_keep_device(
+				libmupdf._wasm_begin_page(this.pointer, RECT(mediabox))
+			)
+		)
+	}
+
+	endPage() {
+		libmupdf._wasm_end_page(this.pointer)
+	}
+
+	close() {
+		libmupdf._wasm_close_document_writer(this.pointer)
+	}
+}
+
+export type DocumentPermission =
+	"print" |
+	"copy" |
+	"edit" |
+	"annotate" |
+	"form" |
+	"accessibility" |
+	"assemble" |
+	"print-hq"
+
+export class Document extends Userdata<"any_document"> {
+	static override readonly _drop = libmupdf._wasm_drop_document
+
+	static readonly META_FORMAT = "format"
+	static readonly META_ENCRYPTION = "encryption"
+	static readonly META_INFO_AUTHOR = "info:Author"
+	static readonly META_INFO_TITLE = "info:Title"
+	static readonly META_INFO_SUBJECT = "info:Subject"
+	static readonly META_INFO_KEYWORDS = "info:Keywords"
+	static readonly META_INFO_CREATOR = "info:Creator"
+	static readonly META_INFO_PRODUCER = "info:Producer"
+	static readonly META_INFO_CREATIONDATE = "info:CreationDate"
+	static readonly META_INFO_MODIFICATIONDATE = "info:ModDate"
+
+	static readonly PERMISSION: Record<DocumentPermission,number> = {
+		"print": "p".charCodeAt(0),
+		"copy": "c".charCodeAt(0),
+		"edit": "e".charCodeAt(0),
+		"annotate": "n".charCodeAt(0),
+		"form": "f".charCodeAt(0),
+		"accessibility": "y".charCodeAt(0),
+		"assemble": "a".charCodeAt(0),
+		"print-hq": "h".charCodeAt(0),
+	}
+
+	static readonly LINK_DEST: LinkDestType[] = [
+		"Fit",
+		"FitB",
+		"FitH",
+		"FitBH",
+		"FitV",
+		"FitBV",
+		"FitR",
+		"XYZ",
+	]
+
+	static openDocument(from: Buffer | ArrayBuffer | Uint8Array | Stream | string, magic?: string): Document {
+		let pointer = 0 as Pointer<"any_document">
+		let free_from = false
+
+		if (typeof from === "string") {
+			magic = from
+			if (node_fs)
+				from = node_fs.readFileSync(from)
+			else
+				throw new Error("missing 'fs' module")
+		} else {
+			if (typeof magic === "undefined")
+				magic = "application/pdf"
+		}
+
+		checkType(magic, "string")
+
+		if (from instanceof ArrayBuffer || from instanceof Uint8Array) {
+			from = new Buffer(from)
+			free_from = true
+		}
+		if (from instanceof Buffer)
+			pointer = libmupdf._wasm_open_document_with_buffer(STRING(magic), from.pointer)
+		else if (from instanceof Stream)
+			pointer = libmupdf._wasm_open_document_with_stream(STRING(magic), from.pointer)
+		else
+			throw new Error("not a Buffer or Stream")
+
+		if (free_from) {
+			// Destroy any implicit Buffer instances immediately!
+			// This may help the GC and FinalizationRegistry out when
+			// processing many documents without a pause.
+			from.destroy()
+		}
+
+		let pdf = libmupdf._wasm_pdf_document_from_fz_document(pointer)
+		if (pdf)
+			return new PDFDocument(pdf)
+
+		return new Document(pointer)
+	}
+
+	formatLinkURI(dest: LinkDest) {
+		return fromStringFree(
+			libmupdf._wasm_format_link_uri(this.pointer,
+				dest.chapter | 0,
+				dest.page | 0,
+				ENUM<LinkDestType>(dest.type, Document.LINK_DEST),
+				+dest.x,
+				+dest.y,
+				+dest.width,
+				+dest.height,
+				+dest.zoom
+			)
+		)
+	}
+
+	asPDF() {
+		if (this instanceof PDFDocument)
+			return this
+		return null
+	}
+
+	isPDF() {
+		return this instanceof PDFDocument
+	}
+
+	needsPassword() {
+		return !!libmupdf._wasm_needs_password(this.pointer)
+	}
+
+	authenticatePassword(password: string) {
+		return libmupdf._wasm_authenticate_password(this.pointer, STRING(password))
+	}
+
+	hasPermission(perm: DocumentPermission) {
+		let perm_ix = Document.PERMISSION[perm]
+		return !!libmupdf._wasm_has_permission(this.pointer, perm_ix)
+	}
+
+	getMetaData(key: string) {
+		let value = libmupdf._wasm_lookup_metadata(this.pointer, STRING(key))
+		if (value)
+			return fromString(value)
+		return undefined
+	}
+
+	setMetaData(key: string, value: string) {
+		libmupdf._wasm_set_metadata(this.pointer, STRING(key), STRING2(value))
+	}
+
+	countPages() {
+		return libmupdf._wasm_count_pages(this.pointer)
+	}
+
+	isReflowable() {
+		libmupdf._wasm_is_document_reflowable(this.pointer)
+	}
+
+	layout(w: number, h: number, em: number) {
+		libmupdf._wasm_layout_document(this.pointer, w, h, em)
+	}
+
+	loadPage(index: number) {
+		let fz_ptr = libmupdf._wasm_load_page(this.pointer, index)
+		if (this instanceof PDFDocument) {
+			let pdf_ptr = libmupdf._wasm_pdf_page_from_fz_page(fz_ptr)
+			if (pdf_ptr)
+				return new PDFPage(this, pdf_ptr)
+		}
+		return new Page(fz_ptr)
+	}
+
+	loadOutline() {
+		let doc = this.pointer
+		function to_outline(outline: Pointer<"fz_outline">) {
+			let result: OutlineItem[] = []
+			while (outline) {
+				let title = libmupdf._wasm_outline_get_title(outline)
+				let uri = libmupdf._wasm_outline_get_uri(outline)
+				let open = libmupdf._wasm_outline_get_is_open(outline)
+
+				let item: OutlineItem = {
+					title: title ? fromString(title) : undefined,
+					uri: uri ? fromString(uri) : undefined,
+					open: !!open,
+				}
+
+				let page = libmupdf._wasm_outline_get_page(doc, outline)
+				if (page >= 0)
+					item.page = page
+
+				let down = libmupdf._wasm_outline_get_down(outline)
+				if (down)
+					item.down = to_outline(down)
+
+				result.push(item)
+
+				outline = libmupdf._wasm_outline_get_next(outline)
+			}
+			return result
+		}
+		let root = libmupdf._wasm_load_outline(doc)
+		if (root)
+			return to_outline(root)
+		return null
+	}
+
+	resolveLink(link: string | Link) {
+		if (link instanceof Link)
+			return libmupdf._wasm_resolve_link(this.pointer, libmupdf._wasm_link_get_uri(link.pointer))
+		return libmupdf._wasm_resolve_link(this.pointer, STRING(link))
+	}
+
+	resolveLinkDestination(link: string | Link): LinkDest {
+		let dest: Pointer<"fz_link_dest">
+		if (link instanceof Link)
+			dest = libmupdf._wasm_resolve_link_dest(this.pointer, libmupdf._wasm_link_get_uri(link.pointer))
+		else
+			dest = libmupdf._wasm_resolve_link_dest(this.pointer, STRING(link))
+		return {
+			type: Document.LINK_DEST[libmupdf._wasm_link_dest_get_type(dest)] as LinkDestType,
+			chapter: libmupdf._wasm_link_dest_get_chapter(dest),
+			page: libmupdf._wasm_link_dest_get_page(dest),
+			x: libmupdf._wasm_link_dest_get_x(dest),
+			y: libmupdf._wasm_link_dest_get_y(dest),
+			width: libmupdf._wasm_link_dest_get_w(dest),
+			height: libmupdf._wasm_link_dest_get_h(dest),
+			zoom: libmupdf._wasm_link_dest_get_zoom(dest),
+		}
+	}
+
+	outlineIterator() {
+		return new OutlineIterator(libmupdf._wasm_new_outline_iterator(this.pointer))
+	}
+}
+
+interface OutlineItem {
+	title: string | undefined,
+	uri: string | undefined,
+	open: boolean,
+	down?: OutlineItem[],
+	page?: number,
+}
+
+export class OutlineIterator extends Userdata<"fz_outline_iterator"> {
+	static override readonly _drop = libmupdf._wasm_drop_outline_iterator
+
+	static readonly RESULT_DID_NOT_MOVE = -1
+	static readonly RESULT_AT_ITEM = 0
+	static readonly RESULT_AT_EMPTY = 1
+
+	item() {
+		let item = libmupdf._wasm_outline_iterator_item(this.pointer)
+		if (item) {
+			let title_ptr = libmupdf._wasm_outline_item_get_title(item)
+			let uri_ptr = libmupdf._wasm_outline_item_get_uri(item)
+			let is_open = libmupdf._wasm_outline_item_get_is_open(item)
+			return {
+				title: title_ptr ? fromString(title_ptr) : undefined,
+				uri: uri_ptr ? fromString(uri_ptr) : undefined,
+				open: !!is_open,
+			} as OutlineItem
+		}
+		return null
+	}
+
+	next() {
+		return libmupdf._wasm_outline_iterator_next(this.pointer)
+	}
+
+	prev() {
+		return libmupdf._wasm_outline_iterator_prev(this.pointer)
+	}
+
+	up() {
+		return libmupdf._wasm_outline_iterator_up(this.pointer)
+	}
+
+	down() {
+		return libmupdf._wasm_outline_iterator_down(this.pointer)
+	}
+
+	delete() {
+		return libmupdf._wasm_outline_iterator_delete(this.pointer)
+	}
+
+	insert(item: OutlineItem) {
+		return libmupdf._wasm_outline_iterator_insert(this.pointer, STRING_OPT(item.title), STRING2_OPT(item.uri), item.open)
+	}
+
+	update(item: OutlineItem) {
+		libmupdf._wasm_outline_iterator_update(this.pointer, STRING_OPT(item.title), STRING2_OPT(item.uri), item.open)
+	}
+}
+
+export type LinkDestType =
+	"Fit" |
+	"FitB" |
+	"FitH" |
+	"FitBH" |
+	"FitV" |
+	"FitBV" |
+	"FitR" |
+	"XYZ"
+
+interface LinkDest {
+	type: LinkDestType,
+	chapter: number,
+	page: number,
+	x: number,
+	y: number,
+	width: number,
+	height: number,
+	zoom: number,
+}
+
+export class Link extends Userdata<"fz_link"> {
+	static override readonly _drop = libmupdf._wasm_drop_link
+
+	getBounds() {
+		return fromRect(libmupdf._wasm_link_get_rect(this.pointer))
+	}
+
+	setBounds(rect: Rect) {
+		checkRect(rect)
+		libmupdf._wasm_link_set_rect(this.pointer, RECT(rect))
+	}
+
+	getURI() {
+		return fromString(libmupdf._wasm_link_get_uri(this.pointer))
+	}
+
+	setURI(uri: string) {
+		checkType(uri, "string")
+		libmupdf._wasm_link_set_uri(this.pointer, STRING(uri))
+	}
+
+	isExternal() {
+		return /^\w[\w+-.]*:/.test(this.getURI())
+	}
+}
+
+export type PageBox = "MediaBox" | "CropBox" | "BleedBox" | "TrimBox" | "ArtBox"
+
+export class Page extends Userdata<"any_page"> {
+	static override readonly _drop = libmupdf._wasm_drop_page
+
+	static readonly BOXES: PageBox[] = [
+		"MediaBox",
+		"CropBox",
+		"BleedBox",
+		"TrimBox",
+		"ArtBox"
+	]
+
+	isPDF() {
+		return this instanceof PDFPage
+	}
+
+	getBounds(box: PageBox = "CropBox") {
+		let box_ix = ENUM<PageBox>(box, Page.BOXES)
+		return fromRect(libmupdf._wasm_bound_page(this.pointer, box_ix))
+	}
+
+	getLabel() {
+		return fromString(libmupdf._wasm_page_label(this.pointer))
+	}
+
+	run(device: Device, matrix: Matrix) {
+		checkType(device, Device)
+		checkMatrix(matrix)
+		libmupdf._wasm_run_page(this.pointer, device.pointer, MATRIX(matrix))
+	}
+
+	runPageContents(device: Device, matrix: Matrix) {
+		checkType(device, Device)
+		checkMatrix(matrix)
+		libmupdf._wasm_run_page_contents(this.pointer, device.pointer, MATRIX(matrix))
+	}
+
+	runPageAnnots(device: Device, matrix: Matrix) {
+		checkType(device, Device)
+		checkMatrix(matrix)
+		libmupdf._wasm_run_page_annots(this.pointer, device.pointer, MATRIX(matrix))
+	}
+
+	runPageWidgets(device: Device, matrix: Matrix) {
+		checkType(device, Device)
+		checkMatrix(matrix)
+		libmupdf._wasm_run_page_widgets(this.pointer, device.pointer, MATRIX(matrix))
+	}
+
+	toPixmap(matrix: Matrix, colorspace: ColorSpace, alpha = false, showExtras = true) {
+		checkType(colorspace, ColorSpace)
+		checkMatrix(matrix)
+		let result
+		if (showExtras)
+			result = libmupdf._wasm_new_pixmap_from_page(this.pointer,
+				MATRIX(matrix),
+				colorspace.pointer,
+				alpha)
+		else
+			result = libmupdf._wasm_new_pixmap_from_page_contents(this.pointer,
+				MATRIX(matrix),
+				colorspace.pointer,
+				alpha)
+		return new Pixmap(result)
+	}
+
+	toDisplayList(showExtras = true) {
+		let result
+		if (showExtras)
+			result = libmupdf._wasm_new_display_list_from_page(this.pointer)
+		else
+			result = libmupdf._wasm_new_display_list_from_page_contents(this.pointer)
+		return new DisplayList(result)
+	}
+
+	toStructuredText(options = "") {
+		checkType(options, "string")
+		return new StructuredText(libmupdf._wasm_new_stext_page_from_page(this.pointer, STRING(options)))
+	}
+
+	getLinks() {
+		let links: Link[] = []
+		let link = libmupdf._wasm_load_links(this.pointer)
+		while (link) {
+			links.push(new Link(libmupdf._wasm_keep_link(link)))
+			link = libmupdf._wasm_link_get_next(link)
+		}
+		return links
+	}
+
+	createLink(bbox: Rect, uri: string) {
+		checkRect(bbox)
+		return new Link(libmupdf._wasm_create_link(this.pointer, RECT(bbox), STRING(uri)))
+	}
+
+	deleteLink(link: Link) {
+		checkType(link, Link)
+		libmupdf._wasm_delete_link(this.pointer, link.pointer)
+	}
+
+	search(needle: string, max_hits = 500) {
+		return runSearch(libmupdf._wasm_search_page, this.pointer, needle, max_hits)
+	}
+
+}
+
+/* -------------------------------------------------------------------------- */
+
+export class PDFDocument extends Document {
+	// Create a new empty document
+	constructor()
+
+	// Open an existing document
+	constructor(filename: string)
+	constructor(data: Buffer | ArrayBuffer | Uint8Array | Stream)
+
+	// PRIVATE
+	constructor(clone: PDFDocument)
+	constructor(pointer: Pointer<"any_document">)
+	constructor(data: Buffer | ArrayBuffer | Uint8Array | Stream)
+
+	constructor(arg1?: Pointer<"any_document"> | Buffer | ArrayBuffer | Uint8Array | Stream | PDFDocument | string) {
+		if (typeof arg1 === "undefined")
+			super(libmupdf._wasm_pdf_create_document())
+		else if (typeof arg1 === "number")
+			super(arg1)
+		else if (arg1 instanceof PDFDocument) {
+			super(arg1.pointer)
+			libmupdf._wasm_keep_document(this.pointer)
+		} else {
+			let doc = Document.openDocument(arg1, "application/pdf")
+			if (doc instanceof PDFDocument)
+				return doc
+			throw new Error("not a PDF document")
+		}
+	}
+
+	override loadPage(index: number) {
+		return super.loadPage(index) as PDFPage
+	}
+
+	// PDFObject instances are always bound to a document, so the WASM/JS value interface lives here.
+
+	// Wrap a pdf_obj in a Userdata object. The pointer must be newly created or we already own it.
+	_fromPDFObjectNew(ptr: Pointer<"pdf_obj">) {
+		if (ptr === 0)
+			return PDFObject.Null
+		return new PDFObject(this, ptr)
+	}
+
+	// Wrap a pdf_obj in a Userdata object. The pointer must be a borrowed pointer, so we have to take ownership.
+	_fromPDFObjectKeep(ptr: Pointer<"pdf_obj">) {
+		if (ptr === 0)
+			return PDFObject.Null
+		return new PDFObject(this, libmupdf._wasm_pdf_keep_obj(ptr))
+	}
+
+	_toPDFObject(obj: any) {
+		if (obj instanceof PDFObject)
+			return obj
+		if (obj === null || obj === undefined)
+			return this.newNull()
+		if (typeof obj === "string") {
+			// if a JS string is surrounded by parens, convert it to a PDF string
+			if (obj.startsWith("(") && obj.endsWith(")"))
+				return this.newString(obj.slice(1, -1))
+			// otherwise treat it as a name
+			return this.newName(obj)
+		}
+		if (typeof obj === "number") {
+			if (obj === (obj | 0))
+				return this.newInteger(obj)
+			return this.newReal(obj)
+		}
+		if (typeof obj === "boolean")
+			return this.newBoolean(obj)
+		if (obj instanceof Array) {
+			let result = this.newArray()
+			for (let item of obj)
+				result.push(item)
+			return result
+		}
+		if (obj instanceof Object) {
+			let result = this.newDictionary()
+			for (let key in obj)
+				result.put(key, obj[key])
+			return result
+		}
+		throw new TypeError("cannot convert value to PDFObject")
+	}
+
+	_PDFOBJ(obj: any) {
+		// Note: We have to create a PDFObject instance for garbage collection.
+		return this._toPDFObject(obj).pointer
+	}
+
+	getVersion() {
+		return libmupdf._wasm_pdf_version(this.pointer)
+	}
+
+	getLanguage() {
+		return fromStringOrNull(libmupdf._wasm_pdf_document_language(this.pointer))
+	}
+
+	setLanguage(lang: string) {
+		libmupdf._wasm_pdf_set_document_language(this.pointer, STRING(lang))
+	}
+
+	countObjects() {
+		return libmupdf._wasm_pdf_xref_len(this.pointer)
+	}
+
+	getTrailer() {
+		return new PDFObject(this, libmupdf._wasm_pdf_trailer(this.pointer))
+	}
+
+	createObject() {
+		let num = libmupdf._wasm_pdf_create_object(this.pointer)
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_indirect(this.pointer, num))
+	}
+
+	newNull() { return PDFObject.Null }
+	newBoolean(v: boolean) { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_bool(v)) }
+	newInteger(v: number) { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_int(v)) }
+	newReal(v: number) { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_real(v)) }
+	newName(v: string) { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_name(STRING(v))) }
+	newString(v: string) { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_text_string(STRING(v))) }
+
+	newByteString(v: Uint8Array | number[]) {
+		if (v instanceof Array)
+			v = Uint8Array.from(v)
+		checkType(v, Uint8Array)
+		let len = v.byteLength
+		let ptr = Malloc<"char">(len)
+		libmupdf.HEAPU8.set(v, ptr)
+		try {
+			return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_string(ptr, len))
+		} finally {
+			Free(ptr)
+		}
+	}
+
+	newIndirect(v: number) { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_indirect(this.pointer, v)) }
+	newArray() { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_array(this.pointer)) }
+	newDictionary() { return this._fromPDFObjectNew(libmupdf._wasm_pdf_new_dict(this.pointer)) }
+
+	deleteObject(num: number | PDFObject) {
+		if (num instanceof PDFObject)
+			num = num.asIndirect()
+		else
+			checkType(num, "number")
+		libmupdf._wasm_pdf_delete_object(this.pointer, num)
+	}
+
+	addObject(obj: any) {
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_add_object(this.pointer, this._PDFOBJ(obj)))
+	}
+
+	addStream(buf: AnyBuffer, obj: any) {
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_add_stream(this.pointer, BUFFER(buf), this._PDFOBJ(obj), 0))
+	}
+
+	addRawStream(buf: AnyBuffer, obj: any) {
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_add_stream(this.pointer, BUFFER(buf), this._PDFOBJ(obj), 1))
+	}
+
+	newGraftMap() {
+		return new PDFGraftMap(this, libmupdf._wasm_pdf_new_graft_map(this.pointer))
+	}
+
+	graftObject(obj: PDFObject) {
+		checkType(obj, PDFObject)
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_graft_object(this.pointer, obj.pointer))
+	}
+
+	graftPage(to: number, srcDoc: PDFDocument, srcPage: number) {
+		checkType(to, "number")
+		checkType(srcDoc, PDFDocument)
+		checkType(srcPage, "number")
+		libmupdf._wasm_pdf_graft_page(this.pointer, to, srcDoc.pointer, srcPage)
+	}
+
+	addSimpleFont(font: Font, encoding: FontSimpleEncoding = "Latin") {
+		checkType(font, Font)
+		var encoding_ix = ENUM<FontSimpleEncoding>(encoding, Font.SIMPLE_ENCODING)
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_add_simple_font(this.pointer, font.pointer, encoding_ix))
+	}
+
+	addCJKFont(font: Font, lang: FontCJKOrdering | FontCJKLanguage, wmode = 0, serif = true) {
+		checkType(font, Font)
+		if (typeof lang === "string")
+			lang = Font.CJK_ORDERING_BY_LANG[lang]
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_add_cjk_font(this.pointer, font.pointer, lang, wmode, serif))
+	}
+
+	addFont(font: Font) {
+		checkType(font, Font)
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_add_cid_font(this.pointer, font.pointer))
+	}
+
+	addImage(image: Image) {
+		checkType(image, Image)
+		return this._fromPDFObjectNew(libmupdf._wasm_pdf_add_image(this.pointer, image.pointer))
+	}
+
+	loadImage(ref: PDFObject) {
+		checkType(ref, PDFObject)
+		return new Image(libmupdf._wasm_pdf_load_image(this.pointer, ref.pointer))
+	}
+
+	findPage(index: number) {
+		checkType(index, "number")
+		return this._fromPDFObjectKeep(libmupdf._wasm_pdf_lookup_page_obj(this.pointer, index))
+	}
+
+	addPage(mediabox: Rect, rotate: Rotate, resources: any, contents: AnyBuffer) {
+		checkRect(mediabox)
+		checkType(rotate, "number")
+		return this._fromPDFObjectNew(
+			libmupdf._wasm_pdf_add_page(
+				this.pointer,
+				RECT(mediabox),
+				rotate,
+				this._PDFOBJ(resources),
+				BUFFER(contents)
+			)
+		)
+	}
+
+	insertPage(at: number, obj: PDFObject) {
+		checkType(at, "number")
+		libmupdf._wasm_pdf_insert_page(this.pointer, at, this._PDFOBJ(obj))
+	}
+
+	deletePage(at: number) {
+		checkType(at, "number")
+		libmupdf._wasm_pdf_delete_page(this.pointer, at)
+	}
+
+	isEmbeddedFile(ref: PDFObject) {
+		checkType(ref, PDFObject)
+		return !!libmupdf._wasm_pdf_is_embedded_file(ref.pointer)
+	}
+
+	addEmbeddedFile(filename: string, mimetype: string, contents: AnyBuffer, created: Date, modified: Date, checksum = false) {
+		checkType(filename, "string")
+		checkType(mimetype, "string")
+		checkType(created, Date)
+		checkType(modified, Date)
+		checkType(checksum, "boolean")
+		return this._fromPDFObjectNew(
+			libmupdf._wasm_pdf_add_embedded_file(
+				this.pointer,
+				STRING(filename),
+				STRING2(mimetype),
+				BUFFER(contents),
+				created.getTime() / 1000 | 0,
+				modified.getTime() / 1000 | 0,
+				checksum
+			)
+		)
+	}
+
+	getFilespecParams(ref: PDFObject) {
+		checkType(ref, PDFObject)
+		let ptr = libmupdf._wasm_pdf_get_filespec_params(ref.pointer)
+		return {
+			filename:
+				fromString(libmupdf._wasm_pdf_filespec_params_get_filename(ptr)),
+			mimetype:
+				fromString(libmupdf._wasm_pdf_filespec_params_get_mimetype(ptr)),
+			size:
+				libmupdf._wasm_pdf_filespec_params_get_filename(ptr),
+			creationDate:
+				new Date(libmupdf._wasm_pdf_filespec_params_get_created(ptr) * 1000),
+			modificationDate:
+				new Date(libmupdf._wasm_pdf_filespec_params_get_modified(ptr) * 1000),
+		}
+	}
+
+	getEmbeddedFileContents(ref: PDFObject) {
+		checkType(ref, PDFObject)
+		let contents = libmupdf._wasm_pdf_load_embedded_file_contents(ref.pointer)
+		if (contents)
+			return new Buffer(contents)
+		return null
+	}
+
+	getEmbeddedFiles(): Record<string,PDFObject> {
+		function _getEmbeddedFilesRec(result: Record<string,PDFObject>, N: PDFObject) {
+			var i, n
+			if (N.isDictionary()) {
+				var NN = N.get("Names")
+				if (NN)
+					for (i = 0, n = NN.length; i < n; i += 2)
+						result[NN.get(i+0).asString()] = NN.get(i+1)
+				var NK = N.get("Kids")
+				if (NK)
+					for (i = 0, n = NK.length; i < n; i += 1)
+						_getEmbeddedFilesRec(result, NK.get(i))
+			}
+			return result
+		}
+		return _getEmbeddedFilesRec({}, this.getTrailer().get("Root", "Names", "EmbeddedFiles"))
+	}
+
+	loadNameTree(treeName: string): Record<string,PDFObject> {
+		function _loadNameTreeRec(dict: Record<string,PDFObject>, node: PDFObject) {
+			var kids = node.get("Kids")
+			if (kids && kids.isArray())
+				for (var i = 0; i < kids.length; i += 1)
+					_loadNameTreeRec(dict, kids.get(i))
+			var names = node.get("Names")
+			if (names && names.isArray())
+				for (var i = 0; i < names.length; i += 2)
+					dict[names.get(i).asString()] = names.get(i+1)
+		}
+		var node = this.getTrailer().get("Root").get("Names").get(treeName)
+		var dict = {}
+		if (node.isDictionary())
+			_loadNameTreeRec(dict, node)
+		return dict
+	}
+
+	insertEmbeddedFile(filename: string, filespec: PDFObject) {
+		var efs = this.getEmbeddedFiles()
+		efs[filename] = filespec
+		this._rewriteEmbeddedFiles(efs)
+	}
+
+	deleteEmbeddedFile(filename: string) {
+		var efs = this.getEmbeddedFiles()
+		delete efs[filename]
+		this._rewriteEmbeddedFiles(efs)
+	}
+
+	_rewriteEmbeddedFiles(efs: Record<string,PDFObject>) {
+		var efs_keys = Object.keys(efs)
+		efs_keys.sort()
+		var root = this.getTrailer().get("Root")
+		var root_names = root.get("Names")
+		if (!root_names.isDictionary())
+			root_names = root.put("Names", this.newDictionary())
+		var root_names_efs = root_names.put("EmbeddedFiles", this.newDictionary())
+		var root_names_efs_names = root_names_efs.put("Names", this.newArray())
+		for (var key of efs_keys) {
+			root_names_efs_names.push(this.newString(key))
+			root_names_efs_names.push(efs[key])
+		}
+	}
+
+	saveToBuffer(options: string | Record<string,any> = "") {
+		var options_string
+		if (typeof options === "object") {
+			options_string = Object.entries(options).map(kv => {
+				var k: string = kv[0]
+				var v: any = kv[1]
+				if (v === true)
+					return k + "=" + "yes"
+				else if (v === false)
+					return k + "=" + "no"
+				else
+					return k + "=" + String(v).replaceAll(",", ":")
+			}).join(",")
+		} else {
+			options_string = options
+		}
+		return new Buffer(libmupdf._wasm_pdf_write_document_buffer(this.pointer, STRING(options_string)))
+	}
+
+	save(filename: string, options: string | Record<string,any> = "") {
+		if (node_fs)
+			node_fs.writeFileSync(filename, this.saveToBuffer(options).asUint8Array())
+		else
+			throw new Error("missing 'fs' module")
+	}
+
+	static readonly PAGE_LABEL_NONE = "\0"
+	static readonly PAGE_LABEL_DECIMAL = "D"
+	static readonly PAGE_LABEL_ROMAN_UC = "R"
+	static readonly PAGE_LABEL_ROMAN_LC = "r"
+	static readonly PAGE_LABEL_ALPHA_UC = "A"
+	static readonly PAGE_LABEL_ALPHA_LC = "a"
+
+	setPageLabels(index: number, style = "D", prefix = "", start = 1) {
+		libmupdf._wasm_pdf_set_page_labels(this.pointer, index, style.charCodeAt(0), STRING(prefix), start)
+	}
+
+	deletePageLabels(index: number) {
+		libmupdf._wasm_pdf_delete_page_labels(this.pointer, index)
+	}
+
+	wasRepaired() {
+		return !!libmupdf._wasm_pdf_was_repaired(this.pointer)
+	}
+
+	hasUnsavedChanges() {
+		return !!libmupdf._wasm_pdf_has_unsaved_changes(this.pointer)
+	}
+
+	countVersions() {
+		return libmupdf._wasm_pdf_count_versions(this.pointer)
+	}
+
+	countUnsavedVersions() {
+		return libmupdf._wasm_pdf_count_unsaved_versions(this.pointer)
+	}
+
+	validateChangeHistory() {
+		return libmupdf._wasm_pdf_validate_change_history(this.pointer)
+	}
+
+	canBeSavedIncrementally() {
+		return !!libmupdf._wasm_pdf_can_be_saved_incrementally(this.pointer)
+	}
+
+	enableJournal() {
+		libmupdf._wasm_pdf_enable_journal(this.pointer)
+	}
+
+	getJournal() {
+		let position = libmupdf._wasm_pdf_undoredo_state_position(this.pointer)
+		let n = libmupdf._wasm_pdf_undoredo_state_count(this.pointer)
+		let steps: string[] = []
+		for (let i = 0; i < n; ++i)
+			steps.push(
+				fromString(
+					libmupdf._wasm_pdf_undoredo_step(this.pointer, i),
+				)
+			)
+		return { position, steps }
+	}
+
+	beginOperation(op: string) {
+		libmupdf._wasm_pdf_begin_operation(this.pointer, STRING(op))
+	}
+
+	beginImplicitOperation() {
+		libmupdf._wasm_pdf_begin_implicit_operation(this.pointer)
+	}
+
+	endOperation() {
+		libmupdf._wasm_pdf_end_operation(this.pointer)
+	}
+
+	abandonOperation() {
+		libmupdf._wasm_pdf_abandon_operation(this.pointer)
+	}
+
+	canUndo() {
+		return !!libmupdf._wasm_pdf_can_undo(this.pointer)
+	}
+
+	canRedo() {
+		return !!libmupdf._wasm_pdf_can_redo(this.pointer)
+	}
+
+	undo() {
+		libmupdf._wasm_pdf_undo(this.pointer)
+	}
+
+	redo() {
+		libmupdf._wasm_pdf_redo(this.pointer)
+	}
+
+	isJSSupported() {
+		return !!libmupdf._wasm_pdf_js_supported(this.pointer)
+	}
+
+	enableJS() {
+		libmupdf._wasm_pdf_enable_js(this.pointer)
+	}
+
+	disableJS() {
+		libmupdf._wasm_pdf_disable_js(this.pointer)
+	}
+
+	setJSEventListener(_listener: any) {
+		throw "TODO"
+	}
+
+	rearrangePages(pages: number[]) {
+		let n = pages.length
+		let ptr = Malloc<"int">(n << 2)
+		for (let i = 0; i < n; ++i)
+			libmupdf.HEAPU32[(ptr >> 2) + i] = pages[i] || 0
+		try {
+			libmupdf._wasm_pdf_rearrange_pages(this.pointer, n, ptr)
+		} finally {
+			Free(ptr)
+		}
+	}
+
+	subsetFonts() {
+		libmupdf._wasm_pdf_subset_fonts(this.pointer)
+	}
+
+	bake(bakeAnnots = true, bakeWidgets = true) {
+		libmupdf._wasm_pdf_bake_document(this.pointer, bakeAnnots, bakeWidgets)
+	}
+
+	countLayers(): number {
+		return libmupdf._wasm_pdf_count_layers(this.pointer)
+	}
+
+	isLayerVisible(layer: number): boolean {
+		return !!libmupdf._wasm_pdf_layer_is_enabled(this.pointer, layer)
+	}
+
+	setLayerVisible(layer: number, visible: boolean): void {
+		libmupdf._wasm_pdf_enable_layer(this.pointer, layer, Number(visible))
+	}
+
+	getLayerName(layer: number): string {
+		return fromString(libmupdf._wasm_pdf_layer_name(this.pointer, layer))
+	}
+
+	resetForm(fields: PDFObject, exclude: boolean) {
+		libmupdf._wasm_pdf_reset_form(this.pointer, this._PDFOBJ(fields), Number(exclude))
+	}
+}
+
+export class PDFPage extends Page {
+	_doc: PDFDocument
+	_annots: PDFAnnotation[] | null
+	_widgets: PDFWidget[] | null
+
+	// PRIVATE
+	constructor(doc: PDFDocument, pointer: Pointer<"any_page">) {
+		super(pointer)
+		this._doc = doc
+		this._annots = null
+		this._widgets = null
+	}
+
+	getObject() {
+		return this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_page_get_obj(this.pointer))
+	}
+
+	getTransform() {
+		return fromMatrix(libmupdf._wasm_pdf_page_transform(this.pointer))
+	}
+
+	setPageBox(box: PageBox, rect: Rect) {
+		let box_ix = ENUM<PageBox>(box, Page.BOXES)
+		checkRect(rect)
+		libmupdf._wasm_pdf_set_page_box(this.pointer, box_ix, RECT(rect))
+	}
+
+	override toPixmap(matrix: Matrix, colorspace: ColorSpace, alpha = false, showExtras = true, usage = "View", box: PageBox = "CropBox") {
+		checkMatrix(matrix)
+		checkType(colorspace, ColorSpace)
+		let box_ix = ENUM<PageBox>(box, Page.BOXES)
+		let result
+		if (showExtras)
+			result = libmupdf._wasm_pdf_new_pixmap_from_page_with_usage(this.pointer,
+				MATRIX(matrix),
+				colorspace.pointer,
+				alpha,
+				STRING(usage),
+				box_ix)
+		else
+			result = libmupdf._wasm_pdf_new_pixmap_from_page_contents_with_usage(this.pointer,
+				MATRIX(matrix),
+				colorspace.pointer,
+				alpha,
+				STRING(usage),
+				box_ix)
+		return new Pixmap(result)
+	}
+
+	getWidgets() {
+		if (!this._widgets) {
+			this._widgets = []
+			let widget = libmupdf._wasm_pdf_first_widget(this.pointer)
+			while (widget) {
+				this._widgets.push(new PDFWidget(this._doc, libmupdf._wasm_pdf_keep_annot(widget)))
+				widget = libmupdf._wasm_pdf_next_widget(widget)
+			}
+		}
+		return this._widgets
+	}
+
+	getAnnotations() {
+		if (!this._annots) {
+			this._annots = []
+			let annot = libmupdf._wasm_pdf_first_annot(this.pointer)
+			while (annot) {
+				this._annots.push(new PDFAnnotation(this._doc, libmupdf._wasm_pdf_keep_annot(annot)))
+				annot = libmupdf._wasm_pdf_next_annot(annot)
+			}
+		}
+		return this._annots
+	}
+
+	createAnnotation(type: PDFAnnotationType) {
+		let type_ix = ENUM<PDFAnnotationType>(type, PDFAnnotation.ANNOT_TYPES)
+		let annot = new PDFAnnotation(this._doc, libmupdf._wasm_pdf_create_annot(this.pointer, type_ix))
+		if (this._annots)
+			this._annots.push(annot)
+		return annot
+	}
+
+	deleteAnnotation(annot: PDFAnnotation) {
+		checkType(annot, PDFAnnotation)
+		libmupdf._wasm_pdf_delete_annot(this.pointer, annot.pointer)
+		if (this._annots) {
+			let ix = this._annots.indexOf(annot)
+			if (ix >= 0)
+				this._annots.splice(ix, 1)
+		}
+	}
+
+	static readonly REDACT_IMAGE_NONE = 0
+	static readonly REDACT_IMAGE_REMOVE = 1
+	static readonly REDACT_IMAGE_PIXELS = 2
+	static readonly REDACT_IMAGE_UNLESS_INVISIBLE = 3
+
+	static readonly REDACT_LINE_ART_NONE = 0
+	static readonly REDACT_LINE_ART_REMOVE_IF_COVERED = 1
+	static readonly REDACT_LINE_ART_REMOVE_IF_TOUCHED = 2
+
+	static readonly REDACT_TEXT_REMOVE = 0
+	static readonly REDACT_TEXT_NONE = 1
+
+	applyRedactions(black_boxes = true, image_method = 2, line_art_method = 1, text_method = 0) {
+		libmupdf._wasm_pdf_redact_page(this.pointer, Number(black_boxes), image_method, line_art_method, text_method)
+	}
+
+	update() {
+		return !!libmupdf._wasm_pdf_update_page(this.pointer)
+	}
+}
+
+type PDFObjectPath = Array<number | string | PDFObject>
+
+export class PDFObject extends Userdata<"pdf_obj"> {
+	static override readonly _drop = libmupdf._wasm_pdf_drop_obj
+
+	static readonly Null = new PDFObject(null as unknown as PDFDocument, 0 as Pointer<"pdf_obj">)
+
+	_doc: PDFDocument
+
+	// PRIVATE
+	constructor(doc: PDFDocument, pointer: Pointer<"pdf_obj">) {
+		super(libmupdf._wasm_pdf_keep_obj(pointer))
+		this._doc = doc
+	}
+
+	isNull() { return this === PDFObject.Null }
+	isIndirect() { return !!libmupdf._wasm_pdf_is_indirect(this.pointer) }
+	isBoolean() { return !!libmupdf._wasm_pdf_is_bool(this.pointer) }
+	isInteger() { return !!libmupdf._wasm_pdf_is_int(this.pointer) }
+	isReal() { return !!libmupdf._wasm_pdf_is_real(this.pointer) }
+	isNumber() { return !!libmupdf._wasm_pdf_is_number(this.pointer) }
+	isName() { return !!libmupdf._wasm_pdf_is_name(this.pointer) }
+	isString() { return !!libmupdf._wasm_pdf_is_string(this.pointer) }
+	isArray() { return !!libmupdf._wasm_pdf_is_array(this.pointer) }
+	isDictionary() { return !!libmupdf._wasm_pdf_is_dict(this.pointer) }
+	isStream() { return !!libmupdf._wasm_pdf_is_stream(this.pointer) }
+
+	asIndirect(): number { return libmupdf._wasm_pdf_to_num(this.pointer) }
+	asBoolean() { return !!libmupdf._wasm_pdf_to_bool(this.pointer) }
+	asNumber(): number { return libmupdf._wasm_pdf_to_real(this.pointer) }
+	asName() { return fromString(libmupdf._wasm_pdf_to_name(this.pointer)) }
+	asString() { return fromString(libmupdf._wasm_pdf_to_text_string(this.pointer)) }
+
+	asByteString() {
+		let ptr = libmupdf._wasm_pdf_to_string(this.pointer, _wasm_int)
+		let len = libmupdf.HEAPU32[_wasm_int >> 2] as number
+		return libmupdf.HEAPU8.slice(ptr, ptr + len)
+	}
+
+	readStream() { return new Buffer(libmupdf._wasm_pdf_load_stream(this.pointer)) }
+	readRawStream() { return new Buffer(libmupdf._wasm_pdf_load_raw_stream(this.pointer)) }
+
+	writeObject(obj: any) {
+		if (!this.isIndirect())
+			throw new TypeError("can only call PDFObject.writeObject on an indirect reference")
+		libmupdf._wasm_pdf_update_object(this._doc.pointer, this.asIndirect(), this._doc._PDFOBJ(obj))
+	}
+
+	writeStream(buf: AnyBuffer) {
+		if (!this.isIndirect())
+			throw new TypeError("can only call PDFObject.writeStream on an indirect reference")
+		libmupdf._wasm_pdf_update_stream(this._doc.pointer, this.pointer, BUFFER(buf), 0)
+	}
+
+	writeRawStream(buf: AnyBuffer) {
+		if (!this.isIndirect())
+			throw new TypeError("can only call PDFObject.writeRawStream on an indirect reference")
+		libmupdf._wasm_pdf_update_stream(this._doc.pointer, this.pointer, BUFFER(buf), 1)
+	}
+
+	resolve() {
+		return this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_resolve_indirect(this.pointer))
+	}
+
+	get length() {
+		return libmupdf._wasm_pdf_array_len(this.pointer)
+	}
+
+	set length(_: number) {
+		throw new TypeError("object length is read-only")
+	}
+
+	_get(path: PDFObjectPath) {
+		let obj = this.pointer
+		for (let key of path) {
+			if (typeof key === "number")
+				obj = libmupdf._wasm_pdf_array_get(obj, key)
+			else if (key instanceof PDFObject)
+				obj = libmupdf._wasm_pdf_dict_get(obj, key.pointer)
+			else
+				obj = libmupdf._wasm_pdf_dict_gets(obj, STRING(key))
+			if (obj === 0)
+				break
+		}
+		return obj
+	}
+
+	get(...path: PDFObjectPath): PDFObject {
+		return this._doc._fromPDFObjectKeep(this._get(path))
+	}
+
+	getInheritable(key: string | PDFObject) {
+		if (key instanceof PDFObject)
+			return this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_dict_get_inheritable(this.pointer, key.pointer))
+		return this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_dict_gets_inheritable(this.pointer, STRING(key)))
+	}
+
+	put(key: number | string | PDFObject, value: any) {
+		value = this._doc._toPDFObject(value)
+		if (typeof key === "number")
+			libmupdf._wasm_pdf_array_put(this.pointer, key, value.pointer)
+		else if (key instanceof PDFObject)
+			libmupdf._wasm_pdf_dict_put(this.pointer, key.pointer, value.pointer)
+		else
+			libmupdf._wasm_pdf_dict_puts(this.pointer, STRING(key), value.pointer)
+		return value
+	}
+
+	push(value: any) {
+		value = this._doc._toPDFObject(value)
+		libmupdf._wasm_pdf_array_push(this.pointer, value.pointer)
+		return value
+	}
+
+	delete(key: number | string | PDFObject) {
+		if (typeof key === "number")
+			libmupdf._wasm_pdf_array_delete(this.pointer, key)
+		else if (key instanceof PDFObject)
+			libmupdf._wasm_pdf_dict_del(this.pointer, key.pointer)
+		else
+			libmupdf._wasm_pdf_dict_dels(this.pointer, STRING(key))
+	}
+
+	override valueOf() {
+		if (this.isNull()) return null
+		if (this.isBoolean()) return this.asBoolean()
+		if (this.isNumber()) return this.asNumber()
+		if (this.isName()) return this.asName()
+		if (this.isString()) return this.asString()
+		if (this.isIndirect()) return `${this.asIndirect()} 0 R`
+		return this
+	}
+
+	override toString(tight = true, ascii = true) {
+		return fromStringFree(libmupdf._wasm_pdf_sprint_obj(this.pointer, tight, ascii))
+	}
+
+	forEach(fn: (val: PDFObject, key: number | string, self: PDFObject) => void) {
+		if (this.isArray()) {
+			let n = this.length
+			for (let i = 0; i < n; ++i)
+				fn(this.get(i), i, this)
+		} else if (this.isDictionary()) {
+			let n = libmupdf._wasm_pdf_dict_len(this.pointer)
+			for (let i = 0; i < n; ++i) {
+				let key = this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_dict_get_key(this.pointer, i))
+				let val = this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_dict_get_val(this.pointer, i))
+				fn(val, key.asName(), this)
+			}
+		}
+	}
+
+	// Convert to plain Javascript values, objects, and arrays.
+	// If you want to resolve indirect references, pass an empty object or array as the first argument.
+	// On exit, this object will contain all indirect objects encountered indexed by object number.
+	// Note: This function will omit cyclic references.
+	asJS(seen?: Record<number,PDFObject>): any {
+		if (this.isIndirect()) {
+			let ref = this.asIndirect()
+			if (!seen)
+				return `${ref} 0 R`
+			if (ref in seen)
+				return seen[ref]
+			seen[ref] = PDFObject.Null // stop recursion!
+			return seen[ref] = this.resolve().asJS(seen)
+		}
+
+		if (this.isArray()) {
+			let result: any[] = []
+			this.forEach(val => {
+				result.push(val.asJS(seen))
+			})
+			return result
+		}
+
+		if (this.isDictionary()) {
+			let result: Record<string,any> = {}
+			this.forEach((val, key) => {
+				result[key] = val.asJS(seen)
+			})
+			return result
+		}
+
+		return this.valueOf()
+	}
+}
+
+export class PDFGraftMap extends Userdata<"pdf_graft_map"> {
+	static override readonly _drop = libmupdf._wasm_pdf_drop_graft_map
+
+	_doc: PDFDocument
+
+	// PRIVATE
+	constructor(doc: PDFDocument, pointer: Pointer<"pdf_graft_map">) {
+		super(pointer)
+		this._doc = doc
+	}
+
+	graftObject(obj: PDFObject) {
+		checkType(obj, PDFObject)
+		return this._doc._fromPDFObjectNew(libmupdf._wasm_pdf_graft_mapped_object(this.pointer, obj.pointer))
+	}
+
+	graftPage(to: number, srcDoc: PDFDocument, srcPage: number) {
+		checkType(to, "number")
+		checkType(srcDoc, PDFDocument)
+		checkType(srcPage, "number")
+		libmupdf._wasm_pdf_graft_mapped_page(this.pointer, to, srcDoc.pointer, srcPage)
+	}
+}
+
+export type PDFAnnotationType =
+	"Text" |
+	"Link" |
+	"FreeText" |
+	"Line" |
+	"Square" |
+	"Circle" |
+	"Polygon" |
+	"PolyLine" |
+	"Highlight" |
+	"Underline" |
+	"Squiggly" |
+	"StrikeOut" |
+	"Redact" |
+	"Stamp" |
+	"Caret" |
+	"Ink" |
+	"Popup" |
+	"FileAttachment" |
+	"Sound" |
+	"Movie" |
+	"RichMedia" |
+	"Widget" |
+	"Screen" |
+	"PrinterMark" |
+	"TrapNet" |
+	"Watermark" |
+	"3D" |
+	"Projection"
+
+export type PDFAnnotationLineEndingStyle =
+	"None" |
+	"Square" |
+	"Circle" |
+	"Diamond" |
+	"OpenArrow" |
+	"ClosedArrow" |
+	"Butt" |
+	"ROpenArrow" |
+	"RClosedArrow" |
+	"Slash"
+
+export type PDFAnnotationBorderStyle = "Solid" | "Dashed" | "Beveled" | "Inset" | "Underline"
+
+export type PDFAnnotationBorderEffect = "None" | "Cloudy"
+
+export type PDFAnnotationIntent =
+	null |
+	"FreeTextCallout" |
+	"FreeTextTypeWriter" |
+	"LineArrow" |
+	"LineDimension" |
+	"PloyLine" |
+	"PolygonCloud" |
+	"PolygonDimension" |
+	"StampImage" |
+	"StampSnapshot"
+
+export class PDFAnnotation extends Userdata<"pdf_annot"> {
+	static override readonly _drop = libmupdf._wasm_pdf_drop_annot
+
+	_doc: PDFDocument
+
+	/* IMPORTANT: Keep in sync with mupdf/pdf/annot.h and PDFAnnotation.java */
+	static readonly ANNOT_TYPES: PDFAnnotationType[] = [
+		"Text",
+		"Link",
+		"FreeText",
+		"Line",
+		"Square",
+		"Circle",
+		"Polygon",
+		"PolyLine",
+		"Highlight",
+		"Underline",
+		"Squiggly",
+		"StrikeOut",
+		"Redact",
+		"Stamp",
+		"Caret",
+		"Ink",
+		"Popup",
+		"FileAttachment",
+		"Sound",
+		"Movie",
+		"RichMedia",
+		"Widget",
+		"Screen",
+		"PrinterMark",
+		"TrapNet",
+		"Watermark",
+		"3D",
+		"Projection",
+	]
+
+	static readonly LINE_ENDING: PDFAnnotationLineEndingStyle[] = [
+		"None",
+		"Square",
+		"Circle",
+		"Diamond",
+		"OpenArrow",
+		"ClosedArrow",
+		"Butt",
+		"ROpenArrow",
+		"RClosedArrow",
+		"Slash",
+	]
+
+	static readonly BORDER_STYLE: PDFAnnotationBorderStyle[] = [ "Solid", "Dashed", "Beveled", "Inset", "Underline" ]
+
+	static readonly BORDER_EFFECT: PDFAnnotationBorderEffect[] = [ "None", "Cloudy" ]
+
+	static readonly INTENT: PDFAnnotationIntent[] = [
+		null,
+		"FreeTextCallout",
+		"FreeTextTypeWriter",
+		"LineArrow",
+		"LineDimension",
+		"PloyLine",
+		"PolygonCloud",
+		"PolygonDimension",
+		"StampImage",
+		"StampSnapshot"
+	]
+
+	// Bit masks for getFlags and setFlags
+	static readonly IS_INVISIBLE = 1 << (1 - 1)
+	static readonly IS_HIDDEN = 1 << (2 - 1)
+	static readonly IS_PRINT = 1 << (3 - 1)
+	static readonly IS_NO_ZOOM = 1 << (4 - 1)
+	static readonly IS_NO_ROTATE = 1 << (5 - 1)
+	static readonly IS_NO_VIEW = 1 << (6 - 1)
+	static readonly IS_READ_ONLY = 1 << (7 - 1)
+	static readonly IS_LOCKED = 1 << (8 - 1)
+	static readonly IS_TOGGLE_NO_VIEW = 1 << (9 - 1)
+	static readonly IS_LOCKED_CONTENTS = 1 << (10 - 1)
+
+	// PRIVATE
+	constructor(doc: PDFDocument, pointer: Pointer<"pdf_annot">) {
+		super(pointer)
+		this._doc = doc
+	}
+
+	getObject() {
+		return this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_annot_obj(this.pointer))
+	}
+
+	getBounds() {
+		return fromRect(libmupdf._wasm_pdf_bound_annot(this.pointer))
+	}
+
+	run(device: Device, matrix: Matrix) {
+		checkType(device, Device)
+		checkMatrix(matrix)
+		libmupdf._wasm_pdf_run_annot(this.pointer, device.pointer, MATRIX(matrix))
+	}
+
+	toPixmap(matrix: Matrix, colorspace: ColorSpace, alpha = false) {
+		checkMatrix(matrix)
+		checkType(colorspace, ColorSpace)
+		return new Pixmap(
+			libmupdf._wasm_pdf_new_pixmap_from_annot(
+				this.pointer,
+				MATRIX(matrix),
+				colorspace.pointer,
+				alpha)
+		)
+	}
+
+	toDisplayList() {
+		return new DisplayList(libmupdf._wasm_pdf_new_display_list_from_annot(this.pointer))
+	}
+
+	update() {
+		return !!libmupdf._wasm_pdf_update_annot(this.pointer)
+	}
+
+	getType() {
+		let type = libmupdf._wasm_pdf_annot_type(this.pointer)
+		return PDFAnnotation.ANNOT_TYPES[type] || "Text"
+	}
+
+	getLanguage() {
+		return fromStringOrNull(libmupdf._wasm_pdf_annot_language(this.pointer))
+	}
+
+	setLanguage(lang: string) {
+		libmupdf._wasm_pdf_set_annot_language(this.pointer, STRING(lang))
+	}
+
+	getFlags() {
+		return libmupdf._wasm_pdf_annot_flags(this.pointer)
+	}
+
+	setFlags(flags: number) {
+		return libmupdf._wasm_pdf_set_annot_flags(this.pointer, flags)
+	}
+
+	getContents() {
+		return fromString(libmupdf._wasm_pdf_annot_contents(this.pointer))
+	}
+
+	setContents(text: string) {
+		libmupdf._wasm_pdf_set_annot_contents(this.pointer, STRING(text))
+	}
+
+	getAuthor() {
+		return fromString(libmupdf._wasm_pdf_annot_author(this.pointer))
+	}
+
+	setAuthor(text: string) {
+		libmupdf._wasm_pdf_set_annot_author(this.pointer, STRING(text))
+	}
+
+	getCreationDate() {
+		return new Date(libmupdf._wasm_pdf_annot_creation_date(this.pointer) * 1000)
+	}
+
+	setCreationDate(date: Date) {
+		checkType(date, Date)
+		libmupdf._wasm_pdf_set_annot_creation_date(this.pointer, date.getTime() / 1000)
+	}
+
+	getModificationDate() {
+		return new Date(libmupdf._wasm_pdf_annot_modification_date(this.pointer) * 1000)
+	}
+
+	setModificationDate(date: Date) {
+		checkType(date, Date)
+		libmupdf._wasm_pdf_set_annot_modification_date(this.pointer, date.getTime() / 1000)
+	}
+
+	hasRect() {
+		return !!libmupdf._wasm_pdf_annot_has_rect(this.pointer)
+	}
+	hasInkList() {
+		return !!libmupdf._wasm_pdf_annot_has_ink_list(this.pointer)
+	}
+	hasQuadPoints() {
+		return !!libmupdf._wasm_pdf_annot_has_quad_points(this.pointer)
+	}
+	hasVertices() {
+		return !!libmupdf._wasm_pdf_annot_has_vertices(this.pointer)
+	}
+	hasLine() {
+		return !!libmupdf._wasm_pdf_annot_has_line(this.pointer)
+	}
+	hasInteriorColor() {
+		return !!libmupdf._wasm_pdf_annot_has_interior_color(this.pointer)
+	}
+	hasLineEndingStyles() {
+		return !!libmupdf._wasm_pdf_annot_has_line_ending_styles(this.pointer)
+	}
+	hasBorder() {
+		return !!libmupdf._wasm_pdf_annot_has_border(this.pointer)
+	}
+	hasBorderEffect() {
+		return !!libmupdf._wasm_pdf_annot_has_border_effect(this.pointer)
+	}
+	hasIcon() {
+		return !!libmupdf._wasm_pdf_annot_has_icon_name(this.pointer)
+	}
+	hasOpen() {
+		return !!libmupdf._wasm_pdf_annot_has_open(this.pointer)
+	}
+	hasAuthor() {
+		return !!libmupdf._wasm_pdf_annot_has_author(this.pointer)
+	}
+	hasFilespec() {
+		return !!libmupdf._wasm_pdf_annot_has_filespec(this.pointer)
+	}
+	hasCallout() {
+		return !!libmupdf._wasm_pdf_annot_has_callout(this.pointer)
+	}
+	hasRichContents() {
+		return !!libmupdf._wasm_pdf_annot_has_rich_contents(this.pointer)
+	}
+
+	getRect() {
+		return fromRect(libmupdf._wasm_pdf_annot_rect(this.pointer))
+	}
+
+	setRect(rect: Rect) {
+		checkRect(rect)
+		libmupdf._wasm_pdf_set_annot_rect(this.pointer, RECT(rect))
+	}
+
+	getPopup() {
+		return fromRect(libmupdf._wasm_pdf_annot_popup(this.pointer))
+	}
+
+	setPopup(rect: Rect) {
+		checkRect(rect)
+		libmupdf._wasm_pdf_set_annot_popup(this.pointer, RECT(rect))
+	}
+
+	getIsOpen() {
+		return !!libmupdf._wasm_pdf_annot_is_open(this.pointer)
+	}
+
+	setIsOpen(isOpen: boolean) {
+		checkType(isOpen, "boolean")
+		libmupdf._wasm_pdf_set_annot_is_open(this.pointer, isOpen)
+	}
+
+	getHiddenForEditing() {
+		return !!libmupdf._wasm_pdf_annot_hidden_for_editing(this.pointer)
+	}
+
+	setHiddenForEditing(isHidden: boolean) {
+		checkType(isHidden, "boolean")
+		libmupdf._wasm_pdf_set_annot_hidden_for_editing(this.pointer, isHidden)
+	}
+
+	getIcon() {
+		return fromString(libmupdf._wasm_pdf_annot_icon_name(this.pointer))
+	}
+
+	setIcon(text: string) {
+		checkType(text, "string")
+		libmupdf._wasm_pdf_set_annot_icon_name(this.pointer, STRING(text))
+	}
+
+	getOpacity() {
+		return libmupdf._wasm_pdf_annot_opacity(this.pointer)
+	}
+
+	setOpacity(opacity: number) {
+		checkType(opacity, "number")
+		libmupdf._wasm_pdf_set_annot_opacity(this.pointer, opacity)
+	}
+
+	getQuadding() {
+		return libmupdf._wasm_pdf_annot_quadding(this.pointer)
+	}
+
+	setQuadding(quadding: number) {
+		checkType(quadding, "number")
+		libmupdf._wasm_pdf_set_annot_quadding(this.pointer, quadding)
+	}
+
+	getLine() {
+		let a = fromPoint(libmupdf._wasm_pdf_annot_line_1(this.pointer))
+		let b = fromPoint(libmupdf._wasm_pdf_annot_line_2(this.pointer))
+		return [ a, b ]
+	}
+
+	setLine(a: Point, b: Point) {
+		checkPoint(a)
+		checkPoint(b)
+		libmupdf._wasm_pdf_set_annot_line(this.pointer, POINT(a), POINT2(b))
+	}
+
+	getLineEndingStyles() {
+		let a = libmupdf._wasm_pdf_annot_line_ending_styles_start(this.pointer)
+		let b = libmupdf._wasm_pdf_annot_line_ending_styles_end(this.pointer)
+		return {
+			start: PDFAnnotation.LINE_ENDING[a] || "None",
+			end: PDFAnnotation.LINE_ENDING[b] || "None",
+		}
+	}
+
+	setLineEndingStyles(start: PDFAnnotationLineEndingStyle, end: PDFAnnotationLineEndingStyle) {
+		let start_ix = ENUM<PDFAnnotationLineEndingStyle>(start, PDFAnnotation.LINE_ENDING)
+		let end_ix = ENUM<PDFAnnotationLineEndingStyle>(end, PDFAnnotation.LINE_ENDING)
+		libmupdf._wasm_pdf_set_annot_line_ending_styles(this.pointer, start_ix, end_ix)
+	}
+
+	getLineCaption() {
+		return libmupdf._wasm_pdf_annot_line_caption(this.pointer)
+	}
+
+	setLineCaption(on: boolean) {
+		return libmupdf._wasm_pdf_set_annot_line_caption(this.pointer, on)
+	}
+
+	getLineCaptionOffset() {
+		return fromPoint(libmupdf._wasm_pdf_annot_line_caption_offset(this.pointer))
+	}
+
+	setLineCaptionOffset(p: Point) {
+		return libmupdf._wasm_pdf_set_annot_line_caption_offset(this.pointer, POINT(p))
+	}
+
+	getLineLeader() {
+		return libmupdf._wasm_pdf_annot_line_leader(this.pointer)
+	}
+
+	getLineLeaderExtension() {
+		return libmupdf._wasm_pdf_annot_line_leader_extension(this.pointer)
+	}
+
+	getLineLeaderOffset() {
+		return libmupdf._wasm_pdf_annot_line_leader_offset(this.pointer)
+	}
+
+	setLineLeader(v: number) {
+		return libmupdf._wasm_pdf_set_annot_line_leader(this.pointer, v)
+	}
+
+	setLineLeaderExtension(v: number) {
+		return libmupdf._wasm_pdf_set_annot_line_leader_extension(this.pointer, v)
+	}
+
+	setLineLeaderOffset(v: number) {
+		return libmupdf._wasm_pdf_set_annot_line_leader_offset(this.pointer, v)
+	}
+
+	getCalloutStyle() {
+		let style = libmupdf._wasm_pdf_annot_callout_style(this.pointer)
+		return PDFAnnotation.LINE_ENDING[style] || "None"
+	}
+
+	setCalloutStyle(style: PDFAnnotationLineEndingStyle) {
+		let style_ix = ENUM<PDFAnnotationLineEndingStyle>(style, PDFAnnotation.LINE_ENDING)
+		libmupdf._wasm_pdf_set_annot_callout_style(this.pointer, style_ix)
+	}
+
+	getCalloutLine() {
+		let n = libmupdf._wasm_pdf_annot_callout_line(this.pointer,
+			(_wasm_point << 2) as Pointer<"fz_point">)
+		if (n == 3)
+			return [
+				fromPoint((_wasm_point+0) << 2 as Pointer<"fz_point">),
+				fromPoint((_wasm_point+1) << 2 as Pointer<"fz_point">),
+				fromPoint((_wasm_point+2) << 2 as Pointer<"fz_point">)
+			]
+		if (n == 2)
+			return [
+				fromPoint((_wasm_point+0) << 2 as Pointer<"fz_point">),
+				fromPoint((_wasm_point+1) << 2 as Pointer<"fz_point">)
+			]
+		return null
+	}
+
+	setCalloutLine(line: Point[]) {
+		let a = line[0] || [0, 0]
+		let b = line[1] || [0, 0]
+		let c = line[2] || [0, 0]
+		libmupdf._wasm_pdf_set_annot_callout_line(this.pointer, line.length, POINT(a), POINT2(b), POINT3(c))
+	}
+
+	getCalloutPoint() {
+		let line = this.getCalloutLine()
+		if (line)
+			return line[0]
+		return undefined
+	}
+
+	setCalloutPoint(p: Point) {
+		libmupdf._wasm_pdf_set_annot_callout_point(this.pointer, POINT(p))
+	}
+
+	getColor() {
+		return fromAnnotColor(libmupdf._wasm_pdf_annot_color(this.pointer, COLOR()))
+	}
+
+	getInteriorColor() {
+		return fromAnnotColor(libmupdf._wasm_pdf_annot_interior_color(this.pointer, COLOR()))
+	}
+
+	setColor(color: AnnotColor) {
+		checkAnnotColor(color)
+		libmupdf._wasm_pdf_set_annot_color(this.pointer, color.length, COLOR(color))
+	}
+
+	setInteriorColor(color: AnnotColor) {
+		checkAnnotColor(color)
+		libmupdf._wasm_pdf_set_annot_interior_color(this.pointer, color.length, COLOR(color))
+	}
+
+	getBorderWidth() {
+		return libmupdf._wasm_pdf_annot_border_width(this.pointer)
+	}
+
+	setBorderWidth(value: number) {
+		checkType(value, "number")
+		return libmupdf._wasm_pdf_set_annot_border_width(this.pointer, value)
+	}
+
+	getBorderStyle() {
+		return PDFAnnotation.BORDER_STYLE[libmupdf._wasm_pdf_annot_border_style(this.pointer)] || "Solid"
+	}
+
+	setBorderStyle(value: PDFAnnotationBorderStyle) {
+		let value_ix = ENUM<PDFAnnotationBorderStyle>(value, PDFAnnotation.BORDER_STYLE)
+		return libmupdf._wasm_pdf_set_annot_border_style(this.pointer, value_ix)
+	}
+
+	getBorderEffect() {
+		return PDFAnnotation.BORDER_EFFECT[libmupdf._wasm_pdf_annot_border_effect(this.pointer)] || "None"
+	}
+
+	setBorderEffect(value: PDFAnnotationBorderEffect) {
+		let value_ix = ENUM<PDFAnnotationBorderEffect>(value, PDFAnnotation.BORDER_EFFECT)
+		return libmupdf._wasm_pdf_set_annot_border_effect(this.pointer, value_ix)
+	}
+
+	getBorderEffectIntensity() {
+		return libmupdf._wasm_pdf_annot_border_effect_intensity(this.pointer)
+	}
+
+	setBorderEffectIntensity(value: number) {
+		checkType(value, "number")
+		return libmupdf._wasm_pdf_set_annot_border_effect_intensity(this.pointer, value)
+	}
+
+	getBorderDashCount() {
+		return libmupdf._wasm_pdf_annot_border_dash_count(this.pointer)
+	}
+
+	getBorderDashItem(idx: number) {
+		return libmupdf._wasm_pdf_annot_border_dash_item(this.pointer, idx)
+	}
+
+	clearBorderDash() {
+		libmupdf._wasm_pdf_clear_annot_border_dash(this.pointer)
+	}
+
+	addBorderDashItem(v: number) {
+		checkType(v, "number")
+		return libmupdf._wasm_pdf_add_annot_border_dash_item(this.pointer, v)
+	}
+
+	getBorderDashPattern() {
+		let n = this.getBorderDashCount()
+		let result = new Array(n)
+		for (let i = 0; i < n; ++i)
+			result[i] = this.getBorderDashItem(i)
+		return result
+	}
+
+	setBorderDashPattern(list: number[]) {
+		this.clearBorderDash()
+		for (let v of list)
+			this.addBorderDashItem(v)
+	}
+
+	getIntent(): PDFAnnotationIntent {
+		return PDFAnnotation.INTENT[libmupdf._wasm_pdf_annot_intent(this.pointer)] || null
+	}
+
+	setIntent(value: PDFAnnotationIntent) {
+		let value_ix = ENUM<PDFAnnotationIntent>(value, PDFAnnotation.INTENT)
+		return libmupdf._wasm_pdf_set_annot_intent(this.pointer, value_ix)
+	}
+
+	setDefaultAppearance(fontName: string, size: number, color: AnnotColor) {
+		checkType(fontName, "string")
+		checkType(size, "number")
+		checkAnnotColor(color)
+		libmupdf._wasm_pdf_set_annot_default_appearance(this.pointer, STRING(fontName), size, color.length, COLOR(color))
+	}
+
+	getDefaultAppearance() {
+		let font = fromString(libmupdf._wasm_pdf_annot_default_appearance_font(this.pointer))
+		let size = libmupdf._wasm_pdf_annot_default_appearance_size(this.pointer)
+		let color = fromAnnotColor(libmupdf._wasm_pdf_annot_default_appearance_color(this.pointer, COLOR()))
+		return { font, size, color }
+	}
+
+	getFileSpec() {
+		return this._doc._fromPDFObjectKeep(libmupdf._wasm_pdf_annot_filespec(this.pointer))
+	}
+
+	setFileSpec(fs: PDFObject) {
+		return libmupdf._wasm_pdf_set_annot_filespec(this.pointer, this._doc._PDFOBJ(fs))
+	}
+
+	getQuadPoints() {
+		let n = libmupdf._wasm_pdf_annot_quad_point_count(this.pointer)
+		let result: Quad[] = []
+		for (let i = 0; i < n; ++i)
+			result.push(fromQuad(libmupdf._wasm_pdf_annot_quad_point(this.pointer, i)))
+		return result
+	}
+
+	clearQuadPoints() {
+		libmupdf._wasm_pdf_clear_annot_quad_points(this.pointer)
+	}
+
+	addQuadPoint(quad: Quad) {
+		checkQuad(quad)
+		libmupdf._wasm_pdf_add_annot_quad_point(this.pointer, QUAD(quad))
+	}
+
+	setQuadPoints(quadlist: Quad[]) {
+		this.clearQuadPoints()
+		for (let quad of quadlist)
+			this.addQuadPoint(quad)
+	}
+
+	getVertices() {
+		let n = libmupdf._wasm_pdf_annot_vertex_count(this.pointer)
+		let result: Point[] = new Array(n)
+		for (let i = 0; i < n; ++i)
+			result[i] = fromPoint(libmupdf._wasm_pdf_annot_vertex(this.pointer, i))
+		return result
+	}
+
+	clearVertices() {
+		libmupdf._wasm_pdf_clear_annot_vertices(this.pointer)
+	}
+
+	addVertex(vertex: Point) {
+		checkPoint(vertex)
+		libmupdf._wasm_pdf_add_annot_vertex(this.pointer, POINT(vertex))
+	}
+
+	setVertices(vertexlist: Point[]) {
+		this.clearVertices()
+		for (let vertex of vertexlist)
+			this.addVertex(vertex)
+	}
+
+	getInkList() {
+		let n = libmupdf._wasm_pdf_annot_ink_list_count(this.pointer)
+		let outer: Point[][] = []
+		for (let i = 0; i < n; ++i) {
+			let m = libmupdf._wasm_pdf_annot_ink_list_stroke_count(this.pointer, i)
+			let inner: Point[] = new Array(m)
+			for (let k = 0; k < m; ++k)
+				inner[k] = fromPoint(libmupdf._wasm_pdf_annot_ink_list_stroke_vertex(this.pointer, i, k))
+			outer.push(inner)
+		}
+		return outer
+	}
+
+	clearInkList() {
+		libmupdf._wasm_pdf_clear_annot_ink_list(this.pointer)
+	}
+
+	addInkListStroke() {
+		libmupdf._wasm_pdf_add_annot_ink_list_stroke(this.pointer)
+	}
+
+	addInkListStrokeVertex(v: Point) {
+		checkPoint(v)
+		libmupdf._wasm_pdf_add_annot_ink_list_stroke_vertex(this.pointer, POINT(v))
+	}
+
+	setInkList(inklist: Point[][]) {
+		this.clearInkList()
+		for (let stroke of inklist) {
+			this.addInkListStroke()
+			for (let vertex of stroke)
+				this.addInkListStrokeVertex(vertex)
+		}
+	}
+
+	getRichContents() {
+		return fromString(libmupdf._wasm_pdf_annot_rich_contents(this.pointer))
+	}
+
+	setRichContents(plain: string, html: string) {
+		checkType(plain, "string")
+		checkType(html, "string")
+		libmupdf._wasm_pdf_set_annot_rich_contents(this.pointer, STRING(plain), STRING2(html))
+	}
+
+	getRichDefaults() {
+		return fromString(libmupdf._wasm_pdf_annot_rich_defaults(this.pointer))
+	}
+
+	setRichDefaults(style: string) {
+		checkType(style, "string")
+		libmupdf._wasm_pdf_set_annot_rich_defaults(this.pointer, STRING(style))
+	}
+
+	setStampImage(image: Image) {
+		libmupdf._wasm_pdf_set_annot_stamp_image(this.pointer, image.pointer)
+	}
+
+	setAppearanceFromDisplayList(appearance: string | null, state: string | null, transform: Matrix, list: DisplayList) {
+		checkMatrix(transform)
+		checkType(list, DisplayList)
+		libmupdf._wasm_pdf_set_annot_appearance_from_display_list(
+			this.pointer,
+			STRING_OPT(appearance),
+			STRING2_OPT(state),
+			MATRIX(transform),
+			list.pointer
+		)
+	}
+
+	setAppearance(appearance: string | null, state: string | null, transform: Matrix, bbox: Rect, resources: any, contents: AnyBuffer) {
+		checkMatrix(transform)
+		checkRect(bbox)
+		libmupdf._wasm_pdf_set_annot_appearance(
+			this.pointer,
+			STRING_OPT(appearance),
+			STRING2_OPT(state),
+			MATRIX(transform),
+			RECT(bbox),
+			this._doc._PDFOBJ(resources),
+			BUFFER(contents)
+		)
+	}
+
+	applyRedaction(black_boxes = 1, image_method = 2, line_art_method = 1, text_method = 0) {
+		libmupdf._wasm_pdf_apply_redaction(this.pointer, black_boxes, image_method, line_art_method, text_method)
+	}
+}
+
+export class PDFWidget extends PDFAnnotation {
+	/* IMPORTANT: Keep in sync with mupdf/pdf/widget.h and PDFWidget.java */
+	static readonly WIDGET_TYPES = [
+		"widget", // unknown
+		"button",
+		"checkbox",
+		"combobox",
+		"listbox",
+		"radiobutton",
+		"signature",
+		"text",
+	]
+
+	/* Field flags */
+	static readonly FIELD_IS_READ_ONLY = 1
+	static readonly FIELD_IS_REQUIRED = 1 << 1
+	static readonly FIELD_IS_NO_EXPORT = 1 << 2
+
+	/* Text fields */
+	static readonly TX_FIELD_IS_MULTILINE = 1 << 12
+	static readonly TX_FIELD_IS_PASSWORD = 1 << 13
+	static readonly TX_FIELD_IS_COMB = 1 << 24
+
+	/* Button fields */
+	static readonly BTN_FIELD_IS_NO_TOGGLE_TO_OFF = 1 << 14
+	static readonly BTN_FIELD_IS_RADIO = 1 << 15
+	static readonly BTN_FIELD_IS_PUSHBUTTON = 1 << 16
+
+	/* Choice fields */
+	static readonly CH_FIELD_IS_COMBO = 1 << 17
+	static readonly CH_FIELD_IS_EDIT = 1 << 18
+	static readonly CH_FIELD_IS_SORT = 1 << 19
+	static readonly CH_FIELD_IS_MULTI_SELECT = 1 << 21
+
+	getFieldType() {
+		return PDFWidget.WIDGET_TYPES[libmupdf._wasm_pdf_annot_field_type(this.pointer)] || "button"
+	}
+
+	isButton() {
+		let type = this.getFieldType()
+		return type === "button" || type === "checkbox" || type === "radiobutton"
+	}
+
+	isPushButton() {
+		return this.getFieldType() === "button"
+	}
+
+	isCheckbox() {
+		return this.getFieldType() === "checkbox"
+	}
+
+	isRadioButton() {
+		return this.getFieldType() === "radiobutton"
+	}
+
+	isText() {
+		return this.getFieldType() === "text"
+	}
+
+	isChoice() {
+		let type = this.getFieldType()
+		return type === "combobox" || type === "listbox"
+	}
+
+	isListBox() {
+		return this.getFieldType() === "listbox"
+	}
+
+	isComboBox() {
+		return this.getFieldType() === "combobox"
+	}
+
+	getFieldFlags() {
+		return libmupdf._wasm_pdf_annot_field_flags(this.pointer)
+	}
+
+	isMultiline() {
+		return (this.getFieldFlags() & PDFWidget.TX_FIELD_IS_MULTILINE) !== 0
+	}
+
+	isPassword() {
+		return (this.getFieldFlags() & PDFWidget.TX_FIELD_IS_PASSWORD) !== 0
+	}
+
+	isComb() {
+		return (this.getFieldFlags() & PDFWidget.TX_FIELD_IS_COMB) !== 0
+	}
+
+	isReadOnly() {
+		return (this.getFieldFlags() & PDFWidget.FIELD_IS_READ_ONLY) !== 0
+	}
+
+	getLabel() {
+		return fromString(libmupdf._wasm_pdf_annot_field_label(this.pointer))
+	}
+
+	getName() {
+		return fromStringFree(libmupdf._wasm_pdf_load_field_name(this.pointer))
+	}
+
+	getValue() {
+		return fromString(libmupdf._wasm_pdf_annot_field_value(this.pointer))
+	}
+
+	setTextValue(value: string) {
+		return libmupdf._wasm_pdf_set_annot_text_field_value(this.pointer, STRING(value))
+	}
+
+	getMaxLen() {
+		return libmupdf._wasm_pdf_annot_text_widget_max_len(this.pointer)
+	}
+
+	setChoiceValue(value: string) {
+		return libmupdf._wasm_pdf_set_annot_choice_field_value(this.pointer, STRING(value))
+	}
+
+	getOptions(isExport=false) {
+		let result: string[] = []
+		let n = libmupdf._wasm_pdf_annot_choice_field_option_count(this.pointer)
+		for (let i = 0; i < n; ++i) {
+			result.push(
+				fromString(
+					libmupdf._wasm_pdf_annot_choice_field_option(this.pointer, isExport, i)
+				)
+			)
+		}
+		return result
+	}
+
+	toggle() {
+		return libmupdf._wasm_pdf_toggle_widget(this.pointer)
+	}
+
+	// Interactive Text Widget editing in a GUI.
+	// TODO: getEditingState()
+	// TODO: setEditingState()
+	// TODO: clearEditingState()
+	// TODO: layoutTextWidget()
+
+	// Interactive form validation Javascript triggers.
+	// NOTE: No embedded PDF Javascript engine in WASM build.
+	// TODO: eventEnter()
+	// TODO: eventExit()
+	// TODO: eventDown()
+	// TODO: eventUp()
+	// TODO: eventFocus()
+	// TODO: eventBlur()
+
+	// NOTE: No OpenSSL support in WASM build.
+	// TODO: isSigned()
+	// TODO: validateSignature()
+	// TODO: checkCertificate()
+	// TODO: checkDigest()
+	// TODO: getSignature()
+	// TODO: previewSignature()
+	// TODO: clearSignature()
+	// TODO: sign()
+}
+
+/* -------------------------------------------------------------------------- */
+
+/* We need a certain level of ugliness to allow callbacks from C to JS */
+
+declare global {
+	function $libmupdf_stm_close(ptr: number): void
+	function $libmupdf_stm_seek(ptr: number, pos: number, offset: number, whence: number): number
+	function $libmupdf_stm_read(ptr: number, pos: number, addr: number, size: number): number
+}
+
+interface StreamHandle {
+	fileSize(): number,
+	read(memory: Uint8Array, offset: number, length: number, position: number): number,
+	close(): void,
+}
+
+var $libmupdf_stm_id = 0
+var $libmupdf_stm_table: Map<number,StreamHandle> = new Map()
+
+globalThis.$libmupdf_stm_close = function (id: number) {
+	let handle = $libmupdf_stm_table.get(id)
+	if (handle) {
+		handle.close()
+		$libmupdf_stm_table.delete(id)
+		return
+	}
+	throw new Error("invalid file handle")
+}
+
+globalThis.$libmupdf_stm_seek = function (id: number, pos: number, offset: number, whence: number) {
+	let handle = $libmupdf_stm_table.get(id)
+	if (handle) {
+		if (whence === 0)
+			return offset
+		if (whence === 1)
+			return pos + offset
+		if (whence === 2) {
+			let size = handle.fileSize()
+			if (size < 0)
+				return -1
+			return size + offset
+		}
+		throw new Error("invalid whence argument")
+	}
+	throw new Error("invalid file handle")
+}
+
+globalThis.$libmupdf_stm_read = function (id: number, pos: number, addr: number, size: number) {
+	let handle = $libmupdf_stm_table.get(id)
+	if (handle) {
+		return handle.read(libmupdf.HEAPU8, addr, size, pos)
+	}
+	throw new Error("invalid file handle")
+}
+
+export class Stream extends Userdata<"fz_stream"> {
+	static override readonly _drop = libmupdf._wasm_drop_stream
+	constructor(handle: StreamHandle) {
+		let id = $libmupdf_stm_id++
+		$libmupdf_stm_table.set(id, handle)
+		super(libmupdf._wasm_new_stream(id))
+	}
+}
+
+/* -------------------------------------------------------------------------- */
+
+var $libmupdf_load_font_file_js: (name: string, script: string, bold: boolean, italic: boolean) => Font | null
+
+declare global {
+	function $libmupdf_load_font_file(name: Pointer<"char">, script: Pointer<"char">, bold: boolean, italic: boolean): Pointer<"fz_font">
+}
+
+globalThis.$libmupdf_load_font_file = function (name, script, bold, italic) {
+	if ($libmupdf_load_font_file_js) {
+		var font = $libmupdf_load_font_file_js(fromString(name), fromString(script), bold, italic)
+		if (font) {
+			checkType(font, Font)
+			return font.pointer
+		}
+	}
+	return 0 as Pointer<"fz_font">
+}
+
+interface DeviceFunctions {
+	drop?(): void,
+	close?(): void,
+
+	fillPath?(path: Path, evenOdd: boolean, ctm: Matrix, colorspace: ColorSpace, color: number[], alpha: number): void,
+	strokePath?(path: Path, stroke: StrokeState, ctm: Matrix, colorspace: ColorSpace, color: number[], alpha: number): void,
+	clipPath?(path: Path, evenOdd: boolean, ctm: Matrix): void,
+	clipStrokePath?(path: Path, stroke: StrokeState, ctm: Matrix): void,
+
+	fillText?(text: Text, ctm: Matrix, colorspace: ColorSpace, color: number[], alpha: number): void,
+	strokeText?(text: Text, stroke: StrokeState, ctm: Matrix, colorspace: ColorSpace, color: number[], alpha: number): void,
+	clipText?(text: Text, ctm: Matrix): void,
+	clipStrokeText?(text: Text, stroke: StrokeState, ctm: Matrix): void,
+	ignoreText?(text: Text, ctm: Matrix): void,
+
+	fillShade?(shade: Shade, ctm: Matrix, alpha: number): void,
+
+	fillImage?(image: Image, ctm: Matrix, alpha: number): void,
+	fillImageMask?(image: Image, ctm: Matrix, colorspace: ColorSpace, color: number[], alpha: number): void,
+	clipImageMask?(image: Image, ctm: Matrix): void,
+
+	popClip?(): void,
+
+	beginMask?(bbox: Rect, luminosity: boolean, colorspace: ColorSpace, color: number[]): void,
+	endMask?(): void,
+
+	beginGroup?(bbox: Rect, colorspace: ColorSpace, isolated: boolean, knockout: boolean, blendmode: BlendMode, alpha: number): void,
+	endGroup?(): void,
+
+	beginTile?(area: Rect, view: Rect, xstep: number, ystep: number, ctm: Matrix, id: number, doc_id: number): number,
+	endTile?(): void,
+
+	beginLayer?(name: string): void,
+	endLayer?(): void,
+}
+
+var $libmupdf_device_id = 0
+var $libmupdf_device_table: Map<number,DeviceFunctions> = new Map()
+
+var $libmupdf_path_id = 0
+var $libmupdf_path_table: Map<number,PathWalker> = new Map()
+
+var $libmupdf_text_id = 0
+var $libmupdf_text_table: Map<number,TextWalker> = new Map()
+
+declare global {
+	var $libmupdf_path_walk: any
+	var $libmupdf_text_walk: any
+	var $libmupdf_device: any
+}
+
+globalThis.$libmupdf_path_walk = {
+	moveto(id: number, x: number, y: number): void {
+		$libmupdf_path_table.get(id)?.moveTo?.(x, y)
+	},
+	lineto(id: number, x: number, y: number): void {
+		$libmupdf_path_table.get(id)?.lineTo?.(x, y)
+	},
+	curveto(id: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void {
+		$libmupdf_path_table.get(id)?.curveTo?.(x1, y1, x2, y2, x3, y3)
+	},
+	closepath(id: number): void {
+		$libmupdf_path_table.get(id)?.closePath?.()
+	},
+}
+
+var $libmupdf_text_font: Font | null = null
+
+globalThis.$libmupdf_text_walk = {
+	begin_span(
+		id: number,
+		font: Pointer<"fz_font">,
+		trm: Pointer<"fz_matrix">,
+		wmode: number,
+		bidi: number,
+		dir: number,
+		lang: Pointer<"char">
+	): void {
+		if (font !== $libmupdf_text_font?.pointer)
+			$libmupdf_text_font = new Font(font)
+		$libmupdf_text_table.get(id)?.beginSpan?.(
+			$libmupdf_text_font,
+			fromMatrix(trm),
+			wmode,
+			bidi,
+			dir,
+			fromString(lang)
+		)
+	},
+	end_span(id: number): void {
+		$libmupdf_text_table.get(id)?.endSpan?.()
+	},
+	show_glyph(
+		id: number,
+		font: Pointer<"fz_font">,
+		trm: Pointer<"fz_matrix">,
+		glyph: number,
+		unicode: number,
+		wmode: number,
+		bidi: number
+	): void {
+		if (font !== $libmupdf_text_font?.pointer)
+			$libmupdf_text_font = new Font(font)
+		$libmupdf_text_table.get(id)?.showGlyph?.(
+			$libmupdf_text_font,
+			fromMatrix(trm),
+			glyph,
+			unicode,
+			wmode,
+			bidi
+		)
+	},
+}
+
+globalThis.$libmupdf_device = {
+	drop_device(id: number): void {
+		$libmupdf_device_table.get(id)?.drop?.()
+		$libmupdf_device_table.delete(id)
+	},
+
+	close_device(id: number): void {
+		$libmupdf_device_table.get(id)?.close?.()
+	},
+
+	fill_path(
+		id: number,
+		path: Pointer<"fz_path">,
+		even_odd: number,
+		ctm: Pointer<"fz_matrix">,
+		colorspace: Pointer<"fz_colorspace">,
+		color_n: number,
+		color_arr: Pointer<"float">,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.fillPath?.(
+			new Path(libmupdf._wasm_keep_path(path)),
+			!!even_odd,
+			fromMatrix(ctm),
+			new ColorSpace(libmupdf._wasm_keep_colorspace(colorspace)),
+			fromColorArray(color_n, color_arr),
+			alpha
+		)
+	},
+
+	clip_path(
+		id: number,
+		path: Pointer<"fz_path">,
+		even_odd: number,
+		ctm: Pointer<"fz_matrix">
+	): void {
+		$libmupdf_device_table.get(id)?.clipPath?.(
+				new Path(libmupdf._wasm_keep_path(path)),
+				!!even_odd,
+				fromMatrix(ctm)
+			)
+	},
+
+	stroke_path(
+		id: number,
+		path: Pointer<"fz_path">,
+		stroke: Pointer<"fz_stroke_state">,
+		ctm: Pointer<"fz_matrix">,
+		colorspace: Pointer<"fz_colorspace">,
+		color_n: number,
+		color_arr: Pointer<"float">,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.strokePath?.(
+			new Path(libmupdf._wasm_keep_path(path)),
+			new StrokeState(libmupdf._wasm_keep_stroke_state(stroke)),
+			fromMatrix(ctm),
+			new ColorSpace(libmupdf._wasm_keep_colorspace(colorspace)),
+			fromColorArray(color_n, color_arr),
+			alpha
+		)
+	},
+
+	clip_stroke_path(
+		id: number,
+		path: Pointer<"fz_path">,
+		stroke: Pointer<"fz_stroke_state">,
+		ctm: Pointer<"fz_matrix">
+	): void {
+		$libmupdf_device_table.get(id)?.clipStrokePath?.(
+			new Path(libmupdf._wasm_keep_path(path)),
+			new StrokeState(libmupdf._wasm_keep_stroke_state(stroke)),
+			fromMatrix(ctm)
+		)
+	},
+
+	fill_text(
+		id: number,
+		text: Pointer<"fz_text">,
+		ctm: Pointer<"fz_matrix">,
+		colorspace: Pointer<"fz_colorspace">,
+		color_n: number,
+		color_arr: Pointer<"float">,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.fillText?.(
+				new Text(libmupdf._wasm_keep_text(text)),
+				fromMatrix(ctm),
+				new ColorSpace(libmupdf._wasm_keep_colorspace(colorspace)),
+				fromColorArray(color_n, color_arr),
+				alpha
+			)
+	},
+
+	stroke_text(
+		id: number,
+		text: Pointer<"fz_text">,
+		stroke: Pointer<"fz_stroke_state">,
+		ctm: Pointer<"fz_matrix">,
+		colorspace: Pointer<"fz_colorspace">,
+		color_n: number,
+		color_arr: Pointer<"float">,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.strokeText?.(
+				new Text(libmupdf._wasm_keep_text(text)),
+				new StrokeState(libmupdf._wasm_keep_stroke_state(stroke)),
+				fromMatrix(ctm),
+				new ColorSpace(libmupdf._wasm_keep_colorspace(colorspace)),
+				fromColorArray(color_n, color_arr),
+				alpha
+			)
+	},
+
+	clip_text(
+		id: number,
+		text: Pointer<"fz_text">,
+		ctm: Pointer<"fz_matrix">
+	): void {
+		$libmupdf_device_table.get(id)?.clipText?.(
+				new Text(libmupdf._wasm_keep_text(text)),
+				fromMatrix(ctm)
+			)
+	},
+
+	clip_stroke_text(
+		id: number,
+		text: Pointer<"fz_text">,
+		stroke: Pointer<"fz_stroke_state">,
+		ctm: Pointer<"fz_matrix">,
+	): void {
+		$libmupdf_device_table.get(id)?.clipStrokeText?.(
+				new Text(libmupdf._wasm_keep_text(text)),
+				new StrokeState(libmupdf._wasm_keep_stroke_state(stroke)),
+				fromMatrix(ctm)
+			)
+	},
+
+	ignore_text(
+		id: number,
+		text: Pointer<"fz_text">,
+		ctm: Pointer<"fz_matrix">
+	): void {
+		$libmupdf_device_table.get(id)?.ignoreText?.(
+				new Text(libmupdf._wasm_keep_text(text)),
+				fromMatrix(ctm)
+			)
+	},
+
+	fill_shade(
+		id: number,
+		shade: Pointer<"fz_shade">,
+		ctm: Pointer<"fz_matrix">,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.fillShade?.(
+				new Shade(shade),
+				fromMatrix(ctm),
+				alpha
+			)
+	},
+
+	fill_image(
+		id: number,
+		image: Pointer<"fz_image">,
+		ctm: Pointer<"fz_matrix">,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.fillImage?.(
+			new Image(image),
+			fromMatrix(ctm),
+			alpha
+		)
+	},
+
+	fill_image_mask(
+		id: number,
+		image: Pointer<"fz_image">,
+		ctm: Pointer<"fz_matrix">,
+		colorspace: Pointer<"fz_colorspace">,
+		color_n: number,
+		color_arr: Pointer<"float">,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.fillImageMask?.(
+			new Image(image),
+			fromMatrix(ctm),
+			new ColorSpace(libmupdf._wasm_keep_colorspace(colorspace)),
+			fromColorArray(color_n, color_arr),
+			alpha
+		)
+	},
+
+	clip_image_mask(
+		id: number,
+		image: Pointer<"fz_image">,
+		ctm: Pointer<"fz_matrix">
+	): void {
+		$libmupdf_device_table.get(id)?.clipImageMask?.(
+			new Image(image),
+			fromMatrix(ctm)
+		)
+	},
+
+	pop_clip(id: number): void {
+		$libmupdf_device_table.get(id)?.popClip?.()
+	},
+
+	begin_mask(
+		id: number,
+		bbox: Pointer<"fz_rect">,
+		luminosity: number,
+		colorspace: Pointer<"fz_colorspace">,
+		color_n: number,
+		color_arr: Pointer<"float">
+	): void {
+		$libmupdf_device_table.get(id)?.beginMask?.(
+			fromRect(bbox),
+			!!luminosity,
+			new ColorSpace(libmupdf._wasm_keep_colorspace(colorspace)),
+			fromColorArray(color_n, color_arr)
+		)
+	},
+
+	begin_group(
+		id: number,
+		bbox: Pointer<"fz_rect">,
+		colorspace: Pointer<"fz_colorspace">,
+		isolated: number,
+		knockout: number,
+		blendmode: number,
+		alpha: number
+	): void {
+		$libmupdf_device_table.get(id)?.beginGroup?.(
+			fromRect(bbox),
+			new ColorSpace(libmupdf._wasm_keep_colorspace(colorspace)),
+			!!isolated,
+			!!knockout,
+			Device.BLEND_MODES[blendmode] as BlendMode,
+			alpha
+		)
+	},
+
+	begin_tile(
+		id: number,
+		area: Pointer<"fz_rect">,
+		view: Pointer<"fz_rect">,
+		xstep: number,
+		ystep: number,
+		ctm: Pointer<"fz_matrix">,
+		tile_id: number,
+		doc_id: number
+	): number {
+		return $libmupdf_device_table.get(id)?.beginTile?.(
+			fromRect(area),
+			fromRect(view),
+			xstep,
+			ystep,
+			fromMatrix(ctm),
+			tile_id,
+			doc_id
+		) || 0
+	},
+
+	begin_layer(id: number, name: Pointer<"char">): void {
+		$libmupdf_device_table.get(id)?.beginLayer?.(
+			fromString(name)
+		)
+	},
+
+	end_mask(id: number): void {
+		$libmupdf_device_table.get(id)?.endMask?.()
+	},
+
+	end_group(id: number): void {
+		$libmupdf_device_table.get(id)?.endGroup?.()
+	},
+
+	end_tile(id: number): void {
+		$libmupdf_device_table.get(id)?.endTile?.()
+	},
+
+	end_layer(id: number): void {
+		$libmupdf_device_table.get(id)?.endLayer?.()
+	},
+
+}
+
+/* default exports */
+export default {
+	// const
+	Matrix,
+	Rect,
+
+	// function
+	enableICC,
+	disableICC,
+	setUserCSS,
+	installLoadFontFunction,
+
+	// class
+	Buffer,
+	ColorSpace,
+	Font,
+	Image,
+	StrokeState,
+	Path,
+	Text,
+	DisplayList,
+	Pixmap,
+	Shade,
+	StructuredText,
+	Device,
+	DrawDevice,
+	DisplayListDevice,
+	DocumentWriter,
+	Document,
+	OutlineIterator,
+	Link,
+	Page,
+	PDFDocument,
+	PDFPage,
+	PDFObject,
+	PDFGraftMap,
+	PDFAnnotation,
+	PDFWidget,
+	Stream,
+
+	// debugging
+	memento
+}