sumatrapdf Documentation

C c & c-plus-plus repository by @sumatrapdfreader. Model Context Protocol (MCP) server & tools with 18k stars.

17,753 stars C C 1 file · ~131,184 tokens #c#c-plus-plus#pdf#pdf-viewer#win32
RAW Doc

Technical Documentation: sumatrapdfreader/sumatrapdf

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Ext Mupdf Docs Reference C Overview (`ext/mupdf/docs/reference/c/overview.md`)

Overview

Basic MuPDF usage example

To limit the complexity and give an easier introduction
this code has no error handling at all, but any serious piece of code
using MuPDF should use the error handling strategies described below.

Common function arguments

Most functions in MuPDF's interface take a context argument.

A context contains global state used by MuPDF inside functions when
parsing or rendering pages of the document. It contains for example:

  • an exception stack (see error handling below),
  • a memory allocator (allowing for custom allocators)
  • a resource store (for caching of images, fonts, etc.)
  • a set of locks and (un-)locking functions (for multi-threading)

Without the set of locks and accompanying functions the context and
its proxies may only be used in a single-threaded application.

Error handling

MuPDF uses a set of exception handling macros to simplify error return
and cleanup. Conceptually, they work a lot like C++'s try/catch
system, but do not require any special compiler support.

The basic formulation is as follows:

text
fz_try(ctx)
{
	// Try to perform a task. Never 'return', 'goto' or
	// 'longjmp' out of here. 'break' may be used to
	// safely exit (just) the try block scope.
}
fz_always(ctx)
{
	// Any code here is always executed, regardless of
	// whether an exception was thrown within the try or
	// not. Never 'return', 'goto' or longjmp out from
	// here. 'break' may be used to safely exit (just) the
	// always block scope.
}
fz_catch(ctx)
{
	// This code is called (after any always block) only
	// if something within the fz_try block (including any
	// functions it called) threw an exception. The code
	// here is expected to handle the exception (maybe
	// record/report the error, cleanup any stray state
	// etc) and can then either exit the block, or pass on
	// the exception to a higher level (enclosing) fz_try
	// block (using fz_throw, or fz_rethrow).
}

The fz_always block is optional, and can safely be omitted.

The macro based nature of this system has 3 main limitations:

  1. Never return from within try (or 'goto' or longjmp out of it).
    This upsets the internal housekeeping of the macros and will
    cause problems later on. The code will detect such things
    happening, but by then it is too late to give a helpful error
    report as to where the original infraction occurred.

  2. The try/always/catch statement is not one atomic C statement.

    The fz_try(ctx) { ... } fz_always(ctx) { ... } fz_catch(ctx) { ... } is not one atomic C statement.
    That is to say, if you do:

    text
    if (condition)
         fz_try(ctx) { ... }
         fz_catch(ctx) { ... }

    then you will not get what you want. Use the following instead:

    text
    if (condition) {
         fz_try(ctx) { ... }
         fz_catch(ctx) { ... }
     }
  3. The macros are implemented using setjmp and longjmp, and so
    the standard C restrictions on the use of those functions
    apply to fz_try/fz_catch too.

    In particular, any "truly local"
    variable that is set between the start of fz_try and something
    in fz_try throwing an exception may become undefined as part
    of the process of throwing that exception.

As a way of mitigating this problem, we provide a fz_var()
macro that tells the compiler to ensure that that variable is
not unset by the act of throwing the exception.

A model piece of code using these macros then might be:

text
house build_house(plans *p)
{
	material m = NULL;
	walls w = NULL;
	roof r = NULL;
	house h = NULL;
	tiles t = make_tiles();

	fz_var(w);
	fz_var(r);
	fz_var(h);

	fz_try(ctx)
	{
		fz_try(ctx)
		{
			m = make_bricks();
		}
		fz_catch(ctx)
		{
			// No bricks available, make do with straw?
			m = make_straw();
		}
		w = make_walls(m, p);
		r = make_roof(m, t);
		// Note, NOT: return combine(w,r);
		h = combine(w, r);
	}
	fz_always(ctx)
	{
		drop_walls(w);
		drop_roof(r);
		drop_material(m);
		drop_tiles(t);
	}
	fz_catch(ctx)
	{
		fz_throw(ctx, "build_house failed");
	}
	return h;
}

Things to note about this:

  1. If make_tiles throws an exception, this will immediately be
    handled by some higher level exception handler. If it
    succeeds, t will be set before fz_try starts, so there is no
    need to fz_var(t);

  2. We try first off to make some bricks as our building material.
    If this fails, we fall back to straw. If this fails, we'll end
    up in the fz_catch, and the process will fail neatly.

  3. We assume in this code that combine takes new reference to
    both the walls and the roof it uses, and therefore that w and
    r need to be cleaned up in all cases.

  4. We assume the standard C convention that it is safe to destroy
    NULL things.

Multi-threading

First off, study the basic usage example in docs/examples/example.c
and make sure you understand how it works as the data structures manipulated
there will be referred to in this section too.

MuPDF can usefully be built into a multi-threaded application without
the library needing to know anything threading at all. If the library
opens a document in one thread, and then sits there as a 'server'
requesting pages and rendering them for other threads that need them,
then the library is only ever being called from this one thread.

Other threads can still be used to handle UI requests etc, but as far
as MuPDF is concerned it is only being used in a single threaded way.
In this instance, there are no threading issues with MuPDF at all,
and it can safely be used without any locking, as described in the
previous sections.

This section will attempt to explain how to use MuPDF in the more
complex case; where we genuinely want to call the MuPDF library
concurrently from multiple threads within a single application.

MuPDF can be invoked with a user supplied set of locking functions.
It uses these to take mutexes around operations that would conflict
if performed concurrently in multiple threads. By leaving the
exact implementation of locks to the caller MuPDF remains threading
library agnostic.

The following simple rules should be followed to ensure that
multi-threaded operations run smoothly:

  1. "No simultaneous calls to MuPDF in different threads are
    allowed to use the same context."

    Most of the time it is simplest to just use a different
    context for every thread; just create a new context at the
    same time as you create the thread. For more details see
    "Cloning the context" below.

  2. "No simultaneous calls to MuPDF in different threads are
    allowed to use the same document."

    Only one thread can be accessing a document at a time, but
    once display lists are created from that document, multiple
    threads at a time can operate on them.

    The document can be used from several different threads as
    long as there are safeguards in place to prevent the usages
    being simultaneous.

  3. "No simultaneous calls to MuPDF in different threads are
    allowed to use the same device."

    Calling a device simultaneously from different threads will
    cause it to get confused and may crash. Calling a device from
    several different threads is perfectly acceptable as long as
    there are safeguards in place to prevent the calls being
    simultaneous.

  4. "An fz_locks_context must be supplied at context creation time,
    unless MuPDF is to be used purely in a single thread at a time."

    MuPDF needs to protect against unsafe access to certain structures/
    resources/libraries from multiple threads. It does this by using
    the user supplied locking functions. This holds true even when
    using completely separate instances of MuPDF.

  5. "All contexts in use must share the same fz_locks_context (or
    the underlying locks thereof)."

    We strongly recommend that fz_new_context is called just once,
    and fz_clone_context is called to generate new contexts from
    that. This will automatically ensure that the same locking
    mechanism is used in all MuPDF instances. For now, we do support
    multiple completely independent contexts being created using
    repeated calls to fz_new_context, but these MUST share the
    same fz_locks_context (or at least depend upon the same underlying
    locks). The facility to create different independent contexts
    may be removed in future.

So, how does a multi-threaded example differ from a non-multithreaded
one?

Firstly, when we create the first context, we call fz_new_context
as before, but the second argument should be a pointer to a set
of locking functions.

The calling code should provide FZ_LOCK_MAX mutexes, which will be
locked/unlocked by MuPDF calling the lock/unlock function pointers
in the supplied structure with the user pointer from the structure
and the lock number, i (0 <= i < FZ_LOCK_MAX). These mutexes can
safely be recursive or non-recursive as MuPDF only calls in a non-
recursive style.

To make subsequent contexts, the user should NOT call fz_new_context
again (as this will fail to share important resources such as the
store and glyphcache), but should rather call fz_clone_context.
Each of these cloned contexts can be freed by fz_free_context as
usual. They will share the important data structures (like store,
glyph cache etc) with the original context, but will have their
own exception stacks.

To open a document, call fz_open_document as usual, passing a context
and a filename. It is important to realise that only one thread at a
time can be accessing the documents itself.

This means that only one thread at a time can perform operations such
as fetching a page, or rendering that page to a display list. Once a
display list has been obtained however, it can be rendered from any
other thread (or even from several threads simultaneously, giving
banded rendering).

This means that an implementer has 2 basic choices when constructing
an application to use MuPDF in multi-threaded mode. Either he can
construct it so that a single nominated thread opens the document
and then acts as a 'server' creating display lists for other threads
to render, or he can add his own mutex around calls to mupdf that
use the document. The former is likely to be far more efficient in
the long run.

For an example of how to do multi-threading see
docs/examples/multi-threaded.c
which has a main thread and one rendering thread per page.

Cloning the context

As described above, every context contains an exception stack which is
manipulated during the course of nested fz_try/fz_catches. For obvious
reasons the same exception stack cannot be used from more than one
thread at a time.

If, however, we simply created a new context (using fz_new_context) for
every thread, we would end up with separate stores/glyph caches etc,
which is not (generally) what is desired. MuPDF therefore provides a
mechanism for "cloning" a context. This creates a new context that
shares everything with the given context, except for the exception
stack.

A commonly used general scheme is therefore to create a 'base' context
at program start up, and to clone this repeatedly to get new contexts
that can be used on new threads.


Ext Mupdf Docs Reference C Fitz Api Overview (`ext/mupdf/docs/reference/c/fitz/api-overview.md`)

API Overview

The MuPDF fitz library is the core C API for rendering and manipulating PDF,
XPS, EPUB, and other document formats. The public API is exposed through the
include/mupdf/fitz/ header files, all of which are collected under the
umbrella header mupdf/fitz.h.

The library is organised into functional groups:

  • Core – context, geometry, system utilities
  • I/O – buffers, streams, compression, archives
  • Resources – pixmaps, images, bitmaps, fonts, colour spaces, paths
  • Rendering – devices, display lists, structured text
  • Document – documents, pages, links, outlines, bookmarks
  • Utilities – high-level helpers combining the above

Note: All functions that allocate memory or may fail take an fz_context *ctx
as their first argument. Errors are signalled via setjmp/longjmp
exception handling; callers should wrap operations in
fz_try / fz_catch blocks.

📒 For more see the full {doc}../api/index.


mupdf/fitz.h – Umbrella Header

c
#include "mupdf/fitz.h"

This single header pulls in the entire public fitz API. It is the recommended
way to include MuPDF in your project.

Include groups (in order of declaration):

  • Core: version.h, config.h, system.h, context.h, output.h, log.h
  • Utilities: crypt.h, getopt.h, geometry.h, hash.h, pool.h,
    string-util.h, tree.h, bidi.h, xml.h, json.h, hyphen.h
  • I/O: buffer.h, stream.h, compress.h, compressed-buffer.h,
    filter.h, archive.h, heap.h
  • Resources: store.h, color.h, pixmap.h, image.h, bitmap.h,
    shade.h, font.h, path.h, text.h, separation.h, glyph.h
  • Rendering: device.h, display-list.h, structured-text.h,
    transition.h, glyph-cache.h
  • Document: link.h, outline.h, document.h

context.h – Context & Error Handling

c
#include "mupdf/fitz/context.h"

The fz_context is the central state object passed to virtually every fitz
function. It holds the allocator, exception stack, and resource caches.

Exception Macros

c
fz_try(ctx)      { /* code that may throw */ }
fz_catch(ctx)    { /* handle the error */ }
fz_always(ctx)   { /* always executed (cleanup) */ }

fz_throw(ctx, FZ_ERROR_GENERIC, "format %s", msg);
fz_rethrow(ctx);
fz_warn(ctx, "warning: %s", msg);

Error codes (selected)

Constant Meaning
FZ_ERROR_NONE No error.
FZ_ERROR_GENERIC General runtime error.
FZ_ERROR_ARGUMENT Invalid argument passed to a function.
FZ_ERROR_MEMORY Memory allocation failure.
FZ_ERROR_IO I/O failure.
FZ_ERROR_FORMAT Document format error.

Context lifecycle

fz_context *fz_new_context(const fz_alloc_context *alloc, const fz_locks_context *locks, size_t max_store)

Create a new fitz context.

  • alloc – Custom allocator, or NULL for the system default.
  • locks – Locking callbacks for thread safety, or NULL.
  • max_store – Maximum size of the resource store in bytes (e.g. FZ_STORE_DEFAULT).
  • Returns: A new fz_context, or NULL on failure.

fz_context *fz_clone_context(fz_context *ctx)

Clone a context for use in a new thread. Shares the same resource store
and locks as the original.

  • ctx – The context to clone.
  • Returns: A new context; must be freed with fz_drop_context.

void fz_drop_context(fz_context *ctx)

Free a context and all associated resources. Never throws exceptions.

  • ctx – The context to destroy.

Memory helpers

void *fz_malloc(fz_context *ctx, size_t size)

Allocate size bytes. Throws FZ_ERROR_MEMORY on failure.


void *fz_calloc(fz_context *ctx, size_t count, size_t size)

Allocate and zero-initialise count * size bytes.


void fz_free(fz_context *ctx, void *p)

Free memory previously allocated with fz_malloc / fz_calloc.


geometry.h – Geometric Types

c
#include "mupdf/fitz/geometry.h"

Types

fz_point – A 2-D point.

c
typedef struct { float x, y; } fz_point;

fz_rect – An axis-aligned rectangle with float coordinates.

c
typedef struct { float x0, y0, x1, y1; } fz_rect;

The fz_empty_rect and fz_infinite_rect constants are provided for convenience.

fz_irect – An axis-aligned rectangle with int coordinates (used for pixmap bounds).

c
typedef struct { int x0, y0, x1, y1; } fz_irect;

fz_matrix – A 3×2 affine transformation matrix.

c
typedef struct { float a, b, c, d, e, f; } fz_matrix;

Pre-defined matrices: fz_identity.

fz_quad – A quadrilateral (four corners), used for text hit quads.

c
typedef struct { fz_point ul, ur, ll, lr; } fz_quad;

Selected functions

fz_matrix fz_scale(float sx, float sy) – Create a scaling matrix.

fz_matrix fz_rotate(float degrees) – Create a rotation matrix (counter-clockwise, in degrees).

fz_matrix fz_translate(float tx, float ty) – Create a translation matrix.

fz_matrix fz_concat(fz_matrix left, fz_matrix right) – Concatenate two matrices (left is applied first).

fz_matrix fz_invert_matrix(fz_matrix m) – Invert a matrix. Returns fz_identity if the matrix is singular.

fz_point fz_transform_point(fz_point p, fz_matrix m) – Transform a point by a matrix.

fz_rect fz_transform_rect(fz_rect r, fz_matrix m) – Transform a rectangle by a matrix (returns the bounding box of the result).

fz_rect fz_intersect_rect(fz_rect a, fz_rect b) – Return the intersection of two rectangles.

fz_rect fz_union_rect(fz_rect a, fz_rect b) – Return the smallest rectangle containing both a and b.

fz_irect fz_round_rect(fz_rect r) – Convert a float rect to an integer rect by rounding outwards.

int fz_is_empty_rect(fz_rect r) – Return non-zero if the rectangle is empty.

int fz_is_infinite_rect(fz_rect r) – Return non-zero if the rectangle is the infinite rectangle.


archive.h – Archive Access

c
#include "mupdf/fitz/archive.h"

An fz_archive provides a uniform interface over ZIP files, TAR files, and
plain directories.

fz_archive – Opaque handle to an archive.


fz_archive *fz_open_archive(fz_context *ctx, const char *filename)

Open a ZIP or TAR archive by filename. The archive type is detected
automatically from the file signature.

  • ctx – Active fitz context.
  • filename – Path to the archive file.
  • Returns: A new fz_archive whose ownership is returned to the caller.
  • Raises: FZ_ERROR_IO if the file cannot be opened.
  • Raises: FZ_ERROR_FORMAT if the file format is not recognised.

fz_archive *fz_open_archive_with_stream(fz_context *ctx, fz_stream *file) – Open an archive from an existing stream.

fz_archive *fz_open_directory(fz_context *ctx, const char *path) – Open a directory as an archive.

int fz_is_directory(fz_context *ctx, fz_archive *arch) – Return non-zero if the archive is backed by a directory.

int fz_has_archive_entry(fz_context *ctx, fz_archive *arch, const char *name) – Return non-zero if name exists in the archive.

int fz_count_archive_entries(fz_context *ctx, fz_archive *arch) – Return the number of entries in the archive.

const char *fz_list_archive_entry(fz_context *ctx, fz_archive *arch, int idx) – Return the name of entry idx.

fz_stream *fz_open_archive_entry(fz_context *ctx, fz_archive *arch, const char *name) – Open an archive entry as a stream.

fz_buffer *fz_read_archive_entry(fz_context *ctx, fz_archive *arch, const char *name) – Read an archive entry entirely into a buffer.

void fz_drop_archive(fz_context *ctx, fz_archive *arch) – Release an archive.


pixmap.h – Raster Images (Pixmaps)

c
#include "mupdf/fitz/pixmap.h"

A fz_pixmap represents a raster image: a rectangular region of pixels, each
with n colour components (process colours + spot colours + optional alpha) stored
contiguously in row-major order. Samples are in premultiplied alpha during
rendering, but non-premultiplied for colorspace conversions.

fz_pixmap – Opaque pixmap type. Key fields accessible via accessor functions:

  • width / height (pixels)
  • x / y origin (in device space)
  • n (components per pixel)
  • alpha (1 if alpha channel present, else 0)
  • stride (bytes per row)
  • samples (unsigned char * pixel data)
  • colorspace (fz_colorspace *)
  • xres / yres (resolution in dpi)

Creating pixmaps

fz_pixmap *fz_new_pixmap(fz_context *ctx, fz_colorspace *cs, int w, int h, fz_separations *seps, int alpha)

Allocate a pixmap of size w × h.

  • cs – Colour space (or NULL for alpha-only).
  • seps – Spot separations, or NULL.
  • alpha – Non-zero to include an alpha channel.

fz_pixmap *fz_new_pixmap_with_bbox(fz_context *ctx, fz_colorspace *cs, fz_irect bbox, fz_separations *seps, int alpha) – Allocate a pixmap covering bbox (sets the x/y origin accordingly).

fz_pixmap *fz_new_pixmap_with_data(fz_context *ctx, fz_colorspace *cs, int w, int h, fz_separations *seps, int alpha, int stride, unsigned char *samples) – Wrap an existing sample buffer; the buffer is not freed on drop.

fz_pixmap *fz_new_pixmap_with_bbox_and_data(fz_context *ctx, fz_colorspace *cs, fz_irect bbox, fz_separations *seps, int alpha, unsigned char *samples) – Wrap an existing buffer with a bounding box origin.

Reference counting

fz_pixmap *fz_keep_pixmap(fz_context *ctx, fz_pixmap *pix) – Increment the reference count. Returns the same pointer. Never throws exceptions.

void fz_drop_pixmap(fz_context *ctx, fz_pixmap *pix) – Decrement the reference count; the pixmap is freed when it reaches zero. Never throws exceptions.

Accessors

fz_irect fz_pixmap_bbox(fz_context *ctx, const fz_pixmap *pix) – Return the bounding box of the pixmap.

int fz_pixmap_width(fz_context *ctx, const fz_pixmap *pix) – Return the width in pixels.

int fz_pixmap_height(fz_context *ctx, const fz_pixmap *pix) – Return the height in pixels.

int fz_pixmap_x(fz_context *ctx, const fz_pixmap *pix) – Return the x origin of the pixmap (left edge, in device space).

int fz_pixmap_y(fz_context *ctx, const fz_pixmap *pix) – Return the y origin of the pixmap.

int fz_pixmap_stride(fz_context *ctx, const fz_pixmap *pix) – Return the stride (bytes per row).

int fz_pixmap_components(fz_context *ctx, const fz_pixmap *pix) – Return the total number of components (including alpha if present).

unsigned char *fz_pixmap_samples(fz_context *ctx, fz_pixmap *pix) – Return a pointer to the raw sample data.

fz_colorspace *fz_pixmap_colorspace(fz_context *ctx, fz_pixmap *pix) – Return the colour space of the pixmap (may be NULL).

Manipulation

void fz_clear_pixmap(fz_context *ctx, fz_pixmap *pix) – Set all components (including alpha) of all pixels to zero.

void fz_clear_pixmap_with_value(fz_context *ctx, fz_pixmap *pix, int value) – Clear all pixels to value (0–255); alpha is always set to 255 (opaque).

void fz_fill_pixmap_with_color(fz_context *ctx, fz_pixmap *pix, fz_colorspace *cs, float *color, fz_color_params color_params) – Fill the pixmap with a solid colour.

void fz_copy_pixmap_rect(fz_context *ctx, fz_pixmap *dest, fz_pixmap *src, fz_irect r, const fz_default_colorspaces *default_cs) – Copy a rectangular region from src to dest.

fz_pixmap *fz_clone_pixmap(fz_context *ctx, const fz_pixmap *old) – Clone a pixmap to new storage. The reference count of old is unchanged.


fz_pixmap *fz_convert_pixmap(fz_context *ctx, const fz_pixmap *pix, fz_colorspace *cs_des, fz_colorspace *prf, fz_default_colorspaces *default_cs, fz_color_params color_params, int keep_alpha)

Convert the pixmap to a different colour space.

  • cs_des – Destination colour space (NULL = alpha only).
  • prf – Proofing colour space (or NULL).
  • keep_alpha – If 0, alpha is stripped.

fz_pixmap *fz_scale_pixmap(fz_context *ctx, fz_pixmap *src, float x, float y, float w, float h, const fz_irect *clip) – Scale a pixmap to the given w × h size, optionally clipping.

void fz_invert_pixmap(fz_context *ctx, fz_pixmap *pix) – Invert all colour components (not alpha) in place.

void fz_gamma_pixmap(fz_context *ctx, fz_pixmap *pix, float gamma) – Apply gamma correction.

int fz_is_pixmap_monochrome(fz_context *ctx, fz_pixmap *pix) – Return non-zero if the pixmap contains only values 0 and 255.


bitmap.h – 1-bit Bitmaps

c
#include "mupdf/fitz/bitmap.h"

A fz_bitmap stores 1 bit per component, used for halftoned output (e.g. for
sending to a printer). Samples are stored MSB-first, compatible with PBM format.

fz_bitmap

c
typedef struct {
    int refs;
    int w, h, stride, n;
    int xres, yres;
    unsigned char *samples;
} fz_bitmap;

fz_bitmap *fz_keep_bitmap(fz_context *ctx, fz_bitmap *bit) – Increment the reference count. Never throws exceptions.

void fz_drop_bitmap(fz_context *ctx, fz_bitmap *bit) – Decrement the reference count; bitmap is freed when it reaches zero. Never throws exceptions.

fz_bitmap *fz_new_bitmap_from_pixmap(fz_context *ctx, fz_pixmap *pix, fz_halftone *ht) – Convert a pixmap to a halftoned 1-bit bitmap.

fz_bitmap *fz_new_bitmap_from_pixmap_band(fz_context *ctx, fz_pixmap *pix, fz_halftone *ht, int band_start) – Same as above, rendering a single horizontal band.


path.h – Vector Paths

c
#include "mupdf/fitz/path.h"

Vector paths can be filled or stroked, and may have a dash pattern.

fz_path – Opaque path type.

fz_linecap

c
typedef enum {
    FZ_LINECAP_BUTT     = 0,
    FZ_LINECAP_ROUND    = 1,
    FZ_LINECAP_SQUARE   = 2,
    FZ_LINECAP_TRIANGLE = 3,
} fz_linecap;

fz_linejoin

c
typedef enum {
    FZ_LINEJOIN_MITER     = 0,
    FZ_LINEJOIN_ROUND     = 1,
    FZ_LINEJOIN_BEVEL     = 2,
    FZ_LINEJOIN_MITER_XPS = 3,
} fz_linejoin;

fz_stroke_state – Stroke parameters:

c
typedef struct {
    int refs;
    fz_linecap start_cap, dash_cap, end_cap;
    fz_linejoin linejoin;
    float linewidth;
    float miterlimit;
    float dash_phase;
    int dash_len;
    float dash_list[FZ_FLEXIBLE_ARRAY];
} fz_stroke_state;

Path construction

fz_path *fz_new_path(fz_context *ctx) – Create a new empty path.

fz_path *fz_keep_path(fz_context *ctx, const fz_path *path) – Increment the reference count.

void fz_drop_path(fz_context *ctx, const fz_path *path) – Decrement the reference count; frees when zero.

void fz_moveto(fz_context *ctx, fz_path *path, float x, float y) – Start a new subpath at (x, y).

void fz_lineto(fz_context *ctx, fz_path *path, float x, float y) – Add a straight line segment to (x, y).

void fz_curveto(fz_context *ctx, fz_path *path, float cx1, float cy1, float cx2, float cy2, float ex, float ey) – Add a cubic Bézier curve.

void fz_closepath(fz_context *ctx, fz_path *path) – Close the current subpath.

void fz_rectto(fz_context *ctx, fz_path *path, float x1, float y1, float x2, float y2) – Add a rectangle as a closed subpath.

fz_rect fz_bound_path(fz_context *ctx, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm) – Return the bounding box of a path (for fill or, if stroke is not NULL, stroke).

fz_path *fz_clone_path(fz_context *ctx, fz_path *old) – Return a writable clone of a (possibly shared) path.

Stroke state

fz_stroke_state *fz_new_stroke_state(fz_context *ctx) – Allocate a default stroke state (1pt width, butt caps, miter join).

fz_stroke_state *fz_keep_stroke_state(fz_context *ctx, const fz_stroke_state *stroke) – Increment the reference count.

void fz_drop_stroke_state(fz_context *ctx, const fz_stroke_state *stroke) – Decrement the reference count; frees when zero.


document.h – Documents & Pages

c
#include "mupdf/fitz/document.h"

Types

fz_document – Opaque document handle.

fz_page – Opaque page handle.

fz_location – A document location (chapter + page).

c
typedef struct { int chapter, page; } fz_location;

fz_bookmark – An opaque bookmark (intptr_t).

fz_box_type – Page box type.

c
typedef enum {
    FZ_MEDIA_BOX,
    FZ_CROP_BOX,
    FZ_BLEED_BOX,
    FZ_TRIM_BOX,
    FZ_ART_BOX,
    FZ_UNKNOWN_BOX,
} fz_box_type;

Opening documents

fz_document *fz_open_document(fz_context *ctx, const char *filename)

Open a document from filename. The format is detected automatically.

  • Returns: A new fz_document; ownership is returned to the caller.

fz_document *fz_open_document_with_stream(fz_context *ctx, const char *magic, fz_stream *stream) – Open a document from a stream, using magic (MIME type or file extension) for format detection.

fz_document *fz_open_accelerated_document(fz_context *ctx, const char *filename, const char *accel) – Open a document with an accelerator file (e.g. pre-computed linearisation).

void fz_drop_document(fz_context *ctx, fz_document *doc) – Release a document.

fz_document *fz_keep_document(fz_context *ctx, fz_document *doc) – Increment the reference count.

Authentication

int fz_needs_password(fz_context *ctx, fz_document *doc) – Return non-zero if the document requires a password.

int fz_authenticate_password(fz_context *ctx, fz_document *doc, const char *password) – Attempt to authenticate with password. Returns non-zero on success.

Layout

void fz_style_document(fz_context *ctx, fz_document *doc, int publisher_css, const char *user_css)

Control use of publisher and user stylesheets.

  • publisher_css – Enable/disable use of publisher styles.
  • user_css – User stylesheet (the contents; not a file name).

void fz_layout_document(fz_context *ctx, fz_document *doc, float w, float h, float em)

Set the page layout for reflowable documents (EPUB, FB2, etc.).

  • w – Page width in points.
  • h – Page height in points.
  • em – Default em-size in points.

Pre-defined layout constants:

c
FZ_LAYOUT_KINDLE_W   = 260   /* 6in 4:3 */
FZ_LAYOUT_KINDLE_H   = 346
FZ_LAYOUT_KINDLE_EM  = 9
FZ_LAYOUT_A5_W       = 420
FZ_LAYOUT_A5_H       = 595
FZ_LAYOUT_A5_EM      = 11

Page counting & navigation

int fz_count_chapters(fz_context *ctx, fz_document *doc) – Return the number of chapters.

int fz_count_pages(fz_context *ctx, fz_document *doc) – Return the total number of pages (across all chapters).

int fz_count_chapter_pages(fz_context *ctx, fz_document *doc, int chapter) – Return the number of pages in a chapter.

fz_location fz_last_page(fz_context *ctx, fz_document *doc) – Return the location of the last page.

fz_location fz_next_page(fz_context *ctx, fz_document *doc, fz_location loc) – Return the next page location, wrapping across chapters.

fz_location fz_previous_page(fz_context *ctx, fz_document *doc, fz_location loc) – Return the previous page location.

int fz_page_number_from_location(fz_context *ctx, fz_document *doc, fz_location loc) – Convert a chapter/page location to a flat (zero-based) page number.

Loading pages

fz_page *fz_load_page(fz_context *ctx, fz_document *doc, int number) – Load page by flat page number (zero-based).

fz_page *fz_load_chapter_page(fz_context *ctx, fz_document *doc, int chapter, int page) – Load a page by chapter and page index.

void fz_drop_page(fz_context *ctx, fz_page *page) – Release a page.

Page bounds & rendering

fz_rect fz_bound_page(fz_context *ctx, fz_page *page) – Return the bounding box of the page (in points, with y increasing downward).

fz_rect fz_bound_page_box(fz_context *ctx, fz_page *page, fz_box_type box) – Return the bounding box of a specific page box type.

void fz_run_page(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie) – Render the page (content, annotations, and widgets) to a device.

void fz_run_page_contents(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie) – Render only the page content stream (no annotations/widgets).

void fz_run_page_annots(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie) – Render only the annotations.

void fz_run_page_widgets(fz_context *ctx, fz_page *page, fz_device *dev, fz_matrix transform, fz_cookie *cookie) – Render only the widgets (interactive form fields).

Metadata

int fz_lookup_metadata(fz_context *ctx, fz_document *doc, const char *key, char *buf, size_t size)

Look up a metadata string. Standard keys include "format",
"encryption", "info:Title", "info:Author", etc.

  • Returns: Length of the value string (or -1 if not found).

void fz_set_metadata(fz_context *ctx, fz_document *doc, const char *key, const char *value) – Set a metadata entry (for formats that support it).

Bookmarks

fz_bookmark fz_make_bookmark(fz_context *ctx, fz_document *doc, fz_location loc) – Create a persistent bookmark for the given location.

fz_location fz_lookup_bookmark(fz_context *ctx, fz_document *doc, fz_bookmark mark) – Resolve a bookmark to a location.

Document handler registration

void fz_register_document_handler(fz_context *ctx, const fz_document_handler *handler) – Register a document format handler (e.g. a custom renderer).

void fz_register_document_handlers(fz_context *ctx) – Register all built-in document handlers (PDF, XPS, EPUB, etc.).


util.h – High-Level Utilities

c
#include "mupdf/fitz/util.h"

Convenience wrappers that combine several lower-level calls.

Display lists

fz_display_list *fz_new_display_list_from_page(fz_context *ctx, fz_page *page) – Record the complete rendering of page into a reusable display list. Ownership of the returned display list is returned to the caller.

fz_display_list *fz_new_display_list_from_page_number(fz_context *ctx, fz_document *doc, int number) – As above, addressed by flat page number.

fz_display_list *fz_new_display_list_from_page_contents(fz_context *ctx, fz_page *page) – Record only the page content stream (no annotations).

Rendering to pixmaps

fz_pixmap *fz_new_pixmap_from_page(fz_context *ctx, fz_page *page, fz_matrix ctm, fz_colorspace *cs, int alpha)

Render page to a freshly-allocated pixmap in one step.

  • ctm – View transform (typically a scale + rotation).
  • cs – Output colour space (e.g. fz_device_rgb(ctx)).
  • alpha – Non-zero to include an alpha channel.

fz_pixmap *fz_new_pixmap_from_page_number(fz_context *ctx, fz_document *doc, int number, fz_matrix ctm, fz_colorspace *cs, int alpha) – Render page by flat page number.

fz_pixmap *fz_new_pixmap_from_display_list(fz_context *ctx, fz_display_list *list, fz_matrix ctm, fz_colorspace *cs, int alpha) – Render a display list to a pixmap.

fz_pixmap *fz_new_pixmap_from_page_contents(fz_context *ctx, fz_page *page, fz_matrix ctm, fz_colorspace *cs, int alpha) – Render page content only (no annotations).

int fz_search_page(fz_context *ctx, fz_page *page, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max)

Search for text on a page.

  • needle – UTF-8 search string.
  • hit_mark – Array of hit marks (length hit_max); filled on return.
  • hit_bbox – Array of fz_quad hit rectangles (length hit_max).
  • hit_max – Capacity of the hit arrays.
  • Returns: Number of hits found (may be less than the actual count if capped).

int fz_search_page_number(fz_context *ctx, fz_document *doc, int number, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max) – Search by flat page number.

int fz_search_display_list(fz_context *ctx, fz_display_list *list, const char *needle, int *hit_mark, fz_quad *hit_bbox, int hit_max) – Search a display list.


Quick-start Example

The following example renders page 0 of a PDF to a PNG file at 150 dpi:

c
#include "mupdf/fitz.h"

int main(void)
{
    fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_DEFAULT);
    fz_register_document_handlers(ctx);

    fz_document *doc = NULL;
    fz_page     *page = NULL;
    fz_pixmap   *pix  = NULL;

    fz_try(ctx) {
        doc  = fz_open_document(ctx, "input.pdf");
        page = fz_load_page(ctx, doc, 0);

        /* 150 dpi = 150/72 scale */
        fz_matrix ctm = fz_scale(150.0f / 72, 150.0f / 72);
        pix = fz_new_pixmap_from_page(ctx, page, ctm,
                                      fz_device_rgb(ctx), 0);

        fz_save_pixmap_as_png(ctx, pix, "output.png");
    }
    fz_always(ctx) {
        fz_drop_pixmap(ctx, pix);
        fz_drop_page(ctx, page);
        fz_drop_document(ctx, doc);
    }
    fz_catch(ctx) {
        fprintf(stderr, "error: %s\n", fz_caught_message(ctx));
    }

    fz_drop_context(ctx);
    return 0;
}

Header File Index

The following headers make up the complete public fitz API:

Header Contents
fitz/version.h Version constants (FZ_VERSION, FZ_VERSION_MAJOR, etc.)
fitz/config.h Compile-time feature flags (FZ_ENABLE_PDF, etc.)
fitz/system.h Platform portability macros and type aliases
fitz/context.h Context lifecycle, error handling, memory allocation
fitz/output.h Buffered output writers (fz_output)
fitz/log.h Diagnostic logging
fitz/geometry.h Points, rects, matrices, quads, and transformations
fitz/buffer.h Growable byte buffers (fz_buffer)
fitz/stream.h Seekable/non-seekable byte streams (fz_stream)
fitz/compress.h Zlib/deflate compression helpers
fitz/archive.h ZIP, TAR, and directory archives
fitz/color.h Colour spaces and colour conversion
fitz/pixmap.h Raster pixel data
fitz/image.h Compressed image objects (fz_image)
fitz/bitmap.h 1-bit halftoned bitmaps
fitz/font.h Font objects and glyph metrics
fitz/path.h Vector paths, stroke state
fitz/text.h Glyph sequences for text rendering
fitz/shade.h Gradient shading
fitz/separation.h Spot colour separations
fitz/device.h Rendering device interface
fitz/display-list.h Recorded rendering command lists
fitz/structured-text.h Extracted text blocks, lines, and characters
fitz/link.h Hyperlinks (fz_link)
fitz/outline.h Document outline / bookmarks tree
fitz/document.h Document and page lifecycle, metadata
fitz/util.h High-level rendering and search helpers
fitz/transition.h Page transition effects
fitz/xml.h Lightweight XML DOM
fitz/json.h Lightweight JSON parser
fitz/string-util.h String utility functions
fitz/hash.h Simple hash table
fitz/pool.h Memory pool allocator
fitz/tree.h Balanced binary tree
fitz/store.h Reference-counted resource store / cache
fitz/crypt.h MD5, SHA, AES helpers
fitz/getopt.h Portable getopt implementation

Notes

  • All string parameters are UTF-8 encoded unless otherwise noted.
  • Page numbers are zero-based throughout the API.
  • The fz_context is not thread-safe; create one context per thread
    using fz_clone_context and share the resource store via locks.
  • Memory management follows a keep/drop pattern:
    fz_keep_* increments a reference count, fz_drop_* decrements it
    and frees when the count reaches zero.

Dict Support (`docs/dict-support.md`)

Offline dictionary / word lookup — plan for future work

Status: research done (2026-07-11), no code yet. This is fork feature B from
plus-merge.md — port decision pending; the recommendation below
is to port the UI/plumbing but replace the data layer with a StarDict reader.

How the sumatrapdf-plus fork implements it

Fork source (extract with git show plus/main:):

  • src/WordLookup.{cpp,h} (~2,450 lines) — popup UI + lookup + dictionary loading
  • src/LookupAudio.{cpp,h} — pronunciation playback (Media Foundation, decodes mp3 blobs)
  • src/FloatingPopupStyle.{cpp,h} — shared popup chrome (we folded an equivalent
    into SelectionToolbar.cpp when porting feature H; factor it back out if porting this)
  • cmd/import-oaldpex-dict.ts, cmd/import-xdhy7-dict.ts — offline converters
  • Settings: OfflineDictionaryPath (dir; default {exedir}/dict),
    EnableDoubleClickWordLookup (bool, default true)
  • Commands: CmdLookupSelection, CmdToggleDoubleClickWordLookup,
    CmdSearchSelectionWithYoudaoDict (online fallback, opens browser)

Invocation (three paths)

  1. Double-click a word on the canvas (EnableDoubleClickWordLookup). Routing
    in their Canvas.cpp double-click handler, gated on isOverText:
    • fixed-layout ebooks → ShowEbookWordLookupAt
    • CJK text → ShowChineseWordLookupAt: greedy longest-prefix match against the
      Chinese dictionary (8-hanzi window, 3-char look-back) since Chinese has no
      word boundaries
    • otherwise the double-clicked word → ShowWordLookup
  2. CmdLookupSelection from palette/menu; also the "Look Up" button on their
    selection toolbar (we dropped that button in our feature-H port; re-add via
    CommandAvailability when this lands).
  3. Youdao web fallback command (skip in our port, or generalize to the existing
    SearchSelectionWith* family).

Floating WS_POPUP card ~340px wide, rounded corners, theme-aware; shows:
headword + IPA transcription, part-of-speech tabs (max 8 senses), numbered
bilingual definitions (en/zh line pairs), one example sentence per sense, speaker
icon (hover/wave animation, plays pronunciation), loading-dots spinner (lookup on
a background thread), close button. Positions near the clicked word clamped to
the canvas; pauses read-aloud while open, resumes on close.

Caveat for porting: this layout is OALD-shaped (senses/tabs/bilingual pairs).
A generic dictionary reader needs a plain "formatted text entry" rendering mode
first; their rich layout becomes a special case.

Lookup pipeline

normalize (trim + lowercase) → candidate list via hard-coded English morphology
(irregular plurals table, irregular verbs table, -s/-es/-ies/-ed/-ing stripping)
→ binary search over a fully-in-RAM sorted index → read entry slice from the data
file by offset/size → parse entry.

Their storage format ("SumatraDict" — bespoke, not a standard)

File Content
SumatraDict.idx TSV text, sorted: word \t dataOffset \t dataSize \t audioOffset \t audioSize \t audioExt
SumatraDict.dat concatenated plain-text entries: SDICT1\n magic, then line-based fields (headword, IPA, sense count; per sense: label, POS, def count, en/zh definition pairs, example)
SumatraDictAudio.dat concatenated mp3 blobs, sliced by offset/size (optional)
SumatraDictZh.{idx,dat} separate Chinese dictionary (hanzi headwords, pinyin instead of IPA)

What dictionaries they actually support

Exactly two, via offline bun+Python converters (readmdict/mdict-utils +
BeautifulSoup) that scrape specific MDict (.mdx/.mdd) packages, with the
dictionaries' HTML class structure hardcoded:

  • OALDPE(X) — community MDX repack of Oxford Advanced Learner's Dictionary
    10th ed with Chinese translations (copyrighted; circulates on Chinese
    dictionary forums; users must source it themselves)
  • 现代汉语词典 7 (Contemporary Chinese Dictionary, monolingual)

Consequences: nothing works out of the box, converting requires Python, and the
only documented sources are pirated commercial content. Very China-centric.

Dictionary format landscape

  • StarDict (.ifo + .idx + .dict[.dz], optional .syn) — de-facto FOSS
    standard, best first target. Simple documented format: .idx is a sorted
    list of word\0 u32be-offset u32be-size; .dict.dz is dictzip (gzip with a
    random-access extra field); .ifo is key=value metadata (incl. sametypesequence,
    typically m/t/x/h = plain text / IPA / xdxf / html). .syn maps synonym
    strings to entry indexes. Readable natively with no converter. Big
    freely-licensed ecosystem: ECDICT, FreeDict, CC-CEDICT, JMdict, Wiktionary and
    WordNet exports. Used by GoldenDict, KOReader, etc.
  • MDict (.mdx/.mdd) — dominant in China and for premium repacks (OALD, LDOCE).
    Reverse-engineered, zlib blocks, entries are full HTML (GoldenDict renders them
    with a browser engine), content mostly copyrighted. Don't implement natively;
    the fork's convert-offline approach is right for this one.
  • DSL (ABBYY Lingvo source) — plain-text markup, popular in Russian-speaking
    communities. Feasible later.
  • DICT/dictd, Babylon BGL, EPWING (Japanese), Slob (Aard2) —
    legacy/niche, skip.
  • Freely-licensed data to point users at (or bundle): ECDICT (en↔zh, also
    distributed as StarDict), WordNet (English monolingual, permissive license),
    Wiktionary extracts (wiktextract JSON, all languages), CC-CEDICT (zh→en).
  1. Port from the fork mostly as-is: popup window shell, double-click/command
    invocation, CJK longest-prefix segmentation, English morphology fallbacks,
    async lookup thread, audio playback, settings + commands. Re-add the "Look Up"
    button to SelectionToolbar.cpp (gate via CommandAvailability, hidden when
    no dictionary is installed). Factor popup chrome back out of SelectionToolbar
    into a shared FloatingPopupStyle when both need it.
  2. Replace the data layer: implement a native StarDict reader
    (.ifo/.idx/.dict.dz/.syn; dictzip random access is the only nontrivial
    part — gzip FEXTRA chunk table, inflate per-chunk). Keep the sorted-index +
    binary-search + slice-read architecture — it maps 1:1. Support multiple
    dictionaries in a directory, looked up in priority order.
  3. Rendering: start with a plain-text entry renderer (StarDict m/t types;
    strip tags for h/x); keep the fork's rich sense/tab layout only if we also
    keep their SumatraDict format as an "enhanced" package format.
  4. Converters: optionally keep/generalize the fork's MDX scripts but emit
    StarDict instead of a third format; document ECDICT/Wiktionary/WordNet as the
    out-of-the-box sources.
  5. Port adaptation notes (same issues as features D/E/H): fork uses old Vec
    API (.Size()/.at() → len()/[]), utils/ → base/ includes, no
    #pragma once, str:: API drift, ThemeUsesDarkChrome() → check effective
    colors, PCH opt-out only if a file includes mupdf headers first (these don't).
    WordLookup.cpp touches Canvas/Toolbar/Theme/read-aloud — expect conflicts;
    port by feature diff, not cherry-pick.

Open questions for Krzysztof

  • Bundle a small freely-licensed dictionary with the installer, or download on
    first use, or leave entirely user-supplied?
  • Is en↔zh bilingual layout worth special-casing, or is generic single-language
    rendering enough for the first version?
  • Keep the Youdao command (China-centric) or fold online lookup into the existing
    SearchSelectionWith* handlers (e.g. Wiktionary web)?

Find Improvements Maybe (`docs/find-improvements-maybe.md`)

Find UI — possible future improvements

Context: there are now two find UIs that share a backend:

  • the compact Chrome-style overlay bar — src/FindBar.cpp / src/FindBar.h
    (FindBarWnd).
  • the floating, movable/resizable find window with a results list —
    src/FindWindow.cpp / src/FindWindow.h (FindWindowWnd). It's enabled by the
    SearchUIFloating advanced setting and toggled by the pin/dock buttons.

Which one is active is chosen by gGlobalPrefs->searchUIFloating;
ShowFindBar/HideFindBar/FindBarSetStatus/FindBarSetMatchCaseChecked in
src/FindBar.cpp dispatch to whichever UI applies. The shared backend (the find
thread, the coalescing count thread that builds n / m + the per-match snippet
list MainWindow::findMatches, and GoToFindMatch) lives in
src/SearchAndDDE.cpp.

Items 1–2 came out of the first review (compact bar). Items 3+ came out of the
review of the floating-window feature (issue #4092) and were consciously
deferred (low severity, perf-only, or refactor-only). Each is self-contained.


1. Harden the match-count cache key against engine-pointer reuse

Problem

The "n / m" counter caches all match positions for the current search term so
prev/next is instant. The cache is keyed partly on the document's engine pointer:

  • MainWindow::findCountEngine (a void*, declared in src/MainWindow.h) stores
    (void*)dm->GetEngine() for the document the cache was built against. It is
    compared, never dereferenced (see the comment on the field).
  • UpdateMatchCount() in src/SearchAndDDE.cpp decides "cache hit" with:
    cpp
    bool cacheHit = win->findCountValid && win->findCountText &&
                    str::Eq(win->findCountText, text) &&
                    win->findCountMatchCase == win->findMatchCase &&
                    win->findCountEngine == engine;   // <-- raw pointer compare

This correctly handles the common case (tab switch → different engine pointer →
cache miss → rescan). The hole: if the user reloads/replaces the document, the
old EngineBase is freed and a new engine can be allocated at the same
address
. Then findCountEngine == engine is true again, the term + match-case
match, and the cache "hits" — showing n / m computed for the old document's
pages (wrong, possibly out-of-range indices) instead of rescanning.

Severity is low: requires a reload that reuses the exact address AND the same
search term + match-case still in the box. But it is a real stale-data bug.

Fix (sketch)

Add a monotonically increasing "document generation" counter that bumps every
time a document is (re)loaded into a MainWindow/tab, and key the cache on that
instead of (or in addition to) the engine pointer.

  • Add e.g. int docEpoch to the per-tab/DisplayModel state, bumped on load, OR a
    per-MainWindow int findCountDocGen bumped wherever the doc is swapped.
  • Replace findCountEngine comparison with findCountDocGen == <current gen>.
  • Invalidate the cache (win->findCountValid = false) on document load/close.
    The natural hook is wherever the old inline find UI used to be reset on load;
    LoadDocIntoWindow / UpdateFindbox in src/SumatraPDF.cpp /
    src/Toolbar.cpp are candidates — find where dm->textSearch / find state is
    reset on a new doc and bump the generation there.

Simplest correct version: in the doc-load path call a new
InvalidateFindCount(win) (set findCountValid = false, free findCountText).
Then the cache can never survive a reload and the pointer compare is no longer
load-bearing.

Files

  • src/MainWindow.h — cache fields (findCount*).
  • src/SearchAndDDE.cpp — UpdateMatchCount, ShowMatchCount, StartFindCount,
    CountEndTask.
  • doc-load path in src/SumatraPDF.cpp (search for where textSearch /
    selection is reset on load).

2. Replace FindBar's hand-rolled layout with the wingui layout engine

Problem

FindBarWnd::Layout() in src/FindBar.cpp positions its three children — the
Edit (search box), the status Static ("n / m"), and the embedded Win32
toolbar (prev/next/match-case/close) — with manual MoveWindow + a running x
cursor and hand-computed DPI gaps and vertical centering:

cpp
int p = DpiScale(hwnd, 6);
int gap = DpiScale(hwnd, 4);
...
MoveWindow(edit->hwnd, x, (barDy - editDy) / 2, editDx, editDy, TRUE);
x += editDx + gap;
MoveWindow(status->hwnd, ...);
x += statusDx + gap;
MoveWindow(hwndBtns, ...);
SetWindowPos(hwnd, ... barDx, barDy ...);

This duplicates the HBox/VBox + LayoutAndSizeToContent machinery in
src/wingui/Layout.h that src/CommandPalette.cpp already uses for the same
shape (an Edit plus a horizontal row of controls). The manual version re-derives
spacing/centering/RTL by hand, so any change (spacing, adding a control, RTL,
high-DPI) means re-doing pixel math and re-testing.

This is a refactor, not a bug — the current layout works and was visually
verified (incl. the SS_CENTERIMAGE fix that vertically centers the status).
Deferred because reworking it risks regressions for no user-visible gain.

Fix (sketch)

Model on CommandPaletteWnd::Create in src/CommandPalette.cpp (look for
new HBox(), AddChild, Padding, and LayoutAndSizeToContent):

  • Build an HBox with alignCross = CrossAxisAlign::CrossCenter containing the
    Edit, the status Static, and the buttons.
  • Wrap children in Padding/Insets for gaps instead of manual += gap.
  • The catch: the buttons are a raw Win32 TOOLBARCLASSNAME HWND, not a wingui
    Wnd, so it can't be added to an HBox directly. Options:
    1. Wrap the toolbar HWND in a thin ILayout/Wnd adapter that reports the
      toolbar's TB_GETMAXSIZE as its ideal size and MoveWindows on SetBounds.
    2. Replace the embedded toolbar with individual wingui controls (e.g. bitmap
      Buttons or owner-drawn statics) so everything is a Wnd — but that loses
      the toolbar's free tooltip + checked-state (BTNS_CHECK) handling, which is
      currently why a real toolbar is used (see tooltips via TTN_GETDISPINFOW in
      FindBarWnd::OnNotify and match-case TB_CHECKBUTTON in
      FindBarSetMatchCaseChecked).
  • Option 1 (adapter) is the lower-risk path: keep the toolbar, just let the
    layout engine place it.

If/when this is done, also revisit the manual RTL handling (WS_EX_LAYOUTRTL on
the popup + the button toolbar, set in FindBarWnd::Create) — the layout engine
mirrors automatically, so the explicit exStyle may become unnecessary.

Files

  • src/FindBar.cpp — FindBarWnd::Create, FindBarWnd::Layout.
  • src/wingui/Layout.h — HBox, VBox, Padding, LayoutAndSizeToContent.
  • src/CommandPalette.cpp — reference implementation of the same shape.

3. Share a find-UI "core" between the compact bar and the floating window

Problem

FindWindowWnd (src/FindWindow.cpp) duplicates almost all of FindBarWnd
(src/FindBar.cpp): the Edit + status Static + a flat
TOOLBARCLASSNAMEW toolbar with the same prev/next/match-case buttons, the same
SetWindowTheme(hwnd, L"", L"") + NM_CUSTOMDRAW dark-background workaround, the
same BuildStdToolbarImageList + theme-rebuild logic, the same tooltip table +
TTN_GETDISPINFOW OnNotify, and the same OnTextChanged -> OnFindBarTextChanged
dispatch. The button/command tables and the two tooltip lookup functions
(FindBarButtonTooltip / FindWindowButtonTooltip) are three-quarters identical.

Every find-UI change (new button, icon, RTL/theme fix, the toolbar dark-bg hack)
now has to be made twice and kept byte-identical, and will silently drift.

Fix (sketch)

Extract a FindUiCore that owns the edit + status + toolbar + the
OnFindBarTextChanged dispatch + tooltip table + the NM_CUSTOMDRAW/theme/
imagelist plumbing, and have both FindBarWnd (docked chrome) and
FindWindowWnd (popup caption + results list) embed it and only add their own
chrome. The toolbar differs by exactly one button (compact has Close at index 4;
floating has the dock button) — parameterize the button table.

This is the deepest of the deferred items and touches the most code; do it before
adding any more find-UI features, not after. The duplicated NM_CUSTOMDRAW
toolbar-bg fill is itself a near-copy of the main toolbar's custom draw in
src/Toolbar.cpp (line ~507) — a shared "theme a flat toolbar's background"
helper could serve all three.

Files

  • src/FindBar.cpp, src/FindWindow.cpp — the two near-duplicate window classes.
  • src/Toolbar.cpp — the canonical toolbar custom-draw to factor against.

4. Results list: stop rebuilding the whole ListBox every keystroke

Problem

On every completed count (i.e. roughly every keystroke), CountEndTask ->
FindWindowRefreshResults -> FindWindowWnd::RefreshResults calls
FillWithItems (src/wingui/UIModels.cpp), which does ListBox_ResetContent
then one LB_ADDSTRING with a UTF-8 -> WStr conversion per item. For a common
term on a big document that's up to kMaxFindResults (5000) window messages +
5000 string conversions, tearing down and rebuilding the entire listbox just to
repaint ~25 visible rows. DrawResultItem already pulls text straight from
win->findMatches[i] by index, so the listbox's own string copies are pure waste.

Fix (sketch)

Make the results listbox owner-data: create it with LBS_NODATA and, on refresh,
just send LB_SETCOUNT with win->findMatches.size() instead of FillWithItems.
The owner-draw path (FindWindowWnd::DrawResultItem) is already index-based, so no
per-item strings are needed. Check src/wingui/ListBox.cpp for whether the wingui
ListBox wrapper supports LBS_NODATA (the model API assumes string items) — may
need a small "virtual list" mode on the wrapper, or bypass the wrapper for the
count.

Files

  • src/FindWindow.cpp — RefreshResults, the ListBox::CreateArgs in Create,
    DrawResultItem.
  • src/wingui/ListBox.cpp, src/wingui/UIModels.cpp — FillWithItems, listbox
    styles, model API.

5. Build result snippets lazily instead of all 5000 up front

Problem

CountThread (src/SearchAndDDE.cpp) calls BuildSnippet for every match up to
kMaxFindResults on every count, and the count runs on every keystroke.
BuildSnippet does engine->GetTextForPage (cheap — cached) + str::Dup +
NormalizeWSInPlace + ToUtf8Temp + str::FormatTemp + str::Dup (two heap
allocs + a normalize + a utf8 pass) per match. Only ~25 snippets are ever on
screen; the rest are allocated, then freed on the next keystroke, unseen.

Fix (sketch)

Defer snippet construction to render time. Keep the count building the cheap
positions + the match coordinates (startPage/startGlyph/endPage/endGlyph) for
all matches, but build the snippet string lazily in DrawResultItem (or
FindResultsModel::Item) for the rows actually painted, caching it back into the
FindMatch (or a side cache keyed by index) so scrolling doesn't rebuild. Note
GetTextForPage is engine-thread-affine-ish; building snippets on the UI thread
at paint time is fine since the page text is cached by then, but confirm against
the concurrency rules (see GoToFindMatch / AbortFinding serialization).
Alternative/simpler: only rebuild snippets when the search term actually changed,
not on every count completion.

Files

  • src/SearchAndDDE.cpp — CountThread, BuildSnippet, FindMatch install in
    CountEndTask, kMaxFindResults.
  • src/MainWindow.h — FindMatch (would gain a "snippet not yet built" state).
  • src/FindWindow.cpp — DrawResultItem / FindResultsModel::Item.

6. Dispatch find status/match-case by actual visibility, not the preference

Problem

HideFindBar decides which UI to act on by actual visibility
(IsFindWindowVisible(win)), but FindBarSetStatus and
FindBarSetMatchCaseChecked (src/FindBar.cpp) decide by the persisted
preference
(gGlobalPrefs->searchUIFloating). If the two ever disagree —
preference says floating but the compact bar is the one actually showing (or vice
versa) — a count completion routes the n / m status and the Match-Case checkbox
update to the hidden control, leaving the visible UI stale.

In normal flow ToggleFloatingFindUI keeps the preference and the visible UI in
sync, so this is currently latent, but it's a fragile coupling.

Fix (sketch)

Make the status/match-case setters key off which UI is actually visible, mirroring
HideFindBar: if (IsFindWindowVisible(win)) { FindWindowSet...; return; } then
fall through to the compact bar. A tiny helper like
static bool UseFloatingFindUI(win) { return IsFindWindowVisible(win); } used by
all four dispatchers removes the preference-vs-visibility split.

Files

  • src/FindBar.cpp — FindBarSetStatus, FindBarSetMatchCaseChecked,
    ShowFindBar, HideFindBar (unify the dispatch predicate).

7. Binary-search the match lists instead of linear scans

Problem

FindWindowWnd::CurrentMatchIndex and FindWindowWnd::FirstMatchFromCurrentPage
(src/FindWindow.cpp) do an O(n) linear scan of win->findMatches on every
selection move (each arrow key, Next/Prev, and each RefreshResults). The matches
are already in page order (the count appends them in FindNext order, sorted by
(page, glyph)).

Fix (sketch)

  • FirstMatchFromCurrentPage: std::lower_bound on startPage >= curPage.
  • CurrentMatchIndex: matches are sorted by (startPage, startGlyph), so a binary
    search works; simpler still, remember the index navigated to in GoToFindMatch/
    OnResultSelected and validate it instead of rescanning.

Low impact (n <= 5000, only on user input), pure cleanup.

Files

  • src/FindWindow.cpp — CurrentMatchIndex, FirstMatchFromCurrentPage.

8. Remove the vestigial `AbortFinding(win, hideMessage)` parameter + dead Kind

Problem

The "Searching n of m..." find-progress notification was removed (the find UI's
own n / m counter is the feedback now). ShowUI no longer creates it, so the
kNotifFindProgress Kind (src/SearchAndDDE.cpp line ~46) and the
RemoveNotificationsForGroup(..., kNotifFindProgress) in AbortFinding are dead
(the HideUI copy was already removed). With that gone, AbortFinding's
hideMessage bool parameter no longer does anything.

Left in place only because removing the parameter means editing all ~14 call
sites (src/FindBar.cpp, src/FindWindow.cpp, src/SearchAndDDE.cpp,
src/SumatraPDF.cpp) plus the declaration in src/SearchAndDDE.h.

Fix (sketch)

Drop the hideMessage parameter from AbortFinding, remove the dead
RemoveNotificationsForGroup block, and remove the kNotifFindProgress Kind
declaration. Mechanical: AbortFinding(win, true/false) -> AbortFinding(win) at
every call site. Verify the one return-value user, OnFrameKeyEsc in
src/SumatraPDF.cpp (Esc consumed iff a find thread was aborted) — behavior is
unchanged because the notification path already always returned false.

Files

  • src/SearchAndDDE.cpp / src/SearchAndDDE.h — AbortFinding, kNotifFindProgress.
  • ~14 call sites across src/FindBar.cpp, src/FindWindow.cpp,
    src/SearchAndDDE.cpp, src/SumatraPDF.cpp.

9. (Optional) restore a way to cancel a long explicit find mid-scan

Problem

With the progress notification gone, an explicit Find Next/Prev (or DDE
[Search], or a Match-Case re-run) on a huge document scans to completion with no
on-screen cancel affordance. Esc still cancels (OnFrameKeyEsc -> AbortFinding
sets findCancelled), and the Find toolbar buttons are disabled during the scan,
so this is minor — but there's no longer a visible "searching… (cancel)" control.

Fix (sketch)

If it ever feels slow in practice, show a lightweight, cancelable status only
when an explicit find runs longer than e.g. 300 ms (a timer-armed notification, or
a spinner in the find UI's status area), rather than the old always-on
"Searching n of m..." flash. Most of the time the find is instant and nothing
should appear. Keep find-as-you-type silent (it self-cancels and defers to the
count thread; see UpdateFindStatus's showProgress check).

Files

  • src/SearchAndDDE.cpp — ShowUI / HideUI / UpdateFindStatus (the
    showProgress path), FindThreadData.

Location Chapters Plan (`docs/location-chapters-plan.md`)

Plan: chapter-aware page locations (`Location`)

Status: implemented (phases 1-7). Each phase must build (bun cmd/build.ts -dbg) before
the next starts. Never commit; the user commits.

Why

mupdf addresses pages as fz_location {chapter, page}. SumatraPDF predates that
and uses one flat 1-based pageNo everywhere. For a multi-chapter ebook this
forces laying out every chapter before the first page shows:

  • EngineMupdf::FinishLoading calls fz_count_pages (lays out every EPUB
    chapter) and FinishNonPDFLoading then fz_load_pages every page.
  • EngineMobi::FinishLoading runs HtmlFormatter::FormatAllPages over the
    whole book. C:\Users\kjk\OneDrive\!sumatra\1000.mobi (11.9 MB of HTML,
    886 <mbp:pagebreak> chapters, 3152 pages) takes 38 s to open in the
    release build (LoadDocument: 38069 ms). Target: well under 1 s.

Facts that shape the design (verified, do not re-derive)

  1. .mobi never reaches mupdf. EngineCreate.cpp routes it to
    CreateEngineMobiFromFile (our EngineEbook.cpp + MobiDoc). mupdf's
    mobi.c lacks HUFF/CDIC decompression and flattens a book into a single
    HTML chapter, so routing mobi through mupdf is not an option. Both engines
    need chapters: EPUB in EngineMupdf, MOBI in EngineEbook.
  2. mupdf's fz_bookmark is an in-memory fz_html_flow* pointer
    (html-outline.c: fz_make_html_bookmark). It cannot be persisted. The
    persisted "bookmark" is therefore ours: chapter:page:pagesInChapter
    (proportional restore when the chapter re-paginates), and for EngineEbook
    the exact reparseIdx byte offset that FileState.ReparseIdx was designed
    for. fz_make_bookmark/fz_lookup_bookmark are not used.
  3. In mupdf only EPUB has >1 chapter (epub_count_chapters = spine length).
    fb2/html/txt/md are htdoc single-chapter; XPS/PDF single-chapter. EPUB
    chapter layout is already lazy inside mupdf (count_chapter_pages lays out
    one chapter on demand); what forces the full layout is our use of
    fz_count_pages / fz_load_page / fz_load_outline.
  4. fz_load_outline resolves every outline node with fz_resolve_link
    (outline.c:118), which lays out every chapter a fragment link points into.
    Use fz_new_outline_iterator and keep the URI; resolve on click.
  5. Our page numbers are 1-based. Location is therefore 1-based in both
    fields; fz_location is {loc.chapter - 1, loc.page - 1}.
  6. FileState.ReparseIdx (cmd/gen-settings.ts:997) is unused today.

Design

`Location`

cpp
// src/EngineBase.h
struct Location {
    int chapter = 0; // 1-based, 0 = invalid
    int page = 0;    // 1-based within chapter, 0 = invalid
    bool IsValid() const { return chapter >= 1 && page >= 1; }
    bool operator==(const Location&) const; bool operator!=(const Location&) const;
};
inline Location LocFromPageNo(int pageNo) { return {1, pageNo}; } // single-chapter docs

Flat `pageNo` stays the view index; chapters may be _unlaid_

The app keeps using a flat 1-based pageNo (DisplayModel, RenderCache, text
selection, etc.). What changes: a chapter that has not been laid out counts as
1 placeholder page in the flat numbering. When a chapter gets laid out (on
first render / text extraction / explicit navigation into it) its real page
count replaces the placeholder and every later pageNo shifts. So:

  • The engine owns a chapter table (ChapterTable, see below): per chapter
    {pageCount, laidOut}, prefix sums, and a generation counter bumped on
    every count change. EngineBase::pageCount is always the table's total.
  • State that must survive a shift holds a Location, not a pageNo:
    ScrollState, nav history, pending scroll, RenderCache entries, TOC/link
    destinations. DisplayModel re-syncs (SyncWithEngineLayout) whenever the
    engine generation differs from its own, rebuilding pagesInfo and restoring
    the position from Location.
  • Placeholder = 1 page keeps things deterministic (no estimates). All pages of
    a reflowable doc share one mediabox, so placeholders have a known size and
    never force layout just to be measured.
text
chapter:      1       2      3     4 ... 886
  laid out:    yes      no    yes    no
  pages:      1 2 3 4   [1]   1 2   [1]        <- [1] = placeholder
  pageNo:     1 2 3 4    5    6 7    8
  after laying out chapter 2 (3 pages): pageNo 6,7 -> 8,9 ; generation++

Engine-side contract (`EngineBase`)

Non-virtual, backed by ChapterTable chapters (a member; single-chapter engines
never touch it and behave exactly as today):

cpp
int  ChapterCount();                      // 1 when no chapters
bool HasChapters();                       // ChapterCount() > 1
int  ChapterPageCount(int chapter);       // lays out that chapter (real count)
bool IsChapterLaidOut(int chapter);
Location LocationFromPageNo(int pageNo);  // table lookup, never lays out
int  PageNoFromLocation(Location loc);    // lays out loc.chapter first, clamps loc.page
Location NextLocation(Location), PrevLocation(Location); // cross chapter edges; lays out target chapter
Location FirstLocation(), LastLocation(); // last lays out the last chapter
Location ClampLocation(Location);         // lays out loc.chapter, clamps
int  LayoutGeneration();                  // atomic read
void EnsureAllChaptersLaidOut(ProgressUpdateUI* = nullptr); // print / dump / full search
virtual TempStr MakeBookmarkTemp(Location);   // default: "%d:%d:%d" chapter:page:ChapterPageCount
virtual Location LookupBookmark(Str);         // default: parse; scale page by newCount/oldCount
virtual Location ResolveDest(IPageDestination*); // default {1, dest->pageNo}; caches into dest

One virtual for engines with chapters:

cpp
virtual int LayOutChapter(int chapter); // returns real page count; engine calls chapters.SetPageCount()

ChapterTable (new src/ChapterTable.h/.cpp, unit-tested via -unit-tests):
Init(nChapters), SetPageCount(chapter, n) (bumps generation when changed),
TotalPages(), LocationFromPageNo, PageNoFromLocation (pure, no layout),
IsLaidOut, Generation(). Thread-safe (mutex; generation atomic). Engines
call SetPageCount from whatever thread lays out (render thread included).

Per-page virtuals (PageMediabox(int pageNo), RenderPage, ...) keep int pageNo; engines convert with LocationFromPageNo at entry. Page-keyed engine
storage (EngineMupdf::pages, EngineEbook::pages, EngineBase::pagesText)
becomes per-chapter (Vec<Vec<T*>>, chapter-major) so a shift never
re-associates cached data with the wrong page.

Structs carrying a page: IPageDestination, TocItem, IPageElement,
RenderPageArgs gain Location loc. pageNo stays as the cached flat value
(-1 = unresolved). TocItem::pageNo == -1 with a valid dest means
"resolve on click".

Lazy resolution of destinations

  • EngineMupdf: TOC built from fz_new_outline_iterator (no fz_load_outline).
    For chaptered docs a TOC item stores only the URI and gets loc = {chapter,0}
    by resolving the URI without its fragment (epub_resolve_link returns the
    spine index without layout when there is no #). ResolveDest does the full
    fz_resolve_link_dest (lays out one chapter) and caches loc/pageNo.
  • EngineEbook (mobi): TOC and links are filepos byte offsets. Chapter =
    binary search of the offset in the chapter-start table (no formatting);
    page = format that chapter, then the existing reparseIdx scan.
  • LinkHandler::ScrollTo(IPageDestination*) and GoToTocLink call
    ctrl->ResolveDest(dest) and navigate by Location.

`DisplayModel`

  • PageCount() returns a snapshot taken by BuildPagesInfo, not the live
    engine count. PageInfo gains Location loc.
  • SyncWithEngineLayout(): if engine->LayoutGeneration() != layoutGeneration
    → remember CurrentLocation() + scroll offset, rebuild pagesInfo, Relayout,
    restore via GoToLocation, renderCache->RekeyForLayoutChange(this),
    cb->PagesRenumbered(this). Called at the top of RepaintDisplay,
    RecalcVisibleParts, GoToPage, CurrentPageNo and from the render-done
    callback path (RenderCache → UI thread).
  • Location CurrentLocation(), GoToLocation(Location, bool addNavPoint)
    (= PageNoFromLocation → sync → GoToPage).
  • GoToNextPage/GoToPrevPage/GoToLastPage for chaptered docs go through
    NextLocation/PrevLocation/LastLocation so "prev" from chapter 6 page 1
    lands on the last page of chapter 5 even if chapter 5 was never laid out.
    Single-chapter docs keep the current row-based code path untouched.
  • ScrollState gains Location loc; GetScrollState fills it,
    SetScrollState prefers loc when valid (nav history survives shifts).
  • RenderCache: BitmapCacheEntry gains Location loc (from
    PageInfo::loc); RekeyForLayoutChange(dm) recomputes pageNo from loc
    and drops entries whose loc no longer maps. RenderPageArgs::loc is what
    EngineMupdf/EngineEbook actually render (they ignore pageNo when loc
    is valid) so a shift between request and render cannot draw the wrong page.
  • DocControllerCallback::PagesRenumbered(DisplayModel*): SumatraPDF.cpp
    clears selection, aborts an in-progress find, refreshes toolbar / tab /
    page-info / TOC selection, repaints.

Persistence (`FileState` / `TabState`)

PageNo changes from Int to Str in cmd/gen-settings.ts (both structs;
Favorite.PageNo stays Int). Value grammar, parsed by one helper
(src/PagePosition.h/.cpp: ParseStoredPage(Str) -> {int pageNo; Str bookmark},
FormatStoredPageTemp(...)):

  • "12" – flat page number (legacy files and every single-chapter document)
  • "bm:" + engine bookmark – chaptered documents, e.g. bm:37:2:5
    (EngineMupdf: chapter:page:pagesInChapter) or bm:37:2:5:r1834211
    (EngineEbook adds the exact reparseIdx). Prefix kBookmarkPrefix = "bm:".

Old builds parse bm:... as int 0 → open at page 1 (acceptable). New builds
read old integers unchanged. FileState.ReparseIdx stays declared (compat) but
is no longer written. SetTabState / LoadDocIntoCurrentTab / NewTabState
/ CloneTabState / GoToFavorite's FileState hack / RememberSessionState
all go through the helper. -page N, DDE GotoPage, -dbg-control keep
flat page numbers (LocationFromPageNo).

UI, only when `ctrl->HasChapters()`

  • Toolbar: Page: → [chapter edit] / 886 [page edit] / 4. Second Edit
    created by ToolbarCreatePageEdit (chapter variant), two VirtText totals,
    hit-test / show-hide / theming / teardown mirror the existing page edit.
    Enter in either edit → GoToLocation({chapterEdit, pageEdit}).
    PageNoChanged fills both edits from CurrentLocation().
  • Go To Page dialog: two edits (Chapter (of 886), Page (of 4)), OnOk
    builds a Location, ClampLocation, GoToLocation.
  • Page-info notification and tab suffix: Ch 37 / 886, page 2 / 5.
  • TOC: click resolves lazily (above); UpdateTocSelection matches by
    loc.chapter when pageNo == -1.
  • Print, EngineDump, full-document text search, PDF export, stress test:
    call EnsureAllChaptersLaidOut() first (this is today's open cost, now
    paid only when the user asks for a whole-document operation).

Out of scope (explicitly)

Favorites keep flat page numbers. Annotations / forms are PDF-only (no
chapters). EngineEpub/EngineFb2 fallbacks in EngineEbook.cpp stay
single-chapter. No mupdf patch is needed (no ext/patches change).

Phases

Order: 1 → 4 → 2 → 3 → 5 → 6 → 7 (view-side plumbing before any engine
reports chapters, so a PDF-only tree stays correct at every step).

Each phase: implement, clang-format touched src/ files, build debug,
fix warnings, run the named checks. Report what was verified and what was not.

Phase 1 – `Location` + `ChapterTable` + `EngineBase` contract

Files: src/EngineBase.h/.cpp, new src/ChapterTable.h/.cpp (+ premake
file list premake5.files.lua), src/AppUnitTests.cpp or a _ut.cpp next to
it for ChapterTable tests (bun cmd/run-unit-tests.ts -dbg).
Adds Location, ChapterTable, the non-virtual chapter API, LayOutChapter,
bookmark defaults, ResolveDest, loc fields on IPageDestination,
TocItem, IPageElement, RenderPageArgs (default {}; NewSimpleDest /
AllocTocItem set loc = LocFromPageNo(pageNo) when pageNo >= 1).
pagesText/pagesTextState become chapter-major and are (re)allocated per
chapter on first use. No behavior change for existing engines.

Phase 2 – `EngineMupdf` lazy EPUB chapters

  • FinishLoading: chapters.Init(fz_count_chapters); count chapter 1 only
    (fz_count_chapter_pages(ctx, doc, 0)); no fz_count_pages when
    ChapterCount() > 1. LayOutChapter(ch) = fz_count_chapter_pages under
    docLock + chapters.SetPageCount.
  • FinishNonPDFLoading: for isReflowable fill mediaboxes from the layout
    size (ebookLayoutW/H in points; verify against page 1's fz_bound_page)
    instead of loading every page. Non-reflow (XPS) keeps the loop.
  • pages → chapter-major; GetFzPageInfoLocked takes a Location, loads
    with fz_load_chapter_page, then refreshes the chapter count (a placeholder
    chapter becomes real here) and grows that chapter's FzPageInfo vector.
    Every pages[pageNo - 1] / pi->pageNo use goes through helpers.
  • TOC via outline iterator; ResolveDest for PageDestinationMupdf;
    HandleLinkMupdf returns a Location. Heading-TOC background scan and
    page-label scan skipped when HasChapters().
  • ApplyReflowThemeCss: drop the "keeping pageCount" hack; re-Init the
    table (all chapters unlaid), bump generation; DisplayModel restores position.
  • src/libsumatrapdf.def: add fz_count_chapter_pages,
    fz_location_from_page_number (if used).
  • Check: open C:\Users\kjk\OneDrive\!sumatra\Dune - Frank Herbert.epub with
    -log-to-file, confirm LoadDocument ms drops and only chapter 1 is laid
    out at open (log the chapter count / laid-out count); page through with
    CmdGoToNextPage via -dbg-control and confirm no crash and page count grows.

Phase 3 – `EngineEbook` lazy MOBI chapters

  • EngineEbook gains chapter starts: scan doc->GetHtmlData() once for
    <mbp:pagebreak (case-insensitive) → Vec chapterStart (chapter 1
    starts at 0). Only EngineMobi enables it; when no marker is found the
    document stays single-chapter and formats as today.
  • pages → chapter-major Vec<Vec<HtmlPage*>*>. LayOutChapter(ch) formats
    Str(html.s + start, end - start) with MobiFormatter, adds start to every
    HtmlPage::reparseIdx and anchor offset, extracts that chapter's anchors
    (ExtractPageAnchors becomes per-chapter), and calls
    chapters.SetPageCount. Guard with pagesAccess. Check how
    HtmlFormatter treats a truncated slice (unclosed tags) and that
    GumboHtmlParser positions stay slice-relative.
  • GetNamedDest(filepos): chapter by binary search over chapterStart, lay
    it out, then the existing page scan within that chapter. ResolveDest for
    ebook TOC dests (pageNo == -1, name = filepos).
  • MakeBookmarkTemp appends :r<reparseIdx of loc's page>; LookupBookmark
    uses it when present (chapter from the table, page by reparseIdx scan).
  • Check: 1000.mobi LoadDocument well under 1 s in a release build
    (bun cmd/build.ts -rel, -log-to-file), first page renders,
    next/prev across a chapter edge, TOC click into a far chapter.

Phase 4 – `DisplayModel` / `RenderCache` / `DocController`

As in the design: PageInfo::loc, snapshot PageCount, SyncWithEngineLayout,
CurrentLocation/GoToLocation, chapter-aware next/prev/last, ScrollState::loc,
RenderCache re-keying + RenderPageArgs::loc, PagesRenumbered callback,
DocController additions (HasChapters, CurrentLocation, GoToLocation,
ResolveDest, ChapterCount, ChapterPageCount; ChmModel/MarkdownModel use
the single-chapter defaults). LinkHandler navigates by Location.
Check: PDF behavior unchanged (bun tests/issue-5949.ts or whichever tests
grep as page-navigation related), EPUB/MOBI continuous mode scrolls through
chapter edges without position jumps.

Phase 5 – persistence

cmd/gen-settings.ts (PageNo → Str in FileState and TabState, doc
strings updated), bun cmd/gen-code.ts, src/PagePosition.h/.cpp, all
read/write sites listed in the design, sumatra-website/www/docs/Advanced-options-settings.md
regenerated. Check: a settings file with PageNo = 12 still opens a PDF at
page 12; closing an EPUB at chapter 37 and reopening lands in chapter 37; an
old-style int for an EPUB still opens at that flat page.

Phase 6 – UI for chaptered documents

Toolbar, Go To Page dialog, page-info notification, tab suffix, TOC selection,
EnsureAllChaptersLaidOut at print / dump / full search / PDF export /
stress. Check with -dbg-control + screenshot (see verify skill).

Phase 7 – tests + docs

  • tests/ad-hoc-chapters.ts: opens 1000.mobi and an EPUB, asserts load time
    and chapter navigation through -dbg-control (add ChapterInfo /
    GoToLocation control commands if needed).
  • tests/issue-*.ts-style regression for settings backward compat.
  • sumatra-website/www/docs/Version-history.md entry (behavior change: chapter/page toolbar for
    multi-chapter ebooks, much faster open), sumatra-website/www/docs/Commands.md only if a
    command was added.

Outcome

  • 1000.mobi (892 chapters): LoadDocument 97.5 ms in a release build and
    ~140 ms in a debug build (was 38069 ms in release before this work) — under the "well under 1 s" target
    with headroom to spare, in the slower build config.
  • Dune - Frank Herbert.epub: 62 chapters; toolbar reads Chapter: [n] / 62,
    Page: [n] / M.
  • Two bugs found by tests/ad-hoc-chapters.ts and fixed in Phase 7:
    • EngineMobi::LayOutChapter asserted (ReportIf) when the UI thread and
      the render thread raced to lay out the same not-yet-laid-out chapter.
      Made idempotent: whichever caller wins keeps its result, the loser's
      redundant format is discarded.
    • DisplayModel::GoToNextPage/GoToPrevPage/GoToLastPage only used the
      chapter-aware Location crossing in non-continuous display modes.
      Continuous mode (EPUB's default) fell through to the flat row-based path,
      so CmdGoToPrevPage crossing into an unlaid previous chapter landed on
      its 1-page placeholder instead of its real last page. Fixed by dropping
      the non-continuous restriction for chaptered docs.
  • tests/tmp/toolbar-epub.png/toolbar-pdf.png: confirmed the Bookmarks
    sidebar showing empty-title dots under "Book One - DUNE" is pre-existing
    (reproduces on the pre-Phase-1 baseline via git stash), not caused by
    this work; left unfixed.

Ocr Ux Ideas (`docs/ocr-ux-ideas.md`)

OCR UX ideas for SumatraPDF

UX-focused proposal for adding OCR to SumatraPDF. Fits existing patterns: lightweight reader, command palette for newer features, notifications for feedback, copy/find/read-aloud as the main text workflows. No implementation details.

Design principle

OCR in SumatraPDF should not feel like a separate “OCR app.” It should unlock text the user already expects: select, copy, find, read aloud, and screen-reader access. The mental model is “recognize text”, not “run OCR.”

Results should behave like normal PDF text: invisible overlay, I-beam cursor, existing shortcuts unchanged. No dedicated OCR panel or correction editor — that’s out of character for SumatraPDF.

What to offer (three scopes)

Scope User intent When to surface
Selection / region “Copy this paragraph from a scan” User drags a rectangle on a page with no text
Current page “I need this one page searchable/copyable” Context menu, palette, or after a failed copy on one page
Whole document “Make this scanned PDF usable” Palette, Edit menu, or prompt on open — with a clear time warning

Default recommendation: ship page and selection first; whole document as an explicit, confirmed action (progress UI, cancel). Region OCR is especially valuable because scans are often mixed (cover + text pages) and full-doc OCR is slow.

Session-only by default: recognized text lives for this open document until close. No “Save OCR’d PDF” in v1 — keeps the reader/editor boundary clean. Export-to-text-file can be a later, optional command.

When to suggest it (progressive disclosure)

Don’t auto-OCR on open (CPU, privacy, wrong language). Detect likely scanned content (HasTextForPage is already the signal) and nudge at the moment of need:

  1. On open (once per document): a dismissible notification:
    “This document has little selectable text. Recognize text to enable search and copy?”
    Actions: Recognize on this page · Whole document… · Don’t ask again for this file

  2. When Find is used on a no-text page: enable Find but show in the find bar: “No text on this page — [Recognize text]”

  3. When Copy / Select All yields only an image (today copy falls back to image):
    “No text here. Recognize text on this page?” with the same action link

  4. Read Aloud already says “No text available…” — extend that path with Recognize text, then read (one combined flow, not two separate concepts)

These moments teach the feature without adding permanent chrome.

Where to put it in the UI

Primary (discoverable)

  • Command palette (Ctrl+K): “Recognize Text” with sub-actions or a second step: This page / Selection / Whole document…
    Same discovery pattern as Read Aloud and Advanced Options.

  • Edit menu (or View if Edit is crowded): one item “Recognize Text” → submenu matching the three scopes. Keeps the menu bar honest for users who don’t use the palette.

Contextual (high intent)

  • Right-click on page (no text under cursor): “Recognize Text on This Page” — only when the page has no text layer.

  • Right-click on rectangular selection (image-only area): “Recognize Text in Selection” — sits near Copy, above or below it.

  • Find toolbar / find box — inline link when search can’t run on current page (mirrors how image collections already disable Find in the menu).

Do *not* add (v1)

  • Toolbar button (too niche for permanent space; Read Aloud earned its slot as accessibility)
  • Status-bar mode toggle
  • Separate “OCR layer” preview

Progress and feedback

Use the same family as printing notifications:

  • Non-modal toast: “Recognizing text… page 3 of 47” with Cancel
  • On completion: “Text recognized on 12 pages. You can now search and copy.” (auto-dismiss)
  • On failure: short message + retry; no modal error storm

Optional subtle cue: after OCR, pages could show a one-time tooltip on first successful text selection — “Recognized text (session only)” — so power users know it isn’t embedded in the file.

Settings (Advanced Options)

Keep OCR mostly invisible; expose only what affects behavior:

  • OCR languages (multi-select, default from system/UI language)
  • Prompt when opening scanned PDFs (on / off)
  • Recognize text in background while reading (off by default) — only if whole-document OCR exists; otherwise skip

No tessdata paths or engine knobs in the main UI.

How it plugs into existing features

After recognition, nothing new to learn:

Existing feature Behavior after OCR
Ctrl+F / Find Works across recognized pages
Ctrl+C / Copy Text, not image fallback
Ctrl+A / Select All Page text as usual
Read Aloud Same commands; no special “OCR read” mode
Screen reader / UIA Benefits from text layer like a born-digital PDF
Image collections (CBZ, etc.) Optional: per-image recognize, or leave disabled with a clear “not supported” — same as Find today

Mixed PDFs: only run OCR on pages without text; don’t re-OCR pages that already have a layer.

What to defer

  • In-app OCR correction / proofreading UI
  • Embedding text into the PDF and Save
  • Batch folder OCR
  • Command-line OCR flags (unless power users ask later)
  • “OCR to HTML/SVG” export (MuPDF tool territory, not reader UX)

Suggested v1 story (one sentence for users)

“If a scan has no selectable text, choose Recognize Text — then search, copy, and read aloud work like a normal PDF, for this session.”

That stays aligned with SumatraPDF: small surface area, palette + contextual prompts, and making existing commands work instead of shipping a new subsystem.

  • HasTextForPage() — no glyph hit-testing when page has no text (DisplayModel.cpp)
  • Image collections — Find disabled (Menu.cpp); selection/copy limited (Selection.cpp, SearchAndDDE.cpp)
  • Read aloud — shows “No text available…” when page lacks text (SumatraPDF.cpp)
  • Copy on restricted/no-text pages — may copy as image with notification (Selection.cpp)
  • MuPDF has tessocr integration (ext/mupdf/source/fitz/tessocr.cpp); tool-draw docs mention OCR output formats but mark some as disabled

Pdf Form Filling Plan (`docs/pdf-form-filling-plan.md`)

PDF form filling — implementation plan

Goal: let the user fill interactive PDF (AcroForm) fields directly on the page
— click a text field and type, click a checkbox to toggle, click a dropdown to
pick — no side panel, no modal dialog. Save reuses the existing annotation-save
pipeline.

Scope (v1): text fields (single + multiline), checkboxes, radio buttons,
comboboxes, listboxes. Defer: digital signatures, XFA-only forms, push
buttons that run arbitrary JS, barcode fields.


Implementation status — DONE (phases 0–4, all on master)

Built and validated against f1040.pdf (a hybrid-XFA form whose AcroForm layer
fills fine) plus a synthetic formtest.pdf for the field types the 1040 lacks.

Phase Commit What shipped
0 1949ffa8a Click a checkbox / radio to toggle it in place. Needed: export the mupdf form-mutation APIs from libsumatrapdf.def; make widgets hit-testable (mupdf 1.27 keeps them in a separate page->widgets list, so walk pdf_first_widget in GetFzPageInfo).
1 082faf66d Click a text field → floating Win32 Edit over the field (positioned via CvtToScreen), commit on Enter/blur via pdf_set_text_field_value. New src/FormFields.cpp.
2 627848a9f Combobox/listbox dropdown (pdf_choice_widget_*); radio groups with proper mutual exclusion. Two mupdf gotchas: regenerate the whole page (pdf_update_page) so radio siblings / calc fields refresh; pdf_toggle_widget mishandles distinct-on-state radios, so radios go through pdf_set_field_value instead.
cleanup 053e23d46 Move widgets out of the shared annotation list into FzPageInfo::widgets + GetWidgetAtPos, so form fields stop polluting comment hovers / the edit-annotations panel.
3 dbbf00f85 Tab / Shift+Tab navigation (EngineMupdfGetAdjacentWidget); overlay font from the field's /DA size × zoom; max-length / comb enforcement (EM_SETLIMITTEXT); crash-safe teardown of the overlay on doc close + reload (no dangling widget pointer).
4a 222e45dab Save round-trip verified (reuses the annotation pdf_save_document pipeline — field values + checkbox state persist across save/reopen); generalized the close-time prompt wording from "Unsaved annotations" to "Unsaved changes"; hover cursors (I-beam over text/choice, hand over buttons).
4b f7fd4f998 Scroll the next field into view on Tab when it's past the fold (ScrollScreenToRect).

JavaScript form calc — WORKING (`2f9fb05f5` enable + `24603fc57` mupdf fix)

mupdf JS is compiled in (FZ_ENABLE_JS=1, mujs one.c, pdf-js.c).
FinishLoading now calls pdf_enable_js(ctx, pdfdoc) (replacing the old
// TODO: support javascript assertion), so doc->js is live and edits run
pdf_update_page → pdf_calculate_form.

Cross-field calc works: verified on a JS form total = a + b — the total
recomputes on every edit (10 + 20 → 30, then a = 7 → 27).

Getting there needed an upstream mupdf bug fix (24603fc57): doc_getField
and doc_resetForm in pdf-js.c called the NULL-terminated variadic
pdf_dict_getl(ctx, trailer, Root, AcroForm, Fields) without the trailing
NULL, so the va_arg loop read stack garbage as an extra dict key and
getField() returned null non-deterministically (it surfaced in our build's
stack layout). Adding the NULL — which every other call site already has —
makes getField reliable, so sums/totals compute.

  • Security note: enabling JS runs document-level / open scripts and
    broadens the mujs attack surface vs the previous JS-off default. mujs itself
    is sandboxed to the PDF/form API (no file/network).

Other still-open polish: cross-page Tab (navigation is currently same-page),
comb cells aren't drawn in the edit overlay (plain edit + max-length only),
XFA-only forms fall back to read-only.

Reference implementation analyzed: ext/mupdf/platform/gl/gl-form.c,
gl-input.c, gl-main.c (the mupdf OpenGL viewer's form filling). It polls
widgets each frame and edits text/choice in a modal dialog; we want
in-place editing instead, but the mupdf API surface and the
mutate → update → re-render loop are the same.


1. What already exists in SumatraPDF (leverage, don't rebuild)

  • Widgets are already rendered. The page render path runs widget appearance
    streams: EngineMupdf.cpp:3538-3541 (pdf_run_page_widgets_with_usage /
    pdf_run_page_with_usage). So a checked checkbox / filled text already shows;
    they're just non-interactive.
  • Widgets are already hit-testable. GetFzPageInfo walks
    pdf_first_annot/pdf_next_annot (includes widgets) into pi->annotations
    (EngineMupdf.cpp:3261-3271); EngineMupdfGetAnnotationAtPos
    (EngineMupdf.cpp:4660-4704) returns the smallest annot containing a point —
    widgets included. AnnotationType::Widget exists (Annotation.h, value 20 ==
    PDF_ANNOT_WIDGET).
  • Mutation → re-render pipeline exists. Every annotation setter in
    Annotation.cpp (e.g. SetContents 351-373) does: ScopedCritSec(&docLock) →
    pdf_set_* → pdf_update_annot → (outside docLock) MarkNotificationAsModified
    which drops pageInfo->displayList under renderLock and sets the dirty flag
    (EngineMupdf.cpp:4721-4775); the caller then calls MainWindowRerender
    (SumatraPDF.cpp:2917). Form edits funnel through the same path.
  • Save / dirty / unsaved-changes is reusable as-is. modifiedAnnotations →
    EngineMupdfHasUnsavedAnnotations (EngineMupdf.cpp:4610) →
    EngineMupdfSaveUpdated / pdf_save_document (~4390) → CmdSaveAnnotations
    (Ctrl+Shift+S). A filled field is just a modified pdf_obj.
  • Coordinate mapping exists. DisplayModel::CvtFromScreen(Point/Rect,pageNo)
    (1197/1216) for click→page; CvtToScreen(pageNo, RectF) (1191) to place an
    overlay over a field rect; GetPageNoByPoint (1097).
  • Floated child-edit pattern exists. HomePage.cpp:962 creates a WC_EDITW
    as a child of win->hwndCanvas — exactly the pattern for an in-place text box
    over a field.

2. What's missing (the actual work)

  1. Classify a widget: type (text/checkbox/radio/combo/listbox/signature),
    field flags (read-only, multiline, password, comb, required), value, options.
  2. Mutate a widget value through mupdf under docLock, regenerate appearance,
    invalidate.
  3. Route input: a click-a-field branch in the canvas, a keyboard/focus
    path
    (there is no canvas keyboard handler today — keyboard is frame-level),
    and a per-window "field being edited" state.
  4. The direct-manipulation overlays: floated edit for text, dropdown for choice,
    immediate toggle for buttons; field highlight + Tab navigation.

3. mupdf API surface (verified against gl-form.c / form.h / annot.h)

Enumerate / classify:

  • pdf_annot *pdf_first_widget(ctx, pdf_page*), pdf_next_widget(ctx, pdf_annot*)
  • enum pdf_widget_type pdf_widget_type(ctx, pdf_annot*) →
    PDF_WIDGET_TYPE_{BUTTON,CHECKBOX,COMBOBOX,LISTBOX,RADIOBUTTON,SIGNATURE,TEXT}
  • fz_rect pdf_bound_widget(ctx, pdf_annot*) (page space)
  • int pdf_annot_field_flags(ctx, pdf_annot*) → PDF_FIELD_IS_READ_ONLY,
    PDF_TX_FIELD_IS_MULTILINE|PASSWORD|COMB, PDF_BTN_FIELD_IS_RADIO|PUSHBUTTON,
    PDF_CH_FIELD_IS_COMBO|EDIT|MULTI_SELECT, PDF_FIELD_IS_REQUIRED, …
  • const char *pdf_annot_field_label(ctx, pdf_annot*),
    const char *pdf_annot_field_value(ctx, pdf_annot*)
  • int pdf_text_widget_max_len(ctx, pdf_annot*),
    int pdf_text_widget_format(ctx, pdf_annot*) (NONE/NUMBER/SPECIAL/DATE/TIME)

Read/write values:

  • Text commit (runs validate/format/calculate/JS):
    int pdf_set_text_field_value(ctx, pdf_annot*, const char *value) — returns
    true if accepted.
  • Text live keystroke filter (maxlen/comb/format, no side effects):
    int pdf_edit_text_field_value(ctx, pdf_annot*, value, change, int *selStart, int *selEnd, char **newvalue).
  • Choice: int pdf_choice_widget_options(ctx, pdf_annot*, int exportval, const char *opts[]) (call with NULL to get count), pdf_choice_widget_value,
    int pdf_set_choice_field_value(ctx, pdf_annot*, const char *value),
    pdf_choice_widget_is_multiselect.
  • Checkbox/radio: int pdf_toggle_widget(ctx, pdf_annot*) (used by gl-form.c:740-744;
    confirm it's present in our mupdf header during Phase 0 — fallback is
    pdf_button_field_on_state + pdf_set_field_value / the event helpers).
  • Generic: int pdf_set_annot_field_value(ctx, pdf_document*, pdf_annot*, text, int ignore_trigger_events).

Event triggers (run the field's AA JavaScript; optional for v1):
pdf_annot_event_{enter,exit,down,up,focus,blur}(ctx, pdf_annot*).

Re-render / recalc:

  • int pdf_update_page(ctx, pdf_page*) — regenerates out-of-date appearance
    streams, runs form recalc, returns true if redraw needed. (gl-main.c:1101.)
  • int pdf_update_annot(ctx, pdf_annot*) — per-annot version (annot.h:881).
  • int pdf_update_open_pages(ctx, pdf_document*) — a JS calculate can change a
    field on another page; use after edits if the form has calculations.

Important header note (annot.h:865-899): pass the same pdf_annot* objects
to pdf_update_annot that were last used to render; don't reload page/annots in
between or change-tracking breaks. Our display-list cache already keeps them
stable per FzPageInfo.


4. Architecture (three layers)

4a. Engine layer — `EngineMupdf.cpp` + `Annotation.cpp`

New, thin, mirroring the existing annotation setters (same locking):

  • WidgetInfo EngineMupdfGetWidgetInfo(Annotation*) → { widgetType, fieldFlags, value, label, maxLen, txFormat, options[] } (read under docLock/ctx).
  • bool EngineMupdfSetWidgetText(Annotation*, const char* val) →
    docLock { pdf_set_text_field_value; pdf_update_annot }, then (outside docLock)
    MarkNotificationAsModified.
  • bool EngineMupdfToggleWidget(Annotation*) → pdf_toggle_widget (or set state).
  • bool EngineMupdfSetWidgetChoice(Annotation*, const char* val) /
    ...GetWidgetChoices(...).
  • Optional Annotation* EngineMupdfGetWidgetAtPos(...) that skips read-only and
    signature widgets and returns type info, layered on GetAnnotationAtPos.

Re-render: after a successful set, call the existing MarkNotificationAsModified

  • MainWindowRerender(win). For forms with JS calculations, also
    pdf_update_open_pages before rerender (Phase 4).

4b. Input layer — `Canvas.cpp` + a per-window editing state

  • New state on WindowTab/MainWindow: Annotation* editedWidget (+ overlay
    HWND, page no). A MouseAction or a separate flag for "editing a field".
  • Left button down (OnMouseLeftButtonDown, Canvas.cpp:1281, annot branch
    ~1311): if annot->type == Widget and not read-only, intercept before the
    drag/selection path. Classify:
    • checkbox/radio → toggle immediately, re-render, done.
    • text → begin in-place text edit (4c).
    • combobox/listbox → open dropdown (4c).
    • signature/pushbutton → ignore (v1) or show info.
  • Hover (OnMouseMove/OnSetCursorMouseNone, Canvas.cpp:1027/2201): set
    cursor (I-beam over text, hand over button/choice) and optionally draw a field
    highlight; reuse the existing annotationUnderCursor hover detection.
  • Keyboard / focus: while a text field is being edited, the floated child
    edit owns the keystrokes (it's a real HWND), so we get caret/selection/IME for
    free and don't need a new canvas keyboard handler. Tab/Esc/Enter are caught
    in the overlay's PreTranslateMessage (like the find bar) to commit / move /
    cancel.

4c. Direct-manipulation UI (the overlays)

  • Text field: float a WC_EDITW (or the wingui Edit) child of
    hwndCanvas, positioned with CvtToScreen(pageNo, fieldBounds), seeded with
    pdf_annot_field_value, ES_MULTILINE when PDF_TX_FIELD_IS_MULTILINE,
    ES_PASSWORD for password fields, font size ≈ field DA size × zoom. On commit
    (Enter for single-line, focus-loss, Tab) write back via
    EngineMupdfSetWidgetText; destroy the overlay; re-render. Reuses Win32 text
    editing — no custom caret/text layer (matches the analysis recommendation
    and the floated-edit pattern at HomePage.cpp:962).
  • Checkbox / radio: no text entry — toggle on click and re-render. A radio
    click selects its group's option (mupdf handles the group via the field).
  • Combobox / listbox: float a small dropdown (reuse the wingui ListBox /
    DropDown from CommandPalette/wingui) over the field, fill from
    pdf_choice_widget_options, commit the picked value with
    pdf_set_choice_field_value. Editable comboboxes (PDF_CH_FIELD_IS_EDIT) also
    allow typing (Phase 2/3).
  • Navigation: Tab / Shift+Tab → commit current and move to next/prev field;
    Enter → commit (and, for single-line, advance); Esc → cancel without writing.
    Field order: build an ordered list of editable widgets per page (by the field
    tab order if present, else top-to-bottom/left-to-right) for Tab traversal.
  • Highlight: tint editable fields lightly on hover / when the "fill mode" is
    active so the user can see what's fillable (like browsers' form highlight).

5. Threading & re-render rules (the sharp edges — same ones the find work hit)

  • All pdf_* field mutation runs on the UI thread holding docLock.
  • Call pdf_update_annot (or pdf_update_page) inside the mutation so the
    appearance stream regenerates; otherwise the cached display list re-renders the
    old appearance.
  • Call MarkNotificationAsModified outside docLock — it takes pagesLock
    then docLock/renderLock; calling it while holding docLock deadlocks
    (documented at Annotation.cpp:206). It drops the cached display list under
    renderLock.
  • Then MainWindowRerender(win) (cancels the background renderer, repaints).
  • The background render thread must not run a page while we mutate it — the
    display-list drop + CancelRendering in MainWindowRerender handle this, the
    same way annotation edits already work.

6. Phased plan

  • Phase 0 — spike / de-risk (½–1 day). Confirm f1040.pdf is a fillable
    AcroForm (not XFA-only) in our mupdf build. On left-click of a checkbox
    widget: classify via pdf_widget_type, toggle (pdf_toggle_widget), re-render
    via MarkNotificationAsModified + MainWindowRerender. This proves the whole
    mutate → appearance → invalidate loop end-to-end with the least UI.
  • Phase 1 — text fields. Floated WC_EDITW overlay, single-line, commit via
    pdf_set_text_field_value, re-render, save round-trips (fill → Ctrl+Shift+S →
    reopen shows the value). Get focus/commit/cancel solid.
  • Phase 2 — buttons + choice. Radio groups, comboboxes & listboxes via the
    dropdown overlay and pdf_choice_widget_*.
  • Phase 3 — polish / navigation. Tab/Shift+Tab/Enter/Esc traversal, hover
    cursors + field highlight, multiline & password & comb fields (live filter via
    pdf_edit_text_field_value), read-only/required affordances, match the edit
    font size to the field.
  • Phase 4 — robustness. JS calculate/format fields
    (pdf_update_open_pages), unsaved-changes prompt on close (reuse the
    annotation dirty flag + tab "unsaved" marker), "fill mode" toggle / command,
    reset-form, RTL, high-DPI/zoom/rotation correctness.

7. Test plan — `f1040.pdf`

Local copy: C:\Users\kjk\OneDrive\!sumatra\sumtest\annots\f1040.pdf. Bring up
on a simpler fillable form first (fewer fields, no JS) before the 1040.

On the 1040 verify, per phase:

  • Checkboxes: filing-status boxes (Single / Married filing jointly / …),
    "You as a dependent", digital-assets Yes/No — toggle, re-render, persist.
  • Radio groups: any mutually-exclusive group selects one and clears siblings.
  • Text fields: first/last name, home address — type, multiline where present.
  • Comb fields: SSN / EIN (fixed-cell comb) — characters land in cells,
    max-length enforced.
  • Numeric fields: the dollar-amount lines — number format; if the form has
    calculated totals, confirm pdf_update_open_pages recomputes them (or
    document the limitation if JS calc isn't run).
  • Save round-trip: fill several fields, Ctrl+Shift+S, reopen → values
    present; re-open in Adobe/Chrome → values present and valid.
  • Zoom/rotate: overlays line up with fields at 50% / 200% and rotated.

8. Key decisions / open questions

  • Win32 WC_EDITW vs wingui Edit vs custom caret → start with a floated
    Win32/wingui edit (free caret/IME/selection); custom on-page caret only if the
    font-mismatch-while-editing looks bad. Decided: floated edit.
  • Live validation vs commit-only → Phase 1 commit-only
    (pdf_set_text_field_value returns accept/reject); add live
    pdf_edit_text_field_value filtering for comb/maxlen in Phase 3.
  • JS / calculate → v1 runs whatever the pdf_set_*/pdf_update_page path
    runs (mupdf executes form JS if built with it); don't promise full JS. Note
    whether our mupdf build enables JS.
  • XFA → detect XFA-only forms and fall back to read-only (mupdf fills the
    AcroForm layer, not dynamic XFA).
  • Confirm pdf_toggle_widget exists in our mupdf headers (gl-form.c uses it;
    one analysis pass didn't find it in an older header) — checked in Phase 0.
  • Interaction with the new find UI / annotation edit panel → forms are a
    third input mode; make sure the floated field-edit, the find bar, and the
    annotation drag don't fight over focus/clicks.

9. Files to touch

  • src/EngineMupdf.cpp — widget classify + mutate helpers (new), reuse
    MarkNotificationAsModified (4721), GetAnnotationAtPos (4660), render path.
  • src/Annotation.cpp/.h — WidgetInfo + setters mirroring SetContents (351).
  • src/Canvas.cpp — click-a-field branch in OnMouseLeftButtonDown (1281) /
    OnMouseLeftButtonUp (1453); hover cursor (1027/2201).
  • New src/FormFields.cpp/.h (or fold into Canvas) — the editing-state machine +
    the floated edit / dropdown overlays + Tab navigation.
  • src/DisplayModel.cpp — CvtToScreen/CvtFromScreen (already there).
  • src/SumatraPDF.cpp — MainWindowRerender (2917), a "Fill form" command /
    mode if desired, unsaved-changes prompt.
  • Save path: EngineMupdfSaveUpdated / CmdSaveAnnotations (reused as-is).

Plus Merge (`docs/plus-merge.md`)

Merging from sumatrapdf-plus fork

Tracking selective merges from https://github.com/dengxibo/sumatrapdf-plus (a
Chinese-community fork "Sumatra PDF Plus", GPLv3, same license as upstream).

Fork layout / state

  • Remote is added locally as plus (git remote add plus https://github.com/dengxibo/sumatrapdf-plus.git).
  • All work is on plus/main. Branches plus/sumatrapdf-plus, plus/feature/read-aloud-tts,
    plus/feature/smart-dark-preview are stale ancestors of main — ignore them.
  • Fork point: 2854c78d6 ("re-register OpenWithProgids at startup", 2026-05-21).
  • Fork scope (as of last review): 89 commits, 193 files, +61,677 / −20,075 lines
    (a chunk of that is machine-generated translations and a whitespace-only
    ext/mupdf/source/fitz/font.c reformat).
  • Our master has ~1,600 commits since the fork point, so a plain git merge is
    not viable. We port selectively (see "Merge process" below).
  • They do use our code-gen system (cmd/gen-settings.ts, gen-commands) — good; but
    their base predates recent gen-code.ts changes, so regenerate, never take their
    generated Settings.h/cpp, Commands.h/cpp diffs verbatim
    .

Last reviewed commit on plus/main: c8387b1bb ("Bump to 3.7.18…", 2026-07-26).
Update this marker after every review pass.

Review pass 2026-07-26 (`37bba7a39` → `c8387b1bb`, 90 commits)

Already in master:

  • Find bar / find window polish (shared June history + master's find work)
  • Smart dark mode, CAD enhance (base), selection toolbar (base), TTS on master
  • daf4d5575 illustrated PDF / CAD false positive → master 9a565f51a (#5806)

Ported this pass (small, separate commits):

  • 3229c8b2c selection toolbar jitter (hide while selecting, throttle)
  • 89e4edfed Text annot button on selection toolbar
  • df1b2aab8 annotation Contents flush on switch/save/close (PDF path only)

Deferred / product decisions:

  • G. EPUB progressive loading (full subsystem, not just post-review fixes)
  • B WordLookup (+ 35fb36269 ZH→Hans only with B)
  • Ebook annotations (EbookAnnotations*)
  • Progressive search/ebook sync beyond master's stream-find

Skip:

  • Version bumps, update-check.txt, branding (L)
  • 27de3e915 continuous theme selection — mostly debug logf
  • Pinned Home tab (Plus 3.7.11: always-present pinned Home tab when tabs on;
    Ctrl+W on Home-only closes the window) — not wanted
  • Markdown + Mermaid (1c0601847): master already opens .md via MuPDF/cmark-gfm;
    Plus swaps in md4c + offline Mermaid via WebView2 — skip (not wanted)

Summary of fork changes, by feature

A. Read Aloud (TTS) — largest feature

Windows SAPI text-to-speech with word-by-word highlighting synced to speech; works on
PDF/EPUB/MOBI. Start from top of page / cursor / selection; pause/continue; speed
presets 0.25×–2.0×; separate Chinese/English rates; "smart bilingual" voice switching
(auto-picks a Chinese voice for CJK runs and an English voice otherwise), both local
SAPI and online voices; toolbar button with dropdown, menu, context menu; double-click
on blank area toggles read-aloud. Recommends NaturalVoiceSAPIAdapter for natural voices.

  • New files: src/TextToSpeech.{cpp,h}, src/ReadAloudHighlight.{cpp,h},
    sumatra-website/www/docs/Read-Aloud.md, tools/gen_read_aloud_trans.py
  • Integration spread through SumatraPDF.cpp, Toolbar.cpp, Menu.cpp, Canvas.cpp,
    DisplayModel.cpp, TextSelection.cpp
  • Settings: ReadAloudVoiceId, ReadAloudSpeakingRate (legacy),
    ReadAloudSpeakingRateZh/En, ReadAloudSmartVoiceZh/En, ReadAloudSmartOnlineVoiceZh/En
  • Commands: CmdReadAloud, CmdReadAloudFromCursor, CmdReadAloudFromTopPage,
    CmdReadAloudSelection, CmdPauseReadAloud, CmdContinueReadAloud, CmdStopReadAloud
  • Key commits: 95812be62, 95d695b1d, 4aa346bf5, 48753162f, b4d2fdd06,
    4a6a9ece3, 5a6c285e4, 2d3684974, 270f677d3, 4e5363143 (partly)
  • Note: we have our own ideas in docs/read-aloud-ux-ideas.md — compare before porting.

B. Offline dictionary / word lookup

Double-click a word (or "Look Up Selection") to show a floating dictionary popup from
offline dictionaries (SumatraDict.* .idx/.dat files in {exedir}/dict or
OfflineDictionaryPath); phonetic transcription for multi-sense entries; optional
pronunciation audio; "Search with Youdao Dict" command.

  • New files: src/WordLookup.{cpp,h} (~2,400 lines), src/LookupAudio.{cpp,h},
    src/FloatingPopupStyle.{cpp,h}, dict import scripts cmd/import-oaldpex-dict.ts,
    cmd/import-xdhy7-dict.ts, tools/gen_lookup_*_trans.py
  • Settings: OfflineDictionaryPath, EnableDoubleClickWordLookup
  • Commands: CmdLookupSelection, CmdToggleDoubleClickWordLookup,
    CmdSearchSelectionWithYoudaoDict
  • Key commits: f1722fbb3, 399d52b45, 649b31371, 65b413ab1 (audio optional)
  • Note: dictionary data is not in the repo (users supply it); import scripts convert
    OALD / xdhy7 sources.

C. Ask AI (Doubao / DeepSeek / ChatGPT)

Select text → "Ask AI" opens the provider's web chat in the default browser and pastes
the selection (clipboard + simulated paste; several commits fight with cold-start/paste
targeting per provider). Provider picker; can be fully hidden.

  • Settings: AiChatProvider (doubao/deepseek/chatgpt), EnableAskAI,
    AiChatUseDeepSeekInsteadOfDoubao (deprecated migration shim)
  • Commands: CmdAnalyzeSelectionWithDoubao
  • Key commits: df38d716d, 440ed7c7c, c578c0cf4, ead418671, 39df9ba19, 4c805377d
  • Assessment: China-centric providers, brittle browser-paste automation.

D. Smart PDF dark mode

A real dark-mode rendering pipeline for PDFs (not just invert): intercepts drawing via
a custom fitz device, recolors text/vector content using OKLab color math, classifies
images (photos vs. line-art/scans) to preserve photos and page-1 covers while darkening
scanned/line art; per-document color mode Auto / Black (full dark) / Light (original)
with toolbar buttons; render + engine caches; unit tests included. "Legacy" inversion
kept as default renderer initially.

  • New files (~3,500 lines): src/PdfDarkMode.h, PdfDarkModeAnalysis.cpp,
    PdfDarkModeCache.cpp, PdfDarkModeColor.cpp, PdfDarkModeDevice.cpp,
    PdfDarkModeEngineCache.cpp, PdfDarkModeImageBgBlend.cpp,
    PdfDarkModeImageClassifier.cpp, PdfDarkModeImageRules.cpp,
    PdfDarkModeImageStats.cpp, PdfDarkModeInternal.h, PdfDarkModeNoOp.cpp,
    PdfDarkModeOklab.cpp, PdfDarkModeProfile.cpp, PdfDarkModeScanProcess.cpp,
    unit tests src/utils/tests/PdfDarkMode{ImageClassifier,Oklab}_ut.cpp
  • Touches: EngineMupdf.cpp (+2,800 lines), RenderCache.cpp, libsumatrapdf.def
    (new exported MuPDF symbols), PdfFilter/PdfPreview stubs (PdfDarkModeNoOp.cpp,
    EngineNotifyNoOp.cpp) to fix DLL link
  • Settings: PdfDocumentColorMode (auto/black/light, persisted per session 3.7.x)
  • Commands: CmdSetPdfDocumentColorMode{Auto,Black,Light}, CmdTogglePreservePdfImages
  • Key commits: 67fe3f0e3, e8c329473, b68a0723e, c225f86f4, 65cd4d5de,
    171cb47a2, 50a66cebb, e4e78bcb8 (light-mode perf)

E. CAD / engineering-drawing PDF enhancement

Auto-detects CAD-style PDFs (hairline-heavy vector drawings, incl. WPS hairline and
screenshot PDFs) and thickens hairlines when rendering, with a toggle command.

  • New files: src/PdfCadDetect.{cpp,h}, src/PdfCadEnhanceDevice.{cpp,h},
    src/PdfCadEnhanceNoOp.cpp
  • Command: CmdToggleEngineeringDrawingEnhance
  • Key commits: 735894b11, b058ba496, 38006d0e8

F. Themes & window chrome

New named themes: Light-Warm (eye-care), Light-White, Dark-Dracula,
Dark-Black (plus System). One-click light/dark toggle remembering
LastDarkTheme/LastLightTheme. Dark-mode fixes throughout the chrome: canvas + TOC
scrollbar theming, menu check marks, maximized frame edges, toolbar separators,
tab UI. Windows 11-style caption glyphs drawn from generated geometry
(src/CaptionGlyphs.{cpp,h}, src/tools/gen_caption_glyphs.py) with DPI-aware layout and
interaction-state polish.

  • Touches: Theme.cpp (+400), Caption.cpp, Toolbar.cpp, TableOfContents.cpp,
    Menu.cpp, TabsCtrl.cpp, SvgIcons.cpp
  • Settings: LastDarkTheme, LastLightTheme; command CmdToggleLightDarkTheme
  • Key commits: 36468333a, e8c329473, 81defc78b, 171cb47a2, 597f0c47d,
    1cd0c85d6→b0002c393 (caption series), c4018ec06, 0e493c658, 2183086c1,
    5491f5520
  • Caution: our Theme.cpp/dark-mode code has evolved a lot since their base; expect
    heavy conflicts. Port ideas, not diffs.

G. Ebook (EPUB/MOBI/AZW3) engine work

Real changes inside vendored ext/mupdf/source/html/* (~1,000–1,700 changed lines per file)
even ignoring whitespace) plus Sumatra-side ebook code: faster loading of large ebooks,
NCX table of contents for MOBI/AZW3 and EPUB, progressive TOC loading (with graying-out
of not-yet-reachable in-book links), better Chinese mixed text/image layout, fixes for
garbled MOBI TOC from corrupt NCX/CNCX labels, blank MOBI picture-book pages
(self-closing `` + missing image records), Bookworm EPUB comic layout, EPUB/MOBI
dark-theme switching artifacts.

  • Touches: ext/mupdf/source/html/{html-layout,html-parse,css-apply,epub-doc,html-font}.c,
    src/MobiDoc.cpp (+1,188), src/EngineEbook.cpp (+1,370), src/HtmlFormatter.cpp,
    src/EbookFormatter.cpp, new src/EbookTypography.h, src/mupdf/mupdf_load_system_font.c,
    src/libsumatrapdf.def
  • Settings: per-doc LayoutDx/LayoutDy (flowed ebook page size)
  • Key commits: 937dc3454, 069eb2f23, 243092d1b, 7091e1684, 52239c27a
    (garbled TOC), 0a97bc1a0, 65b413ab1, c111312bc, d8ac31deb
  • Caution: this is the riskiest area to port — vendored mupdf may have moved upstream;
    diff their mupdf changes against our current mupdf, not the fork base.
  • Note: ext/mupdf/source/fitz/font.c diff is whitespace/line-endings only — ignore it.

H. Selection toolbar

Floating mini-toolbar after selecting text in a PDF: highlight, underline, strikeout,
Ask AI. Also fixed annotation menu visibility on non-PDF files.

  • New files: src/SelectionToolbar.{cpp,h}; setting SelectionToolbar (bool)
  • Key commits: db0b32b7a, f1722fbb3

I. Home page: list view + tips

HomePageViewMode setting: thumbnails (current grid) or list (filename + path).
ShowTips toggle. Thumbnail bookkeeping fields (ThumbnailDarkTheme,
ThumbnailBlankKnown, ThumbnailIsBlank — runtime-only) for dark-theme-aware
thumbnails and hiding blank ones. HomePage.cpp +756 lines.

  • Key commit: 0435f1ae6 (part of the 3.7.4 release commit), e4e78bcb8

J. GitHub-based auto-update

Rewired update checks to their repo (raw.githubusercontent.com/dengxibo/... with
jsDelivr fallback), update-check.txt in repo root, GitHub Releases downloads, download
speed/size progress UI, portable self-update that can replace the running exe,
update check on first launch, fix for manual check hanging.

  • Touches: src/UpdateCheck.cpp (+160/−66), update-check.txt
  • Key commits: 4e5363143, 56aefc51a, 17612a1e0, 60a8b3462, 077e00776,
    bc79cf9e6, 9938530ae
  • Assessment: URLs are theirs — do not merge as-is. Possibly worth porting:
    download progress UI, portable self-replace fix, hang fix — as ideas.

K. Standalone bug fixes — verified against master 2026-07-10 (`04726daed`)

Each fork fix was compared against current master to see if the bug still exists here.

Still applicable (bug present in master):

  • 5491f5520 — TOC scrollbar mouse capture stuck during tree reload. Master's
    TreeView::Clear() / SetTreeModel() call TreeView_DeleteAllItems with no
    capture guard; TOC filter/reload paths (ClearTocBox, LoadTocTree,
    ApplyTocFilter) hit exactly that. Port ONLY the src/wingui/TreeView.cpp hunk
    (CancelInProgressInteraction helper sending WM_CANCELMODE + 2 call sites);
    the TableOfContents.cpp hunk targets fork-only ReCreateTocTreeView. Small, clean.
  • 231e4124e — bin2coff.c buffer overflow (strcpy → bounded memcpy). Our
    src/tools/bin2coff.c still has the unbounded strcpys; bin2coff.exe still embeds
    fonts in the mupdf build. Low severity (inputs are build-controlled labels), fix is
    harmless. Their tests/test_invariant_bin2coff.c doesn't fit our test conventions —
    skip or adapt.
  • 8cc39e227 (EngineMupdf.cpp part only) — junk PDF page labels from scanned/OCR
    ebooks
    (.pdg filenames / per-page prefix-only labels shown instead of page
    numbers). Master's BuildPageLabelVec (src/EngineMupdf.cpp:1846) and
    GetPageLabeTemp (:4678) have none of the three guards. Won't apply cleanly —
    master refactored page labels to Str (PageLabelInfo::type is Str, prefix is
    pdf_obj*); port by hand: prefix-only check after the trivial-default early return,
    .pdg check before final return labels;, per-label fallback in GetPageLabeTemp.

Partially applicable (core fixed here; small residual pieces):

  • ac8a2f73b — crash closing tabs during background rendering. Core fz_context
    teardown race is ALREADY FIXED in master: RenderCache::CancelRendering
    (src/RenderCache.cpp:770) already waits for in-flight renders (fork re-implemented
    an equivalent wait), plus master has pauseRendering defenses the fork base lacked.
    Genuinely missing, port by hand if wanted: (a) IsValidZoom early-return guard in
    DisplayModel::SetViewPortSize; (b) in ReplaceDocumentInCurrentTab relayout the
    new doc before delete prevCtrl (master deletes at SumatraPDF.cpp:1738 before
    relayout at :1760).
  • edebf7dbe — crash opening file with tabs disabled. Structurally prevented in
    master: our async load creates the tab after load in LoadDocumentFinish
    (fork used a shell-tab-first model needing all those guards; those functions don't
    exist here). Optional hardening only: guard win->CurrentTab() in
    UpdateUiForCurrentTab (SumatraPDF.cpp:1581); make ReportIf(!tab) in
    ReplaceDocumentInCurrentTab (:1614) an early return; null-guard
    SetFrameTitleForTab.
  • 1369facda + 21ef059f7 — multi-monitor per-monitor-DPI UI fonts. Master
    already handles DPI detection + geometry (src/base/Dpi_win.cpp prefers
    GetDpiForWindow/GetDpiForMonitor; OnDpiChanged at SumatraPDF.cpp:5160
    rebuilds menu/toolbar/find bar) — the fork's Dpi.cpp rewrite is superseded. But the
    font half is a real master bug: GetAppMenuFontSize() (AppSettings.cpp:792)
    sizes fonts from system DPI via SystemParametersInfoW; font cache
    (src/base/Win.cpp:1871) has no DPI in its key; ResetCachedFonts() never runs on
    DPI change; toolbar text (Toolbar.cpp:1459) and tabs (Tabs.cpp:62) keep
    primary-monitor font size on other monitors. Porting = re-implementation, not
    cherry-pick (~120 lines of per-DPI font helpers in Win.cpp/WinDynCalls
    incl. SystemParametersInfoForDpi, DPI-keyed font cache, then rewire
    AppSettings.cpp font getters + OnDpiChanged). Medium effort/risk.

Not applicable:

  • c7a20f168 — compiler warnings: both are in fork-only code (PreparePageNavigation,
    their ebook changes). N/A.
  • 2183086c1 — dark-mode TOC scrollbar staying white: master's recursive
    EnumChildWindows dark-mode pass (DarkMode::setChildCtrlsSubclassAndTheme,
    SumatraPDF.cpp:2198/2345) already themes the nested trees; symptom can't occur here.
  • 069eb2f23 — "TOC loading optimization": actually a mix of fork branding
    (HomePage links) and MobiDoc work — reclassified into group G (ebooks), not a
    standalone fix. Also accidentally commits a stray debug-76c24b.log.

L. Branding / fork-only — do not merge

README rewrites (bilingual), readme.txt Chinese user guide, docs/github-publish-zh.md,
app renamed "Sumatra PDF Plus", their 3.7.x version numbering and release commits,
update-check.txt, fonts/README.txt, machine-generated translation blobs in
translations/*.txt (+11k lines, generated by their tools/gen_*_trans.py scripts —
if we port a feature, run our own translation flow instead), vs2022/* project file
additions (we generate via premake).

Merge process (for every future sync)

  1. Fetch: git fetch plus --no-tags
  2. What's new: git log --date=short --format='%h %ad %s' <last-reviewed>..plus/main
    where <last-reviewed> is the marker near the top of this file.
  3. Categorize new commits into the feature groups above (add new groups if needed)
    and update the group descriptions. Useful commands:
    • Full feature diff: git diff <fork-point>..plus/main -- <files> (fork point
      2854c78d6 — stable, never changes)
    • Real-change check on vendored code: add --ignore-all-space
    • One file's history: git log --oneline plus/main -- <file>
  4. Present to Krzysztof for review; record his decision per feature/commit in the
    Decision log below. Nothing is merged without an explicit decision.
  5. Port approved items:
    • Prefer git cherry-pick -x <sha> when it applies cleanly (-x records origin).
    • Otherwise port manually from the feature diff. Attribute in the commit message:
      Ported from dengxibo/sumatrapdf-plus <sha(s)>.
    • Settings/commands: re-add through cmd/gen-settings.ts / cmd/gen-commands.ts
      and run bun cmd/gen-code.ts — never take their generated files.
    • clang-format only our src/ files; keep ext/ edits minimal-style.
    • Skip their translations blobs; add new strings through our translation flow.
    • Build (bun cmd/build.ts -dbg) and run unit tests (bun cmd/run-unit-tests.ts -dbg).
  6. Update this file: bump the "Last reviewed" marker, log decisions and merge
    commits in the Decision log.

Gotchas

  • The fork's base (2854c78d6, 2026-05-21) is ~1,600 commits behind master. Never
    git merge plus/main. Cherry-picks will often conflict in SumatraPDF.cpp,
    Toolbar.cpp, Menu.cpp, Theme.cpp — porting by feature diff is usually easier
    than commit-by-commit for the big features.
  • Features are interleaved in shared files and in their "Release 3.7.x" commits; a
    single fork commit often touches several features.
  • Any fix in group K may already exist in master in a different form — verify before
    porting.
  • Their libsumatrapdf.def / premake file lists differ; regenerate ours rather than merging.

Decision log

Date Feature / commits Decision Result / notes
(pending) A. Read Aloud (TTS) —
(pending) B. Offline dictionary lookup — analyzed 2026-07-11; implementation notes + port plan in dict-support.md (recommendation: port UI/plumbing, replace their bespoke SumatraDict/MDX-converter data layer with a native StarDict reader)
(pending) C. Ask AI —
2026-07-11 D. Smart PDF dark mode merged as 86e5cb24c; ported all 15 PdfDarkMode* modules + 2 unit-test files by hand (Vec API .Size()/.at() → len()/[], utils/→base/ includes, no #pragma once, PCH opt-outs). KEY FINDING: the fork ships with the object-level OKLab renderer compile-time OFF (kPdfDarkModeRenderer = LegacyBitmapPostProcess in PdfDarkModeColor.cpp) — the live feature is per-doc color mode + classifier-driven image preservation in the bitmap recolor; we kept it identically dormant (wired into RenderPage but unreachable). SEMANTIC ADAPTATION: fork keys dark mode off their dark themes (feature F recolors pages per theme); master's themes never touch page colors, so ours keys off the effective page colors (FixedPageUI.InvertColors / custom dark colors) — with invert on, auto preserves photos (new default), black = old invert-everything, light = don't invert PDFs. Integration: RenderPageArgs.darkProfile + darkModeEpoch (request/entry staleness) in RenderCache, UpdateBitmapColors extended with linkColor+skipRects, GetBitmapRecolorSkipRects (content-stream image collection + classifier gating) in EngineMupdf, PdfDarkModeNoOp.cpp for filter/preview/test_engines/mac, fz_colorspace_n added to libsumatrapdf.def. Setting PdfDocumentColorMode (auto/black/light) + 4 commands. Verified end-to-end with a synthetic photo PDF (screenshots: photo preserved in auto, inverted in black, original in light; deterministic re-renders). Fork's RenderCache non-blocking CancelRendering + UpdateAfterThemeChange reorder (735894b11) still not taken — master's CancelRendering was already reworked.
2026-07-10 E. CAD line enhancement (735894b11, b058ba496, 38006d0e8) merged as 211f989d1; ported by hand against master (fork base predates Pixmap/PCH/Str refactors). Key adaptations: EngineeringDrawingEnhance mode pushed via setter from LoadSettings (gDisableFormJavaScript pattern) instead of reading gGlobalPrefs in PdfCadDetect.cpp — so no PdfCadEnhanceNoOp.cpp needed; real files compile everywhere (incl. mac) with mode defaulting to Off outside the app. Fixed fork's read-after-free of analysis-device stats (stats now caller-owned) and their unguarded fz_rethrow out of DetectCadPdf. Added 12 missing fz_* exports to libsumatrapdf.def (fork's approach was the NoOp stub instead). Skipped: their RenderCache/UpdateAfterThemeChange hunks (dark-mode toolbar fix, belongs to D). Setting editable in Advanced Settings dialog (enum values added).
2026-07-11 F. Themes & chrome ideas ported analysis + outcome in super-themes.md: light/dark toggle + System theme as 0de700a15, Light Warm palette as 8797b20f3 (Dracula/black already existed), Win11 caption glyphs as c837e3d92; their page-color-driving theme philosophy rejected (kept master's chrome-only themes); all dark-chrome bug fixes evaluated and found superseded by master's darkmodelib handling
(pending) G. Ebook engine work —
2026-07-10 H. Selection toolbar (db0b32b7a + follow-up polish) merged as dba10007e; ported SelectionToolbar.{cpp,h} by hand; folded the shared FloatingPopupStyle.{cpp,h} helpers into it (they only exist to share chrome with WordLookup, feature B). Buttons rewritten on top of master's CommandAvailability (GetCommandVisibility, CommandSurface::Toolbar): Copy, Read Aloud, Highlight, Underline, Squiggly, Strike Out; dropped fork's Ask AI / Look Up buttons (features C/B). Theme colors mapped to master's Theme.h (IsLightColor(ThemeWindowBackgroundColor()) for dark detection). Setting Annotations.SelectionToolbar (bool, default true). Skipped unrelated hunks bundled in db0b32b7a: Menu.cpp UINT_PTR fix (master already fixed via CommandAvailability refactor), EngineMupdfGetAnnotations quick-load/no-pagesLock change (master's locking was rewritten; possible perf win for large docs — evaluate separately), HwndSetIcon ICON_SMALL addition (unrelated; could take separately).
2026-07-11 I. Home page list view cherry-picked master independently grew its own list view (32d638e29, #4909, richer rows: thumbnail/size/pin/remove). Cherry-picked from the fork: the directory-path column (right-aligned, muted, path-ellipsized, only when the name leaves room) and the HomePageViewMode = thumbnails|list setting shape (replaces the prerelease-only HomePageShowList bool). Also fixed the fixed-64px size column truncating values like "177.80 KB" (now sized to its text). Skipped: their shell file-type icons (we show real thumbnails), tips/promos banner (ShowTips), dark-theme thumbnail bookkeeping (tied to their themes-recolor-pages design).
(pending) J. Auto-update changes — fork-specific URLs; ideas only
2026-07-10 K: TOC scrollbar capture stuck (5491f5520) merged ported TreeView.cpp hunk only, as a1b40ef1f
2026-07-10 K: junk .pdg PDF page labels (8cc39e227 part) merged hand-ported to Str-based page labels, as dc36c20ce
2026-07-10 K: bin2coff strcpy overflow (231e4124e) merged clean cherry-pick, as dbc91d370; skipped their C test file (doesn't fit our test conventions)
2026-07-10 K: render-teardown hardening (ac8a2f73b residual parts) merged IsValidZoom guard + relayout-before-delete, as 622dcb8d0; core race already fixed in master; skipped their WaitForRenderingComplete (duplicate) and CloseTab pauseRendering (covered by CloseWindow/~DisplayModel)
2026-07-10 K: multi-monitor DPI fonts (21ef059f7+1369facda) merged re-implemented against master's font cache, as 1c1fccdec; per-DPI font cache + SystemParametersInfoForDpi + WM_DPICHANGED re-apply; skipped their Dpi.cpp rewrite (master's Dpi_win.cpp already better)
2026-07-28 K: remaining multi-monitor DPI (30a67f0fa + rest of 1369facda) merged for #5827: GetApp*ForDpi, HwndSetTreeFontForDpi (TVM_SETITEMHEIGHT), trust WM_DPICHANGED wParam via frameDpi, full chrome refresh (toolbar/rebar/find bar/sidebar/caption), ENTERSIZEMOVE defer + settle, maximized NCCALCSIZE work-area clip; no TreeView HWND recreate (VBox ownership; item-height fix covers metrics)
2026-07-10 K: no-tabs open crash (edebf7dbe) skip structurally prevented by master's load-then-create-tab design; optional guards not taken
2026-07-10 K: compiler warnings (c7a20f168) skip warnings are in fork-only code
2026-07-10 K: dark TOC scrollbar (2183086c1) skip master's recursive dark-mode pass already themes nested trees
2026-07-10 L. Branding / fork-only skip readmes, renames, versioning, translations blobs, vs2022 files
2026-07-26 Review 37bba7a39..c8387b1bb (90 commits) triaged marker → c8387b1bb; see "Review pass 2026-07-26" above
2026-07-26 daf4d5575 illustrated PDF / CAD false positive merged as 9a565f51a (fixes #5806)
2026-07-26 3229c8b2c selection toolbar jitter merged hide while selecting; slack + throttle position updates
2026-07-26 89e4edfed text annot on selection toolbar merged CmdCreateAnnotText + selection-end LPARAM
2026-07-26 df1b2aab8 annotation Contents save merged PDF path: FlushContentsFromEdit, null guards, EN_KILLFOCUS; skipped ebook annot files
2026-07-26 G. EPUB progressive / B / C defer product / large ports; WordLookup plan in dict-support.md
2026-07-26 Pinned Home tab (Plus 3.7.11) skip / not wanted permanent pinned Home tab when all docs closed; keep master's no-tabs-empty home view
2026-07-26 Markdown + Mermaid (1c0601847) skip master already has MD via cmark-gfm; md4c + Mermaid/WebView2 not wanted
2026-07-26 27de3e915 continuous theme scroll skip commit is mostly logf

Read Aloud Ux Ideas (`docs/read-aloud-ux-ideas.md`)

Read Aloud UX ideas for SumatraPDF

UX-focused analysis of the Read Aloud feature (pre-release 3.7+) and proposals for improving discoverability, controls, and listening workflows. No implementation details.

Last updated after playback bar, Stop Reading, auto-scroll, toolbar pause icon, and tab-switch behavior review (master, June 2026).

Current experience (as implemented today)

Entry points

Where What users see
Toolbar Speak button — main click = smart start / pause / continue (tooltip reflects next action); dropdown arrow = full Read Aloud submenu
Main menu Read Aloud (TTS) submenu after Selection
Context menu Read Aloud (TTS) after Document; includes Start Reading From Cursor Position (right-click only)
Command palette (Ctrl+K) Read Aloud; Pause Reading; Continue Reading; Stop Reading; Start Reading From Top; Start Reading Selection
Playback bar Thin strip at the bottom of the canvas while a session is active (speaking or paused)

All three menus share one builder (BuildReadAloudMenuItems): when speaking → Pause Reading + Stop Reading; when paused → Continue Reading + Stop Reading; then explicit start commands and Voice → (System default + installed voices).

Playback bar (shipped)

While a read-aloud session is active (speaking or paused):

text
[Pause] [Stop]   Reading · Annual Report.pdf · page 12 of 240 · Smart start
  • Pause / Resume — left-side buttons; label switches to Resume when paused.
  • Stop — ends the session and clears resume state (distinct from pause).
  • Status line — document name, page X of Y (from highlight map + spoken offset), and scope label: Smart start, From top, From cursor, or Selection.
  • Placement — bottom of canvas, full width minus margin; relayouts on resize.
  • Visibility — shown on the window that owns the session; hidden on other SumatraPDF windows while audio may still play there.

The bar is the primary session control surface. Menus and palette duplicate transport actions for keyboard-first users.

Tab and window lifecycle (as implemented)

Switching away from a tab stops speech and clears the read-aloud session on the tab being left (LoadModelIntoTab → CloseDocumentInCurrentTab → ResetReadAloudStateForTab): TTS stops, resume text is freed, highlight and playback bar are torn down. There is no cross-tab “still reading in the background” state within one window.

Event Behavior today
Switch to another tab (same window) Stop speaking; clear session on previous tab; playback bar hidden
Pause, then stay on same tab Session persists; Continue Reading works
Close tab that was reading StopReadAloudIfSourceTab + tab removal
Close window StopReadAloudIfSourceWindow stops TTS for that window’s source tab

Implication: toolbar and menus always target the current tab, which is also the only tab that can have an active or paused session. Cross-tab orphan sessions and “bar shows tab A while canvas shows tab B” do not occur.

What works well

  • Multiple explicit start scopes — selection, first visible text in viewport (“From Top”), cursor position (context menu), plus smart default on toolbar / CmdToggleReadAloud (selection if present, else viewport).
  • Continuous listening — viewport / cursor / smart builds read from the start point through the end of the document; selection reads selection only. 1 KB TTS chunks chain automatically.
  • Word follow-along — current spoken word highlighted on the canvas (selection color; timer-driven repaint).
  • Auto-scroll — viewport follows the spoken word while reading; stops permanently if the user scrolls the highlight fully off-screen (respects manual navigation).
  • Resume — pause remembers position; Continue Reading picks up in the same tab (until you switch tabs).
  • Predictable tab policy — switching tabs ends listening and clears state; no hidden audio from another tab in the same window.
  • Stop — explicit end-of-session in playback bar, all three menus, and command palette (CmdStopReadAloud).
  • Session chrome — playback bar answers “what is playing, from where, how far” at a glance.
  • Discoverability — menubar, context menu, toolbar dropdown, palette, and playback bar.
  • Consistent naming — Pause Reading, Continue Reading, Stop Reading, Start Reading From Top (renamed from “From Here”).
  • Localized menu strings — Tr / TrN on menu labels.
  • Voice remembered — ReadAloudVoiceId advanced setting persists Voice submenu choice.
  • Sensible text cleanup — soft hyphens and line breaks normalized before speech.
  • Backend choice — WinRT Speech Synthesis with SAPI fallback.
  • User docs updated — Accessibility-and-Text-to-Speech.md describes continuous reading, playback bar, menus, and scopes.

Remaining pain points

Issue Why it hurts
Toolbar button still triple-duty One click = start or pause or continue. Tooltip helps but users must read it; no Stop on main click.
Two mental models for “start” Smart (CmdToggleReadAloud) vs explicit menu items (viewport-only, selection-only, cursor). Power users benefit; casual users may not know which to pick. Scope label on the bar helps after start, not before.
“From Top” wording Menu says Start Reading From Top; bar says From top. Same behavior (first visible text → end) but labels differ slightly.
Global audio, second window TTS is app-global. If one window is reading and the user focuses another SumatraPDF window, the focused window has no playback bar (only toolbar tooltip reflects global speaking state). Rare with single-window use; tab switch within a window is not affected (speech stops).
Tab switch ends session Intentional and coherent, but users who pause and switch tabs lose resume position without warning — a one-line toast (“Reading stopped”) on tab change could set expectations.
Silent permission failure Copy-restricted PDFs block read aloud with no user message when Perm::CopySelection is denied.
Highlight not configurable Follow-along always uses selection color; no toggle, dedicated read-aloud color, or disable.
No speech rate control Windows voices speak at system rate only; no in-app slow/normal/fast.
Playback bar has no Voice / settings Voice changes require opening a menu or palette; no ⚙ on the bar (deferred in original mock).
Scanned PDF dead-end “No text available to read aloud” toast with no OCR / next-step affordance.
Palette Stop always listed? CmdStopReadAloud lacks palette context gating (unlike Pause/Continue); may appear when no session is active.
No default keyboard shortcut Everything reachable via palette/menus but nothing bound in default accelerators for “start reading” or “pause”.
Context menu placement Read Aloud (TTS) is a sibling submenu after Document, not inline near Copy / selection actions.

What changed since the first UX pass

Original gap Status
Hard to discover (palette-only) Fixed — menubar, context menu, toolbar dropdown
Page-only, no “keep going” Fixed — reads through last page
No follow-along highlight Shipped — word highlight; toggle/color still missing
No playback chrome Fixed — playback bar with page progress and scope
No Stop, only Pause Fixed — Stop in bar, menus, palette
No auto-scroll Fixed — follows spoken word; user scroll-away disables
Dropdown strings not localized Fixed
Palette naming inconsistent Fixed
Voice list cluttering main menu Fixed — Voice submenu
Read from cursor / viewport Fixed — viewport + context-menu cursor item
Voice not remembered Fixed — ReadAloudVoiceId
“From Here” label unclear Improved — renamed Start Reading From Top; help table updated
User docs lag code Fixed — Accessibility page brought current
Cursor start disabled wrongly Fixed — same text-hit logic as I-beam cursor
Toolbar stop icon while pausing Fixed — pause bars icon while speaking
Cross-tab session confusion Not an issue — tab switch stops speech and clears session

Design principle (unchanged)

Treat Read Aloud as listening mode layered on normal reading — not a hidden palette command. Users should see: what is reading, how to control it, and how far through the content they are.

The playback bar delivers this with minimal chrome. Next polish should reduce toolbar ambiguity and improve first-run clarity, not add modals or permanent toolbar widgets.

1. Toolbar transport clarity (highest impact remaining)

Playback bar and tab policy are solid; the toolbar main button is the main leftover friction.

Proposal Rationale
Optional Stop on toolbar Split button, long-press, or right-click — today Stop is only on bar, menus, palette
Consider simpler main-click rule e.g. click = smart start only when idle; when speaking/paused on current tab, rely on bar for pause/stop (toolbar becomes “start” only) — reduces triple-duty
Fix pause vs stop icon Done — pause bars while speaking

2. Multi-window polish (low priority)

Tab switch within a window is already handled. Remaining edge case: two SumatraPDF windows, one reading:

Proposal Rationale
Compact notice in focused window “Reading in other window · Report.pdf” when TtsIsSpeaking() and source tab is elsewhere
Or stop on focus change Heavier-handed; only if users report confusion

Optional: toast when tab switch ends a paused session (“Reading stopped”) so users know Continue is no longer available.

3. Playback bar polish (incremental)

Addition Notes
⚙ Voice (or chevron) on bar Opens Voice submenu without leaving canvas — matches original mock
Click filename Switch to session tab / scroll to spoken word
Stronger button affordance Slightly higher contrast vs notification background; optional icons ⏸ ⏹ for scanability
Unify scope strings Bar: “From top of page” or match menu exactly: “Start Reading From Top” → shorten to Viewport / Top of view
Paused state in status “Paused · …” prefix so line does not look identical to active reading

4. Clarify start commands for first-time users

Label Behavior
Read Aloud (toolbar / palette) Smart: selection if present, else viewport top → end of document
Start Reading From Top Viewport top → end of document
Start Reading From Cursor Position Right-click point → end (context menu)
Start Reading Selection Selection only; does not continue past selection

Tooltip on toolbar dropdown items (one line each) would help more than renaming again. Consider a short in-app tip the first time Read Aloud runs.

5. Context menu: Read Aloud near Copy (optional)

When text is selected:

  • Inline Start Reading Selection under Copy (disabled when no selection).
  • Keep full Read Aloud (TTS) submenu for voice, viewport start, cursor, transport.

Mirrors how Translate sits in the selection flow.

6. Settings and highlight control

In Advanced Options (or playback-bar ⚙):

Setting Purpose
Read aloud voice Done — ReadAloudVoiceId
Speech rate Slow / Normal / Fast if backend exposes it
Highlight while reading On/off (default on)
Highlight color Separate from selection color
Auto-scroll to spoken word On/off (default on; today always on until user scrolls away)

7. Clearer empty and blocked states

Situation Target message
No extractable text “No text on this page.” → [Find text on next page] if feasible
Copy permission denied “This document doesn’t allow copying text.” (today: silent)
No voices installed Link to Windows Settings → Speech
Tab switch while paused “Reading stopped” (session cleared on tab change; optional toast)

Keep toasts short; one primary action each.

8. Command palette context

Command Show when
Pause Reading TtsIsSpeaking()
Continue Reading paused session on current tab (only tab that can have a session)
Stop Reading any active session (speaking or paused)
Start commands document loaded; not redundant with transport when speaking

Hiding Stop when idle reduces palette noise.

9. Default keyboard shortcut

Bind a discoverable shortcut (e.g. Ctrl+Shift+S toggle pause/resume, or Ctrl+Shift+R start) in default accelerators; document in Keyboard shortcuts. Custom rebinding already supported.

10. Selection scope expectations

Start Reading Selection does not continue past the selection (by design). Users may expect “read my highlight then keep going.” Options:

  • Rename to Read Selection Only / add subtitle in menu.
  • Or add Read Selection, Then Document as an advanced scope (larger lift).

Call out the difference in help and first-run tip.

What to defer

  • Full audiobook features (bookmarks, sleep timer, MP3 export)
  • Neural / cloud voices (stay on Windows installed voices)
  • Per-paragraph voice or SSML editor
  • Replacing Narrator / NVDA for blind users (document limitations in help)
  • Floating / undocked playback widget

Suggested user story (one sentence)

“Open a document, start Read Aloud from the toolbar or right-click menu, choose where to begin (viewport, selection, or cursor), pick a voice under Voice, and listen through the document with the current word highlighted and the page scrolling along — use the playback bar or menu to pause, resume, or stop, and see which page you’re on.”

Quick wins vs larger lifts (revised)

Quick wins Larger lifts
Palette gating for Stop / Continue Voice ⚙ on playback bar
Toast when copy permission blocks TTS Speech rate in Advanced Options
Toast when tab switch ends reading Highlight on/off + color setting
Unify bar scope label with menu wording Inline Start Reading Selection under Copy
“Paused ·” prefix on bar status line OCR / next-page path on no-text
Menu tooltips for start scopes Read selection then continue scope
Default keyboard shortcut Multi-window “reading elsewhere” notice
Bar button contrast / optional icons Optional toolbar Stop affordance
Fix toolbar pause icon Cross-tab session policy (not needed — tab switch clears session)

The largest remaining gap is toolbar triple-duty (start / pause / continue on one button) and discoverability of start scopes for first-time users — not cross-tab state, which is already coherent.

  • Commands: CmdToggleReadAloud, CmdPauseReadAloud, CmdContinueReadAloud, CmdStopReadAloud, CmdReadAloudFromTopPage, CmdReadAloudSelection — palette; no default shortcut
  • TTS menu ids: CmdTtsMenuReadCurrentPage, CmdTtsMenuContinueReading, CmdTtsMenuReadSelection, CmdTtsMenuPauseReading, CmdTtsMenuReadFromCursor, CmdTtsMenuStopReading, CmdTtsVoice*
  • Menus: BuildReadAloudMenuItems / RebuildReadAloudMenu in SumatraPDF.cpp; menubar + context in Menu.cpp
  • Toolbar: speak + dropdown (ShowTtsVoiceMenu); dynamic tooltip/icon in Toolbar.cpp
  • Playback bar: ReadAloudPlaybackBar.cpp — ReadAloudPlaybackBarUpdateSession, ReadAloudPlaybackPauseOrResume, ReadAloudPlaybackStop
  • Scope: WindowTab::readAloudScope — Smart, Viewport, Selection, Cursor; set in ReadAloudInTab, ReadAloudFromViewportTopInTab, etc.
  • Sessions: gReadAloudSessionTab (persists through pause), gReadAloudSourceTab (active TTS)
  • Progress: ReadAloudGetProgressPage in ReadAloudHighlight.cpp
  • Auto-scroll: ReadAloudUpdateAutoScroll, ReadAloudOnUserViewChanged — disables on user scroll-away
  • Document text: ReadAloudHighlightBuildFromDocument, ReadAloudGetViewportStart, ReadAloudGetCursorStart
  • Chunking: kReadAloudMaxChunkLen = 1024, ReadAloudSpeakChunk, WM_TTS_EVENT chaining
  • Highlight: PaintReadAloudHighlight, highlight timer in ReadAloudHighlight.cpp
  • Resume: readAloudText, readAloudResumePos, chunk fields on WindowTab
  • Tab switch: LoadModelIntoTab → CloseDocumentInCurrentTab → ResetReadAloudStateForTab (stops TTS, clears session)
  • Copy permission: silent return in read-aloud entry paths when Perm::CopySelection denied
  • TTS: TextToSpeech.cpp — WinRT + SAPI; ReadAloudVoiceId in settings
  • User docs: sumatra-website/www/docs/Accessibility-and-Text-to-Speech.md, sumatra-website/www/docs/Commands.md, version-history 3.7 entry

Temp Investigate Scroll 6154 (`docs/temp-investigate-scroll-6154.md`)

Investigate sticky scroll at 180%+ zoom (#6154)

Temp notes for an agent on another machine. Delete when done.

Issue: https://github.com/sumatrapdfreader/sumatrapdf/issues/6154
The useful comment: https://github.com/sumatrapdfreader/sumatrapdf/issues/6154#issuecomment-5591969544

Goal

Reproduce (or rule out) sticky / not-smooth vertical scrolling at ~180% zoom and above. Reporter says it happens with two-finger touchpad and with the vertical scrollbar, on a light PDF.

Do not "fix" until you have seen it, or have a measurement that shows zoom-dependent hitch that a human would call sticky.

Reporter setup (from the comment + settings file)

  • Pre-release SumatraPDF, Windows 11 Pro, Best Performance
  • 3K OLED, Scaling 250%, maximized window 0 0 2880 1800 (WindowState = 3)
  • Scroll: Precision touchpad two-finger, later also native vertical scrollbar
  • Sample: lorem.pdf (3 pages, ~403 KB)
  • Settings: SumatraPDF-settings.txt

Keep copies (already downloaded here):

  • C:\Users\kjk\OneDrive\!sumatra\bugs\bug-6154-lorem.pdf
  • C:\Users\kjk\OneDrive\!sumatra\bugs\bug-6154-SumatraPDF-settings.txt

If those paths are missing, download the two GitHub attachments again.

Relevant settings (not the whole file):

text
DefaultDisplayMode = automatic
DefaultZoom = fit content
SmoothScroll = true
ScrollLineAmount = 16
Scrollbars = hidden
MouseWheelTurnsPage = false
ZoomIncrement = 0.01
CustomScreenDPI = 0
WindowPos = 0 0 2880 1800
WindowState = 3

FixedPageUI.UseOverlayScrollbar = true is leftover; live scrollbar mode is Scrollbars = hidden. File history for lorem.pdf had Zoom = 250.917 (matches the tiny ZoomIncrement).

What the first machine could not do

Ran on a 96 DPI ~1920×1200 box, no interactive desktop, no real touchpad. Injected SendInput is dropped; posted WM_MOUSEWHEEL / WM_VSCROLL work. You cannot feel stickiness there.

If your machine has 250% scaling, a touchpad, and a real desktop, manual repro first. That is the missing piece.

Manual repro (do this first on a 250% / 3K box)

  1. Build: bun cmd/build.ts -dbg → out/dbg64/SumatraPDF.exe
  2. Copy reporter settings into a throwaway dir, then:
text
out\dbg64\SumatraPDF.exe -for-testing -appdata <throwaway> -log -log-to-file <log> -zoom 180 <lorem.pdf>

Maximize. Two-finger scroll. Repeat at 100%, 180%, 250%. Then set Scrollbars = windows and drag the thumb / click the track.

  1. Also try SmoothScroll = false. If stickiness vanishes, it is the animation + tile paint, not input.

What "sticky" means here: hitch / catch-up while moving, not "smooth-scroll eases for ~500 ms" (that is kSmoothScrollRate = 15 in src/Canvas.cpp and happens at every zoom).

Log: Slow rendering: N ms fires only if a tile takes >300 ms (src/RenderCache.cpp). Absence of that line does not mean scrolling felt fine.

Why 180% on 250% DPI is special

zoomReal = zoomVirtual * 0.01 * (screenDPI / 72).

Display Zoom % zoomReal notes
96 DPI 180 2.4 still cheap
240 DPI (250% scale) 100 3.33
240 DPI 180 6.0 reporter threshold
240 DPI 250 8.33 their saved zoom

RenderCache::GetTileRes (src/RenderCache.cpp) splits a page when it is larger than maxTileSize (initially SM_CXSCREEN × SM_CYSCREEN). res goes 0 → 1 → 2 as factorAvg grows. On the 96 DPI box, CustomScreenDPI = 240 + 180% was the first case with res=2.

Hypothesis: at 180% × 250% DPI on a 2880×1800 window, each new tile is ~screen-sized; scrolling into uncached tiles hitchs. Once cached, jumps are cheap. Matches "even light PDFs" and "scrollbar too" (both request new tiles).

CustomScreenDPI only fakes the zoom math, not Windows DPI, window pixel size, or touchpad. Prefer a real 250% display.

Measurements already taken (96 DPI machine, 2026-09-11)

Posted input, full work area, lorem.pdf, continuous, WaitRenderIdle then scroll.

Case First idle After scroll
96 DPI, 100–250%, wheel 120 / line / page 8–68 ms moved immediately
96 DPI, 250%, 30-delta "touchpad" wheel same same as 120-delta
CustomScreenDPI=240, 100% page-down 42 ms instant
CustomScreenDPI=240, 180% (zoomR=6, res=2) ~27 s to first idle then page-down instant
CustomScreenDPI=240, 250% (zoomR=8.3, res=2) 447 ms instant
SmoothScroll on, 8× wheel 120 settle ~545 ms at every zoom that is the ease, not a hitch
SmoothScroll off, page-down settle ~85 ms

Wheel and scrollbar line/page did not get slower at 180%+ once tiles were ready. The 27 s first-idle at 180%/240 is suspicious but was also inflated by WaitRenderIdle (it InvalidateRect+UpdateWindow every poll — see below).

Earlier 2026-09-08 probe on the same box (tests/tmp/6154-probe.ts, 96 DPI, 100 vs 200%) also saw no zoom-dependent wheel hitch.

Code to read

  • Wheel / smooth scroll: src/Canvas.cpp CanvasOnMouseWheel, OnVScroll, StartOrUpdateSmoothScrollY (kSmoothScrollRate = 15)
  • Tile split: src/RenderCache.cpp GetTileRes, slow log durMs > 300
  • Idle snapshot (do not treat as "user scroll cost"): src/SumatraControl.cpp SnapshotRenderIdle — invalidates twice per poll
  • DPI for zoom: src/SumatraPDF.cpp gSettings->customScreenDPI / DpiGetForHwnd
  • Overlay vs hidden: ScrollbarsUseOverlay() / ScrollbarsAreHidden() in src/SumatraPDF.cpp

Instrumented re-run (if you cannot feel it)

Do not register a suite test. Write tests/tmp/repro-6154.ts (gitignored) or run from a scratch file.

Launch with -for-testing -dbg-control -appdata <dir> -log -log-to-file <log>. Helpers: tests/win-automation.ts launchControlled, tests/control.ts waitForRenderIdle / TestDpi, tests/winapi.ts getScrollInfo / postMessage.

Window pos format is WIDTHxHEIGHT@XxY (e.g. 1872x1200@0x0), not x,y,dx,dy. Use the work area, not the default right-half test window — small viewport understates tile cost.

Compare:

  1. Native DPI, zoom 100 / 180 / 250
  2. If native DPI is 96, also CustomScreenDPI = 240 at those zooms (math-only stand-in)
  3. Input: WM_MOUSEWHEEL delta 120 (mouse), delta 30 (precision pad), WM_VSCROLL SB_LINEDOWN / SB_PAGEDOWN
  4. SmoothScroll true vs false
  5. Scrollbars = hidden vs windows

Log from waitForRenderIdle: zoomV=… zoomR=… res=… vp=WxH ready=… tile=… cache=…. res jumping 0→2 at the reporter's 180% is the signal.

Time scroll start → first nPos change and time until WaitRenderIdle after a page-sized jump into uncached tiles. Do not wait-for-idle before the jump if you want to see hitch; that pre-warms the cache.

Pitfalls

  • WaitRenderIdle paints every poll. A long first-idle can be the probe starving render threads, not the user's hitch. Prefer log timestamps / Slow rendering / CPU, or sample GetScrollInfo during a burst of wheel messages without waiting idle first.
  • Posted WM_VSCROLL SB_THUMBTRACK does nothing useful: OnVScroll reads si.nTrackPos from GetScrollInfo, which posting does not set. Use SB_PAGEDOWN or drive the overlay/native thumb with mouse messages if the desktop allows it.
  • Hidden scrollbars: GetScrollInfo on the canvas can still move (nPos); overlay mode updates via OverlayScrollbarGetInfo.
  • -for-testing skips session restore and does not save settings. Portable out/dbg64/SumatraPDF-settings.txt can still load. Use -appdata.
  • Command ids: cmdId("Cmd…"), never hardcode.

What would count as a repro

Any of:

  • You feel hitch at ≥180% on 250% scaling, gone or much less at 100%, with touchpad or scrollbar
  • Tile render of visible pages regularly >~50–100 ms at that zoom, and scroll motion stalls until the tile lands
  • res / zoomR at 180%×250% DPI clearly larger than at 100%, and scrolling into a new tile row hitches

If it is smooth at 250% on a 250% 3K box with their settings, say so; then the report is machine-specific (GPU, touchpad driver, OLED) and not a general tile bug.

Do not

  • Run tests/run-almost-all.ts / run-all.ts
  • Commit a fix or a suite test from this doc
  • Treat 545 ms smooth-scroll settle as the bug

Todo Maybe (`docs/todo-maybe.md`)

epub hyphenation

MuPDF can auto-hyphenate HTML-engine docs (EPUB/MOBI/FB2/HTML) when CSS is
hyphens: auto and the document has a language. Default CSS is
hyphens: manual; soft hyphens (U+00AD) already work without tables.

Sumatra builds MuPDF with FZ_ENABLE_HYPHEN=0 (cmd/deps-build-defs.ts,
vcxproj). Lookup then always returns null and MuPDF warns
no hyphenation table for lang='en' (etc.). That is logged; it is not shown
as "Errors in document" (issue #6109). PDFs are unaffected.

Do not enable FZ_ENABLE_HYPHEN just to silence the warning.

If we want EPUB auto-hyphenation as a feature:

  • FZ_ENABLE_HYPHEN=1 and FZ_ENABLE_HYPHEN_ALL=0 (std set includes en)
  • embed ext/mupdf/resources/hyphen/hyph-std.zip (~174 KB). hyph-all.zip
    is ~638 KB and is MuPDF's default if FZ_ENABLE_HYPHEN_ALL is left at 1
  • Sumatra's build does not link those zips in today (fonts come from
    IDR_EMBEDDED_PAK, see src/mupdf/noto_sumatra.c); flipping the define
    without embedding is a linker error (_binary_hyph_std_zip)
  • first use per language unpacks the zip and builds a trie (CPU + RAM)
  • books with hyphens: auto would reflow differently (page breaks vs now)
  • languages not in the zip still warn

Upside: better reflow in narrow/justified EPUB columns and long compounds
when the book asks for auto hyphenation.


Upgrade Chmdec (`docs/upgrade-chmdec.md`)

Upgrading chmdec

How to update SumatraPDF's vendored plain-C CHM reader (ext/chmdec/chm.c +
ext/chmdec/chm.h) from the upstream dist.

Upstream source

The amalgamated files are generated upstream by bun cmd/build-dist.ts. Never
edit ext/chmdec/chm.c or ext/chmdec/chm.h by hand in SumatraPDF.

Steps

1. Fetch latest upstream dist

Clone or refresh a shallow copy, then record the commit hash:

bash
git clone --depth 1 https://github.com/kjk/chmdec.git /tmp/chmdec-update
git -C /tmp/chmdec-update rev-parse HEAD

If /tmp/chmdec-update already exists:

bash
git -C /tmp/chmdec-update pull --ff-only

2. Copy dist files into ext/chmdec

bash
cp /tmp/chmdec-update/dist/chm.c ext/chmdec/chm.c
cp /tmp/chmdec-update/dist/chm.h ext/chmdec/chm.h

On Windows (PowerShell), use Copy-Item ... -Force — run the two copies as
separate commands (avoid ; chaining in the shell tool).

Optionally refresh the license if it changed:

bash
cp /tmp/chmdec-update/LICENSE.md ext/chmdec/LICENSE.md

Do not clang-format files under ext/chmdec/ (vendored code).

3. Update ext/versions.txt

Record the import in ext/versions.txt:

text
chmdec          dist       YYYY-MM-DD
  https://github.com/kjk/chmdec
  commit <full-upstream-hash>
  amalgamated dist/chm.c + dist/chm.h only

Use today's date for YYYY-MM-DD and the hash from step 1.

4. Check for API changes

Diff the new ext/chmdec/chm.h against the previous version.

Pay special attention to:

  • New functions — may need export entries and ChmFile.cpp updates
  • Removed/renamed functions — update callers and exports
  • chm_open buffer lifetime — the in-memory buffer is not copied; it must
    outlive the chm_ctx (SumatraPDF keeps file bytes in ChmFile until
    chm_ctx_free)

Current integration points:

File Role
src/ChmFile.cpp / src/ChmFile.h CHM document open / entry read
src/ChmDump.cpp -dump-chm diagnostics
src/SumatraTest.cpp -test-chm LZX / open regression
src/libsumatrapdf.def Hand-maintained export list for libsumatrapdf.dll
premake5.lua chmdec static lib project
ext/versions.txt Vendored dependency version log

5. Update DLL exports (if API changed)

chmdec is linked into libsumatrapdf.dll (and into the static EXE). The DLL build of
SumatraPDF.exe, PdfFilter, and PdfPreview must not also link the
chmdec static lib — they import chm_* from libsumatrapdf.dll via
src/libsumatrapdf.def.

When new public functions are added, append them to the ; chmdec exports
section in src/libsumatrapdf.def.

Current chmdec exports:

text
chm_ctx_new
chm_ctx_free
chm_open
chm_close
chm_read_entry
chm_get_entries
LZX_test_pretree_make_decode_table

LZX_test_pretree_make_decode_table is a test-only hook used by -test-chm
(src/SumatraTest.cpp); it is defined in chm.c but not declared in chm.h.

6. Build and smoke-test

bash
bun cmd/build.ts -dbg
# optional CHM regression (needs a sample .chm):
# out/dbg64/SumatraPDF.exe -for-testing -test-chm path\to\file.chm
# out/dbg64/SumatraPDF.exe -for-testing -dump-chm path\to\file.chm

Also run bun tests/issue-chm-lzx.ts if LZX decode tables changed.

Commit message template

text
Update chmdec to kjk/chmdec <short-hash>

Imported dist/chm.c and dist/chm.h from https://github.com/kjk/chmdec @ <full-hash>.

Notes

  • ext/chmdec is a single-file amalgamation (like sqlite / heicdec): compile
    chm.c only; include chm.h from callers.
  • The static library project is named chmdec in premake / MSBuild; sources
    keep the historical chm_* C API names.
  • Static SumatraPDF-static and test_engines still link chmdec directly
    (no libsumatrapdf.dll). The DLL-split products import via def exports only.

Upgrade Djvudec (`docs/upgrade-djvudec.md`)

Upgrading djvudec

How to update SumatraPDF's vendored plain-C DjVu decoder (ext/djvudec/djvu.c +
ext/djvudec/djvu.h) from the upstream dist.

Upstream source

The amalgamated files are generated upstream by bun cmd/build-dist.ts. Never
edit ext/djvudec/djvu.c or ext/djvudec/djvu.h by hand in SumatraPDF.

Steps

1. Fetch latest upstream dist

Clone or refresh a shallow copy, then record the commit hash:

bash
git clone --depth 1 https://github.com/kjk/djvudec.git /tmp/djvudec-update
git -C /tmp/djvudec-update rev-parse HEAD

If /tmp/djvudec-update already exists:

bash
git -C /tmp/djvudec-update pull --ff-only

2. Copy dist files into ext/djvudec

bash
cp /tmp/djvudec-update/dist/djvu.c ext/djvudec/djvu.c
cp /tmp/djvudec-update/dist/djvu.h ext/djvudec/djvu.h

On Windows (PowerShell), use Copy-Item ... -Force — run the two copies as
separate commands (avoid ; chaining in the shell tool).

Do not clang-format files under ext/djvudec/ (vendored code).

3. Update ext/versions.txt

Record the import in ext/versions.txt:

text
djvudec         main       YYYY-MM-DD
  https://github.com/kjk/djvudec
  commit <full-upstream-hash>
  amalgamated dist/djvu.c + dist/djvu.h only

Use today's date for YYYY-MM-DD and the hash from step 1.

4. Check for API changes

Diff the new ext/djvudec/djvu.h against the previous version. The public API
is also documented in upstream src/djvu.h.

Pay special attention to:

  • New functions — may need export entries and EngineDjvuDec.cpp updates
  • Removed/renamed functions — update src/EngineDjvuDec.cpp and exports
  • djvu_init() — must be called once before concurrent decode (bilinear
    scaler table). djvu_doc_open also calls it, but that is not enough when
    render threads race on first open. SumatraPDF guards this in
    EngineDjvuDec.cpp via DjvuDecInitOnce() (SRWLOCK, same pattern as
    EngineDjVu.cpp).

Current integration points:

File Role
src/EngineDjvuDec.cpp Engine using the djvu_* API
src/EngineCreate.cpp Creates the djvudec engine for DjVu files
src/libsumatrapdf.def Hand-maintained export list for libsumatrapdf.dll
premake5.lua djvudec static lib project
ext/versions.txt Vendored dependency version log

5. Update DLL exports (if API changed)

djvudec is statically linked into libsumatrapdf.dll; SumatraPDF.exe imports
djvu_* symbols from that DLL via src/libsumatrapdf.def.

When new public functions are added, append them to the ; djvudec exports
section in src/libsumatrapdf.def.

Current djvudec exports:

text
djvu_init
djvu_ctx_new
djvu_ctx_free
djvu_ctx_set_lazy_iw44
djvu_ctx_set_no_compose
djvu_ctx_set_iw_max_chunks
djvu_ctx_set_bgr
djvu_doc_open
djvu_doc_close
djvu_doc_page_count
djvu_doc_page_info
djvu_page_get_type
djvu_page_render
djvu_page_render_info
djvu_page_render_into
djvu_image_destroy
djvu_doc_page_id
djvu_doc_page_title
djvu_doc_page_by_name
djvu_page_text_get_zones
djvu_text_zones_destroy
djvu_doc_outline
djvu_outline_destroy
djvu_page_get_links
djvu_page_links_destroy

6. Update EngineDjvuDec (if needed)

Review src/EngineDjvuDec.cpp for any API mismatches. Common cases:

  • New init function → ensure a once-guarded call before concurrent use
  • New metadata/render helpers → wire up if SumatraPDF should expose them
  • Changed struct fields → fix accessors

After editing src/EngineDjvuDec.cpp, run:

bash
clang-format -i src/EngineDjvuDec.cpp

7. Build and smoke-test

bash
bun cmd/build.ts -dbg

Test with:

bash
./out/dbg64/SumatraPDF.exe -for-testing <path-to>.djvu

Use a .djvu from C:\Users\kjk\OneDrive\!sumatra\bugs\ if available.

8. Commit message

Include the upstream commit hash:

text
Update djvudec to kjk/djvudec <short-hash>

Imported dist/djvu.c and dist/djvu.h from https://github.com/kjk/djvudec @ <full-hash>.

Append prompt: ... if the change was AI-assisted (per Agents.md).

Architecture notes

  • ext/djvudec is a single-file amalgamation (like sqlite): compile
    djvu.c directly; include djvu.h.
  • EngineDjvuDec keeps a single shared djvu_doc (read-only after open);
    djvu_page_render_into / metadata calls are re-entrant on the same doc.
  • Thread-safety analysis lives in docs/djvudec-threading.md and upstream
    thread-safety.md.
  • djvudec is the only DjVu engine (the libdjvu-based EngineDjVu has been
    removed; EngineCreate.cpp creates EngineDjvuDec for all DjVu files).

Checklist

  • Copied dist/djvu.c and dist/djvu.h from kjk/djvudec
  • Updated ext/versions.txt with import date and upstream commit hash
  • Checked djvu.h for API changes
  • Updated src/libsumatrapdf.def if exports changed
  • Updated EngineDjvuDec.cpp if integration changed
  • Did not clang-format ext/djvudec/
  • bun cmd/build.ts -dbg succeeds
  • Smoke-tested a .djvu file

Wishlist (`docs/wishlist.txt`)

-- text viewer (high)

Important thing: make it work even with gigantic files by limiting how much of
the file we load in memory (1-10 MB?). We would only build and index for each
line, consisting of:

  • position in the file
  • length in bytes
  • encoding (to support various encodings; but we would start by only supporting
    ascii/utf8)
  • measured size of this line

We would build that index for the whole file in the background thread, then
only load the needed part as the user scrolls through the document.

-- hex viewer (high)

-- better looking notifications (med)

Visual style of notifications is dated. Use more modern look e.g. inspired by
Chrome or Android.

-- faster re-layout for eBooks (med, risky)

Layout time is dominated by measuring strings.

Split layout into 2 or 3 phases:

  • generate instructions (text fragments, images, font/style changes etc.)
  • measure strings, images etc.
  • calculating positions of the elements given page dx/dy and break them into pages

When users resizes the window, we would only need to redo phase 3. A small
complication: when a string doesn't fit in a single line, we need to split
it into to string instructions. We would need to be able to do it e.g. by
adding a "compound" instruction, that just contains one or more other instructions.
That way the instruction stream would be almost-immutable, and we could turn
a e.g. a long string into 2 smaller strings by replacing string instruction with
"compound" instructions that points to the original string instructions (so that we
can undo that in the next layout) and 2 or more substrings.

Changing default font would require redoing phase 2 and 3.

-- eBook: re-introduce preserving top of page after re-layout (risky, med)

The code to preserve current top of page after re-layout was so complicated
that I had to remove it in order to implement dual pages in eBook mode.
It would be nice to bring it back in a saner way and without the problem
of breaking the styling.
It would be easier if we implement faster re-layout as described above and
have every element remember its reparse point (instead of having it on a
per-page basis).

That way we would generate instructions just once. A page would just be:
(index of first instruction, number of instructions).

We could avoid breaking styling because now we have access to the whole instruction
stream and we could quickly scan back instructions from any point to find
formatting instructions and recover current styling.

-- manual cropping of margins for PDFs (med, risky)

Screen space is always at premium and most PDFs have very wasteful
margins. Good Reader has a really nice feature for manual cropping of margins
(https://www.goodiware.com/gr-man-view-pdf.html#crop). They have a mode for
manually selecting visible part of the PDF, similar to how many graphics
program implement cropping. Cropping can be set for all pages or separately
for odd/even pages. Cropping can also be reset. After cropping, the program
only shows the non-cropped part. This would be especially valuable for small
screens (netbooks/small laptops).

Note: Automatic cropping is currently implemented as "Fit Content" View mode.
Note to note: it's similar but not really the same. One big difference is that
with this style of cropping, for the purpose of layout and display the cropped
part doesn't exist. Fit Content positions the page so that it's out of the view,
but change zoom and it's really there.
Plus, automatic cropping has limitations. There are many cases where a PDF has
lots of white-space but cannot be cropped because has some small thing there,
like a page number.

-- Editing/saving of PDF forms (high)

-- Integration with web-based backup/viewer system (high)

The idea is that users could easily backup their PDFs on the server. They would
have a convenient access to those PDFs from Sumatra as well as being able to
view them on the web. Basically it would be private Scribd for PDFs only. The
web service would have to be paid (since on-line storage is rather expensive)
but there would be free accounts (with quota, similar to how Dropbox works).

-- Document library management (high)

Similar to how e.g. Picasa manages photos or iTunes manages mp3 files.

User would tell us which directories contain PDF and other supported files
(or, on Win 7, we could use windows search or scan the whole hard-drive to
automatically find all files). We would index the files (their filenames, metadata,
maybe extract text for full-text search), build thumbnails and allow efficient
browsing/searching those files.

This would be a good feature for those who have large collections of PDFs
(compared to using file explorer or open file dialog for locating the file).

-- use pdfium (high)

Google released https://pdfium.googlesource.com/pdfium/ which is Foxit codebase
under BSD license.

It would probably be a bunch of work to integrate this (although at first it
could be done alongside mupdf, since we have necessary abstractions to plug
another engine). So that's the downside.

I've briefly looked at the code and it might have some benefits over mupdf:

  • they have hooks for form editing (which should make implementing this much
    easier)
  • their printing code looks more efficient
  • they probably support some of the more advanced PDF features
  • they are probably faster and (thanks to Google) more secure

Writing Docs (`docs/writing-docs.md`)

Writing user docs (sumatra-website www/docs)

Pattern for feature pages, modeled on https://outliner.tana.inc/learn/features/sidebar.

Page shape

  1. # Title — the feature name, as it appears in the UI.
  2. Summary (1–2 sentences, first thing after the title): what the feature is,
    where it lives in the UI, what you use it for. No version note before it.
  3. Version note on its own line after the summary: **Available in version 3.6 or later.**
  4. Bold lead sentence naming the most common use, then an at a glance list:
    one bullet per capability, **Name:** what it does, with its shortcut.
  5. Task sections, in order of how often people need them.
    • Headings are tasks, not nouns: "Add a favorite", "Collapse the sidebar", not "Favorites".
    • Under each, list every way to do it: mouse / menu, keyboard shortcut,
      Command Palette command, advanced setting, command-line.
  6. ## Tips — 3–6 short, practical bullets ("Use X to ...").
  7. Reference sections (settings tables, flags, DDE) near the end.
  8. ## See also — links to related pages with a few words on why.

Short pages (troubleshooting, one-off how-tos) only need 1, 2 and the content.

Style

  • Second person, present tense, imperative steps: "Press Ctrl + B".
  • Shortcuts in code: Ctrl + Shift + N. Menu paths bold: Settings → Advanced Settings....
  • Command Palette: Ctrl + K, then Toggle Favorites. Name the command id (CmdFavoriteToggle) once, for rebinding.
  • Put the fix right after the problem: "If X, do Y."
  • Tips, notes and caveats are one line, prefixed Tip: / Note:. No paragraphs of background.
  • Link the first mention of another feature to its page.
  • Don't document bugs or history; say how it works now. Keep (ver 3.7+) markers where behavior differs by version.

Videos

A video goes on its own line, e.g. right after the title:
:video <youtube link> <r2 link> (the R2 link is the web version under
https://files.sumatrapdfreader.org/assets/sumatrapdf/docs/video/). The website and the
in-app manual (gen_docs.render.js) render it as an embedded YouTube player. How to record
and upload one: "Doc videos" in the website's agents.md.

Index

SumatraPDF-documentation.md groups pages by task (getting started, reading, ...).
Every page link must stay inside a :columns block — the in-app TOC is built from those.
Pages under ## Misc docs (up to ## Downloads) are excluded from the in-app manual.


Test SumatraPDF Settings (`docs/test/SumatraPDF-settings.txt`)

For documentation, see https://www.sumatrapdfreader.org/settings/settings3-7.html

DefaultDisplayMode = automatic
DefaultZoom = fit page
DisableJavaScript = false
AllowExternalImages = false
EnableTeXEnhancements = false
EscToExit = false
FullPathInTitle = false
LazyLoading = false
MainWindowBackground = #80fff200
NoHomeTab = false
HomePageSortByFrequentlyRead = false
HomePageViewMode = thumbnails
ThumbnailsType = full
ReloadModifiedDocuments = true
RememberOpenedFiles = true
RememberStatePerDocument = true
RestoreSession = true
ReuseInstance = true
ShowMenubar = true
ShowMenubarWithTabs = false
ShowPageNumberInTabs = false
ShowHomePageReadingProgress = true
ShowChaptersInEbooks = false
ShowTips = true
ShowToolbar = true
Toolbar = show
ToolbarPosition = top
SearchUIFloating = false
ShowFavorites = false
SortFavoritesByName = false
ShowToc = true
AlwaysShowSidebar = false
SidebarOnRight = false
ShowLinks = false
HighlightFormFields = true
ClickEdgeToTurnPage = false
DisableLinks = false
ExplorerQuickLook = false
RememberViewOffsetOnPageTurn = false
MouseWheelTurnsPage = false
ScrollEdgeTurnsPage = true
ShowDocumentFocusIndicator = false
ShowAnnotationNotification = true
ShowFileNavigateHint = true
ShowAnnotationAuthorInTooltip = false
ShowTocPageNumbers = true
AutoGenerateTOC = false
ShowStartPage = true
SidebarDx = 0
Scrollbars = windows
ScrollbarInSinglePage = false
SmoothScroll = true
ScrollLineAmount = 16
SaveMemory = 50
PaddingAfterLastPage = false
IgnoreDestinationZoom = false
HighlightLinkDestination = false
CitationHoverDelay = -1
ReadAloudSpeed = 1
ReadingAutoScrollSpeed = 40
ReadingBar [
Background = #66ffe082
Invert = false
Height = 0
]
FastScrollOverScrollbar = false
PreventSleepInFullscreen = true
TabWidth = 300
Theme = Light
HelpTheme = app
DocumentColorsFollowTheme = off
TocDy = 0
ToolbarShowReadAloud = false
ToolbarSize = 18
TreeFontName = automatic
TreeFontSize = 0
UIFontSize = 0
DisableAntiAlias = false
EngineeringDrawingEnhance = off
DisableAutoLinks = false
UseSysColors = false
UseTabs = true
SelectionToolbar = true
TabsMru = false
CtrlTabSimple = false
ZoomLevels =
ZoomIncrement = 0

FixedPageUI [
Grayscale = false
TextColor = #000000
BackgroundColor = #ffffff
SelectionColor = #ffff00
WindowMargin = 2 4 2 4
PageSpacing = 4 4
PageGrid [
Width = 72
Height = 72
Subdivisions = 4
OffsetX = 0
OffsetY = 0
Color = #8080ff
Style = dots
Units = in
]
]

EBookUI [
FontSize = 0
LineSpacing = 0
LayoutDx = 0
LayoutDy = 0
IgnoreDocumentCSS = false
]

ComicBookUI [
WindowMargin = 0 0 0 0
PageSpacing = 4 4
CbxMangaMode = false
LimitToWindowWidth = false
LimitToWindowHeight = false
AutoRotateWidePages = false
LandscapeAsSpread = true
]

ImageUI [
DefaultZoom = shrink to fit
LimitToWindowWidth = false
LimitToWindowHeight = false
LandscapeAsSpread = true
]

ChmUI [
UseFixedPageUI = false
]

MarkdownUI [
UseFixedPageUI = false
]

HtmlUI [
UseFixedPageUI = false
]

ClaudeCode [
Model = sonnet
Effort = 1
SkipPermissions = false
BgColor = #ffffff
]

GrokBuild [
Model = grok-4.5
Effort = 1
AlwaysApprove = false
BgColor = #ffffff
]

CodexBuild [
Model = gpt-5.5
Sandbox = 1
SkipSandbox = false
BgColor = #ffffff
]

AntiGravity [
Model = gemini-3.8-flash-medium
Effort = 1
AutoApprove = true
BgColor = #ffffff
]

AIChatSidebarDx = 0

Annotations [
HighlightColor = #ffff00
UnderlineColor = #8bf05d
SquigglyColor = #f199d2
StrikeOutColor = #e24745
FreeTextOpacity = 100
FreeTextSize = 12
FreeTextBorderWidth = 1
FreeTextAlignment = left
PresetColors = #ffff00 #8bf05d #99defa #f199d2 #e24745 #ff0000 #0000ff #000000
InkColor = #66ffff00
InkColors = #66ffff00 #668bf05d #6699defa #66f199d2 #66e24745
InkBorderWidth = 16
]

ExternalViewers [
]

ForwardSearch [
HighlightOffset = 0
HighlightWidth = 15
HighlightColor = #6581ff
HighlightPermanent = false
]

PrinterDefaults [
PrintScale = shrink
Collate = default
PrintDpi = 0
]

Fullscreen [
ShowToolbar = false
Toolbar = hide
ShowMenubar = false
]

SelectionHandlers [
]

TextSnippets [
]

Shortcuts [
]

Themes [
]

TabGroups [
]

CustomScreenDPI = 0

You're not expected to change those manually

WindowState = 1
WindowPos = 0 0 0 0
SearchUIWindowPos = 0 0 0 0
AnnotationsWindowShowNote = false
HelpWindowPos = 0 0 0 0
FileStates [
]
SessionData [
]
TimeOfLastUpdateCheck = 0 0
OpenCountWeek = 0
PropWinPos = 0 0
CheckForUpdates = true

Settings below are not recognized by the current version


Test Test (`docs/test/test.md`)

Test document

Test data for test_engines -model-toc and the MarkdownModel /
MarkdownToc unit tests. Keep it small and keep the headings: the table of
contents below is what the test prints.

Headings

Every heading becomes a table-of-contents entry, anchored by its GitHub slug.

A nested heading

Text under a level 3 heading.

adc_intr_ctl . TRANS_EN

The slug of this one is adc_intr_ctl--trans_en: underscores survive,
punctuation is dropped, each space becomes its own dash.

A relative link and an external one.

Code

js
let n = 1;

A table

column meaning
one first
two second

Ext A Freetype Docs FTL (`ext/a-freetype/docs/FTL.TXT`)

The FreeType Project LICENSE
----------------------------

text
2006-Jan-27

                Copyright 1996-2002, 2006 by
      David Turner, Robert Wilhelm, and Werner Lemberg

Introduction

The FreeType Project is distributed in several archive packages;
some of them may contain, in addition to the FreeType font engine,
various tools and contributions which rely on, or relate to, the
FreeType Project.

This license applies to all files found in such packages, and
which do not fall under their own explicit license. The license
affects thus the FreeType font engine, the test programs,
documentation and makefiles, at the very least.

This license was inspired by the BSD, Artistic, and IJG
(Independent JPEG Group) licenses, which all encourage inclusion
and use of free software in commercial and freeware products
alike. As a consequence, its main points are that:

text
o We don't promise that this software works. However, we will be
  interested in any kind of bug reports. (`as is' distribution)

o You can  use this software for whatever you  want, in parts or
  full form, without having to pay us. (`royalty-free' usage)

o You may not pretend that  you wrote this software.  If you use
  it, or  only parts of it,  in a program,  you must acknowledge
  somewhere  in  your  documentation  that  you  have  used  the
  FreeType code. (`credits')

We specifically permit and encourage the inclusion of this
software, with or without modifications, in commercial products.
We disclaim all warranties covering The FreeType Project and
assume no liability related to The FreeType Project.

Finally, many people asked us for a preferred form for a
credit/disclaimer to use in compliance with this license. We thus
encourage you to use the following text:

"""
Portions of this software are copyright © The FreeType
Project (https://freetype.org). All rights reserved.
"""

Please replace with the value from the FreeType version you
actually use.

  1. Definitions

Throughout this license, the terms package', FreeType Project',
and FreeType archive' refer to the set of files originally distributed by the authors (David Turner, Robert Wilhelm, and Werner Lemberg) as the FreeType Project', be they named as alpha,
beta or final release.

You' refers to the licensee, or person using the project, where using' is a generic term including compiling the project's source
code as well as linking it to form a program' or executable'.
This program is referred to as `a program using the FreeType
engine'.

This license applies to all files distributed in the original
FreeType Project, including all source code, binaries and
documentation, unless otherwise stated in the file in its
original, unmodified form as distributed in the original archive.
If you are unsure whether or not a particular file is covered by
this license, you must contact us to verify this.

The FreeType Project is copyright (C) 1996-2000 by David Turner,
Robert Wilhelm, and Werner Lemberg. All rights reserved except as
specified below.

  1. No Warranty

THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO
USE, OF THE FREETYPE PROJECT.

  1. Redistribution

This license grants a worldwide, royalty-free, perpetual and
irrevocable right and license to use, execute, perform, compile,
display, copy, create derivative works of, distribute and
sublicense the FreeType Project (in both source and object code
forms) and derivative works thereof for any purpose; and to
authorize others to exercise some or all of the rights granted
herein, subject to the following conditions:

text
o Redistribution of  source code  must retain this  license file
  (`FTL.TXT') unaltered; any  additions, deletions or changes to
  the original  files must be clearly  indicated in accompanying
  documentation.   The  copyright   notices  of  the  unaltered,
  original  files must  be  preserved in  all  copies of  source
  files.

o Redistribution in binary form must provide a  disclaimer  that
  states  that  the software is based in part of the work of the
  FreeType Team,  in  the  distribution  documentation.  We also
  encourage you to put an URL to the FreeType web page  in  your
  documentation, though this isn't mandatory.

These conditions apply to any software derived from or based on
the FreeType Project, not just the unmodified files. If you use
our work, you must acknowledge us. However, no fee need be paid
to us.

  1. Advertising

Neither the FreeType authors and contributors nor you shall use
the name of the other for commercial, advertising, or promotional
purposes without specific prior written permission.

We suggest, but do not require, that you use one or more of the
following phrases to refer to this software in your documentation
or advertising materials: FreeType Project', FreeType Engine',
FreeType library', or FreeType Distribution'.

As you have not signed this license, you are not required to
accept it. However, as the FreeType Project is copyrighted
material, only this license, or another one contracted with the
authors, grants you the right to use, distribute, and modify it.
Therefore, by using, distributing, or modifying the FreeType
Project, you indicate that you understand and accept all the terms
of this license.

  1. Contacts

There are two mailing lists related to FreeType:

text
o [email protected]

  Discusses general use and applications of FreeType, as well as
  future and  wanted additions to the  library and distribution.
  If  you are looking  for support,  start in  this list  if you
  haven't found anything to help you in the documentation.

o [email protected]

  Discusses bugs,  as well  as engine internals,  design issues,
  specific licenses, porting, etc.

Our home page can be found at

text
https://freetype.org

--- end of FTL.TXT ---


Ext A Freetype Docs GPLv2 (`ext/a-freetype/docs/GPLv2.TXT`)

GNU GENERAL PUBLIC LICENSE
Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

text
Preamble

The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.

When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.

We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and
modification follow.

GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  1. This License applies to any program or other work which contains
    a notice placed by the copyright holder saying it may be distributed
    under the terms of this General Public License. The "Program", below,
    refers to any such program or work, and a "work based on the Program"
    means either the Program or any derivative work under copyright law:
    that is to say, a work containing the Program or a portion of it,
    either verbatim or with modifications and/or translated into another
    language. (Hereinafter, translation is included without limitation in
    the term "modification".) Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
    source code as you receive it, in any medium, provided that you
    conspicuously and appropriately publish on each copy an appropriate
    copyright notice and disclaimer of warranty; keep intact all the
    notices that refer to this License and to the absence of any warranty;
    and give any other recipients of the Program a copy of this License
    along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  1. You may modify your copy or copies of the Program or any portion
    of it, thus forming a work based on the Program, and copy and
    distribute such modifications or work under the terms of Section 1
    above, provided that you also meet all of these conditions:
text
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.

b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.

c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License.  (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)


These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  1. You may copy and distribute the Program (or a work based on it,
    under Section 2) in object code or executable form under the terms of
    Sections 1 and 2 above provided that you also do one of the following:
text
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,

b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,

c) Accompany it with the information you received as to the offer
to distribute corresponding source code.  (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  1. You are not required to accept this License, since you have not
    signed it. However, nothing else grants you permission to modify or
    distribute the Program or its derivative works. These actions are
    prohibited by law if you do not accept this License. Therefore, by
    modifying or distributing the Program (or any work based on the
    Program), you indicate your acceptance of this License to do so, and
    all its terms and conditions for copying, distributing or modifying
    the Program or works based on it.

  2. Each time you redistribute the Program (or any work based on the
    Program), the recipient automatically receives a license from the
    original licensor to copy, distribute or modify the Program subject to
    these terms and conditions. You may not impose any further
    restrictions on the recipients' exercise of the rights granted herein.
    You are not responsible for enforcing compliance by third parties to
    this License.

  3. If, as a consequence of a court judgment or allegation of patent
    infringement or for any other reason (not limited to patent issues),
    conditions are imposed on you (whether by court order, agreement or
    otherwise) that contradict the conditions of this License, they do not
    excuse you from the conditions of this License. If you cannot
    distribute so as to satisfy simultaneously your obligations under this
    License and any other pertinent obligations, then as a consequence you
    may not distribute the Program at all. For example, if a patent
    license would not permit royalty-free redistribution of the Program by
    all those who receive copies directly or indirectly through you, then
    the only way you could satisfy both it and this License would be to
    refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.

  1. The Free Software Foundation may publish revised and/or new versions
    of the General Public License from time to time. Such new versions will
    be similar in spirit to the present version, but may differ in detail to
    address new problems or concerns.

Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  1. If you wish to incorporate parts of the Program into other free
    programs whose distribution conditions are different, write to the author
    to ask for permission. For software which is copyrighted by the Free
    Software Foundation, write to the Free Software Foundation; we sometimes
    make exceptions for this. Our decision will be guided by the two goals
    of preserving the free status of all derivatives of our free software and
    of promoting the sharing and reuse of software generally.

    text
    NO WARRANTY
  2. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
    FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
    OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
    PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
    OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
    TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
    PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
    REPAIR OR CORRECTION.

  3. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
    REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
    INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
    OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
    TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
    YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
    PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGES.

    text
    END OF TERMS AND CONDITIONS

    How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

text
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year>  <name of author>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

text
Gnomovision version 69, Copyright (C) year  name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

The hypothetical commands show w' and show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than show w' and show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.

, 1 April 1989
Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.


Ext Mupdf Docs Index (`ext/mupdf/docs/index.rst`)

Welcome to MuPDF

.. welcome:
.. image:: images/mupdf-icon.svg
:width: 33%
:align: center
:alt: [MuPDF Logo]
:target: welcome

MuPDF is an open source software framework for viewing and converting PDF, XPS,
and E-book documents. There are viewers for various platforms, several command
line tools, and a software library for building tools and applications.

It is available under either the GNU GPL Affero license <https://www.gnu.org/licenses/agpl-3.0.html>_
or licensed for commercial <https://artifex.com/licensing?utm_source=rtd-mupdf&utm_medium=rtd&utm_content=inline-link#commercial-license>_
use from Artifex Software, Inc <https://artifex.com/?utm_source=rtd-mupdf&utm_medium=rtd&utm_content=inline-link>_.

.. toctree::
:caption: Guides
:maxdepth: 1

text
guide/what-is-mupdf
license
guide/install
guide/using-with-javascript
guide/using-with-java
guide/using-with-android
tools/index

.. toctree::
:caption: Cookbook
:maxdepth: 1

text
cookbook/mupdf-explored
cookbook/c/index
cookbook/progressive-loading
cookbook/javascript/index

.. toctree::
:caption: Reference
:maxdepth: 1

text
reference/common/index
reference/c/index
reference/javascript/index
reference/cxx-and-derived-bindings

.. toctree::
:caption: Other
:maxdepth: 1

text
other/changes
other/third-party

Ext Mupdf Docs Requirements (`ext/mupdf/docs/requirements.txt`)

furo
linkify-it-py
myst-parser
readthedocs-sphinx-search==0.3.2
sphinx==8.2.3
sphinxcontrib-googleanalytics
sphinxcontrib-imagesvg
pipcl


Ext Mupdf Docs Cookbook Mupdf Explored (`ext/mupdf/docs/cookbook/mupdf-explored.md`)

MuPDF Explored

Read the
MuPDF Explored
book by Robin Watts for an in depth exploration of MuPDF.


Ext Mupdf Docs Cookbook Progressive Loading (`ext/mupdf/docs/cookbook/progressive-loading.md`)

Progressive Loading

What is progressive loading?

The idea of progressive loading is that as you download a PDF file into a browser, you can display the pages as they appear.

MuPDF can make use of 2 different mechanisms to achieve this. The first relies on the file being "linearized", the second relies on the caller of MuPDF having fine control over the http fetch and on the server supporting byte-range fetches.

For optimum performance a file should be both linearized and be available over a byte-range supporting link, but benefits can still be had with either one of these alone.

Progressive download using "linearized" files

Adobe defines "linearized" PDFs as being ones that have both a specific layout of objects and a small amount of extra information to help avoid seeking within a file. The stated aim is to deliver the first page of a document in advance of the whole document downloading, whereupon subsequent pages will become available. Adobe also refers to these as "Optimized for fast web view" or "Web Optimized".

In fact, the standard outlines (poorly) a mechanism by which 'hints' can be included that enable the subsequent pages to be found within the file too. Unfortunately this is very poorly supported with many tools, and so the hints have to be treated with suspicion.

MuPDF will attempt to use hints if they are available, but will also use a linear search of the file to discover pages if not. This means that the first page will be displayed quickly, and then subsequent ones will appear with 'incomplete' renderings that improve over time as more and more resources are gradually delivered.

Essentially the file starts with a slightly modified header, and the first object in the file is a special one (the linearization object) that:

  • indicates that the file is linearized.
  • gives some useful information (like the number of pages in the file).

This object is then followed by all the objects required for the first page, then the "hint stream", then sets of object for each subsequent page in turn, then shared objects required for those pages, then various other random things.

While page 1 is sent with all the objects that it uses, shared or otherwise, subsequent pages do not get shared resources until after all the unshared page objects have been sent.

The Hint Stream

Adobe intended "Hint Stream" to be useful to facilitate the display of subsequent pages, but it has never used it. Consequently you can't trust people to write it properly - indeed Adobe outputs something that doesn't quite conform to the specification.

Consequently very few people actually use it. MuPDF will use it after sanity checking the values, and should cope with illegal/incorrect values.

So how does MuPDF handle progressive loading?

MuPDF has made various extensions to its mechanisms for handling progressive loading.

Progressive streams

At its lowest level MuPDF reads file data from a fz_stream, using the fz_open_document_with_stream call. (fz_open_document is implemented by calling this). We have extended the fz_stream slightly, giving the system a way to ask for meta information (or perform meta operations) on a stream.

Using this mechanism MuPDF can query:

  • whether a stream is progressive or not (i.e. whether the entire stream is accessible immediately).
  • what the length of a stream should ultimately be (which an http fetcher should know from the Content-Length header).

With this information MuPDF can decide whether to use its normal object reading code, or whether to make use of a linearized object. Knowing the length enables us to check with the length value given in the linearized object - if these differ, the assumption is that an incremental save has taken place, thus the file is no longer linearized.

When data is pulled from a progressive stream, if we attempt to read data that is not currently available, the stream should throw a FZ_ERROR_TRYLATER error. This particular error code will be interpreted by the caller as an indication that it should retry the parsing of the current objects at a later time.

When a MuPDF call is made on a progressive stream, such as fz_open_document_with_stream, or fz_load_page, the caller should be prepared to handle a FZ_ERROR_TRYLATER error as meaning that more data is required before it can continue. No indication is directly given as to exactly how much more data is required, but as the caller will be implementing the progressive fz_stream that it has passed into MuPDF to start with, it can reasonably be expected to figure out an estimate for itself.

Once a page has been loaded, if its contents are to be 'run' as normal (using e.g. fz_run_page) any error (such as failing to read a font, or an image, or even a content stream belonging to the page) will result in a rendering that aborts with an FZ_ERROR_TRYLATER error. The caller can catch this and display a placeholder instead.

If each pages data was entirely self-contained and sent in sequence this would perhaps be acceptable, with each page appearing one after the other. Unfortunately, the linearization procedure as laid down by Adobe does not do this: objects shared between multiple pages (other than the first) are not sent with the pages themselves, but rather after all the pages have been sent.

This means that a document that has a title page, then contents that share a font used on pages 2 onwards, will not be able to correctly display page 2 until after the font has arrived in the file, which will not be until all the page data has been sent.

To mitigate against this, MuPDF provides a way whereby callers can indicate that they are prepared to accept an 'incomplete' rendering of the file (perhaps with missing images, or with substitute fonts).

Callers prepared to tolerate such renderings should set the incomplete_ok flag in the cookie, then call fz_run_page etc. as normal. If a FZ_ERROR_TRYLATER error is thrown at any point during the page rendering, the error will be swallowed, the 'incomplete' field in the cookie will become non-zero and rendering will continue. When control returns to the caller the caller can check the value of the 'incomplete' field and know that the rendering it received is not authoritative.

Progressive loading using byte range requests

If the caller has control over the http fetch, then it is possible to use byte range requests to fetch the document 'out of order'. This enables non-linearized files to be progressively displayed as they download, and fetches complete renderings of pages earlier than would otherwise be the case. This process requires no changes within MuPDF itself, but rather in the way the progressive stream learns from the attempts MuPDF makes to fetch data.

Consider, for example, an attempt to fetch a hypothetical file from a server.

  • The initial http request for the document is sent with a "Range:" header to pull down the first (say) 4k of the file.

  • As soon as we get the header in from this initial request, we can respond to meta stream operations to give the length, and whether byte requests are accepted.

  • If the header indicates that byte ranges are acceptable the stream proceeds to go into a loop fetching chunks of the file at a time (not necessarily in-order). Otherwise the server will ignore the Range: header, and just serve the whole file.

  • If the header indicates a content-length, the stream returns that.

  • MuPDF can then decide how to proceed based upon these flags and whether the file is linearized or not. (If the file contains a linearized object, and the content length matches, then the file is considered to be linear, otherwise it is not).

If the file is linear:

  • We proceed to read objects out of the file as it downloads. This will provide us the first page and all its resources. It will also enable us to read the hint streams (if present).

  • Once we have read the hint streams, we unpack (and sanity check) them to give us a map of where in the file each object is predicted to live, and which objects are required for each page. If any of these values are out of range, we treat the file as if there were no hint streams.

  • If we have hints, any attempt to load a subsequent page will cause MuPDF to attempt to read exactly the objects required. This will cause a sequence of seeks in the fz_stream followed by reads. If the stream does not have the data to satisfy that request yet, the stream code should remember the location that was fetched (and fetch that block in the background so that future retries will succeed) and should raise an FZ_ERROR_TRYLATER error.

Typically therefore when we jump to a page in a linear file on a byte request capable link, we will quickly see a rough rendering, which will improve fairly fast as images and fonts arrive.

  • Regardless of whether we have hints or byte requests, on every fz_load_page call MuPDF will attempt to process more of the stream (that is assumed to be being downloaded in the background). As linearized files are guaranteed to have pages in order, pages will gradually become available. In the absence of byte requests and hints however, we have no way of getting resources early, so the renderings for these pages will remain incomplete until much more of the file has arrived.

Typically therefore when we jump to a page in a linear file on a non byte request capable link, we will see a rough rendering for that page as soon as data arrives for it (which will typically take much longer than would be the case with byte range capable downloads), and that will improve much more slowly as images and fonts may not appear until almost the whole file has arrived.

  • When the whole file has arrived, then we will attempt to read the outlines for the file.

For a non-linearized PDF on a byte request capable stream:

  • MuPDF will immediately seek to the end of the file to attempt to read the trailer. This will fail with a FZ_ERROR_TRYLATER due to the data not being here yet, but the stream code should remember that this data is required and it should be prioritized in the background fetch process.

  • Repeated attempts to open the stream should eventually succeed therefore. As MuPDF jumps through the file trying to read first the xrefs, then the page tree objects, then the page contents themselves etc., the background fetching process will be driven by the attempts to read the file in the foreground.

Typically therefore the opening of a non-linearized file will be slower than a linearized one, as the xrefs/page trees for a non-linear file can be 20%+ of the file data. Once past this initial point however, pages and data can be pulled from the file almost as fast as with a linearized file.

For a non-linearized PDF on a non-byte request capable stream:

  • MuPDF will immediately seek to the end of the file to attempt to read the trailer. This will fail with a FZ_ERROR_TRYLATER due to the data not being here yet. Subsequent retries will continue to fail until the whole file has arrived, whereupon the whole file will be instantly available.

This is the worst case situation - nothing at all can be displayed until the entire file has downloaded.

A typical structure for a fetcher process (see curl-stream.c, mupdf-curl in platform/win32/mupdf-curl.vcxproj) as an example) might therefore look like this:

  • We consider the file as an (initially empty) buffer which we are filling by making requests. In order to ensure that we make maximum use of our download link, we ensure that whenever one request finishes, we immediately launch another. Further, to avoid the overheads for the request/response headers being too large, we may want to divide the file into 'chunks', perhaps 4 or 32k in size.

  • We can then have a receiver process that sits there in a loop requesting chunks to fill this buffer. In the absence of any other impetus the receiver should request the next 'chunk' of data from the file that it does not yet have, following the last fill point. Initially we start the fill point at the beginning of the file, but this will move around based on the requests made of the progressive stream.

  • Whenever MuPDF attempts to read from the stream, we check to see if we have data for this area of the file already. If we do, we can return it. If not, we remember this as the next "fill point" for our receiver process and throw a FZ_ERROR_TRYLATER error.


Ext Mupdf Docs Guide Install (`ext/mupdf/docs/guide/install.md`)

Installing

Download

Either download a release or clone the latest
source from the Git repository:

text
git clone git://git.ghostscript.com/mupdf.git

You will also need to initialize the submodules if you cloned from git:

text
git submodule update --init --depth 1

Build on Windows

For Windows use the Visual Studio solution file in platform/win32/mupdf.sln.

Build on MacOS

MacOS should be buildable in the same way as for Linux, but the exact set of
required libraries may vary. No viewer option is available as standard.

Build on Linux (and BSD and macOS)

Use the GNU makefile to build on Linux or the BSDs.

text
make

The viewers require the X11 (and OpenGL for mupdf-gl) headers and libraries to build.
Install these packages (the exact package names may vary depending on your distro of choice):

text
sudo apt install xorg-dev libxcursor-dev libxrandr-dev libxinerama-dev
sudo apt install mesa-common-dev libgl1-mesa-dev libglu1-mesa-dev

Alternatively, if you only want to build the MuPDF library command line tools:

text
make tools

Testing the build

The steps above will put the libraries and binaries in the build/release/ directory.

The tools and viewers are portable executables that don't need any auxiliary
files, so can be run from anywhere. To make sure that everything works, you can
run them directly from the build directory:

text
./build/release/mutool -v

Install on Linux

You can safely copy the binaries to any directory in your path.

text
cp ./build/release/mutool ~/.local/bin/mutool

Or, if you prefer you can install the binaries, libraries, and headers system wide:

text
make prefix=/usr/local install

Good luck!


Ext Mupdf Docs Guide Using With Android (`ext/mupdf/docs/guide/using-with-android.md`)

Using with Android

Introduction

This document outlines the steps necessary to use the MuPDF Android Library in various ways.

First, we show you how to embed MuPDF in your app. Then, we explain how to
customize the viewer if you need to change how it looks or behaves. Finally, we
tell you where to go if you need to do work on the library itself.

Embedding the viewer in your app provides an activity that you can start to
view PDF documents from within your app. This should be enough for most use
cases.

Acquire a valid license

Open Source license

If your software is open source, you may use MuPDF under the terms of the
GNU Affero General Public License.

This means that all of the source code for your complete app must be
released under a compatible open source license!

It also means that you may not use any proprietary closed source libraries or
components in your app. This includes (but is not limited to):

  • Google Play Services
  • Google Mobile Services
  • AdMob by Google
  • Crashlytics
  • Answers
  • etc.

Just because a library ships with Android or is made by Google does not make it AGPL compatible!

If you cannot or do not want to comply with these restrictions, you must acquire a commercial license instead.

Commercial license

If your software is not open source, Artifex Software can sell you a license to use MuPDF in closed source software.

Fill out the
MuPDF product inquiry form
for commercial licensing terms and pricing.

Add the MuPDF Library to your project

The MuPDF library uses the Gradle build system.
In order to include MuPDF in your app, you also need to use Gradle.
The Eclipse and Ant build systems are not supported.

The MuPDF library needs Android version 4.1 or newer.
Make sure that the minSdkVersion in your app's build.gradle is at least 16.

text
android {
	defaultConfig {
		minSdkVersion 16
		...
	}
	...
}

The MuPDF library can be retrieved as a pre-built artifact from our Maven repository.
In your project's top build.gradle, add the line to the repositories section:

text
allprojects {
	repositories {
		jcenter()
			maven { url 'http://maven.ghostscript.com' }
		...
	}
}

Then add the MuPDF viewer library to your app's dependencies.
In your app's build.gradle, add the line to the dependencies section:

text
dependencies {
	api 'com.artifex.mupdf:viewer:1.15.+'
	...
}

Invoke the document viewer activity

Once this has been done, you have access to the MuPDF viewer activity.
You can now open a document viewing activity by launching an Intent,
passing the URI of the document you wish to view.

text
import com.artifex.mupdf.viewer.DocumentActivity;

public void startMuPDFActivity(Uri documentUri) {
	Intent intent = new Intent(this, DocumentActivity.class);
	intent.setAction(Intent.ACTION_VIEW);
	intent.setData(documentUri);
	startActivity(intent);
}

How to customize the viewer

If you've already tried embedding the viewer in your app, but are unhappy with some
aspect of the look or behavior and want to modify it in some way, this section should
point you in the right direction.

Decide which viewer to base your customizations on

In order to customize the viewer UI, you will need to modify the existing Android viewer activity.
There are two separate code bases you can start with:

  • mupdf-android-viewer
    The main viewer app. This code is difficult to work with, but has the most
    features and pre-renders neighboring pages into a page cache for faster page
    turning performance.

  • mupdf-android-viewer-mini
    This is a minimalist viewer which has fewer features but is designed to be
    easy to understand and modify. It does not (currently) have high-resolution
    zooming, and it does not use the swipe gesture to flip pages (it requires the
    user to tap on the side of the screen to flip pages).

If all you want to do is brand the UI with your own colors and icons, you are
welcome to use whichever code base you prefer. However, if you want to do
extensive modifications, we suggest you base your code on the mini viewer.

Check out the chosen project

When you have decided which project to base your modifications on, you should check out
the corresponding git repository:

text
$ git clone git://git.ghostscript.com/mupdf-android-viewer.git
$ git clone git://git.ghostscript.com/mupdf-android-viewer-mini.git

Inside the checked out project you will find two modules: app and lib.
The app module is a file chooser activity that lets the user open files from the external storage.
The lib module is the viewer activity, which provides the com.artifex.mupdf:viewer
package that you're already using.

The lib module is the one you want; ignore everything else in this project.

Copy the viewer library module into your project

Copy the 'lib' directory to your project, renaming it to something appropriate.
The following instructions assume you called the directory 'mupdf-lib'.

Don't forget to include the module in the settings.gradle file:

text
include ':app'
include ':mupdf-lib'
...

You'll also want to change your app's dependencies to now depend on your local
copy rather than the official MuPDF viewer package. In your app build.gradle:

text
dependencies {
	api project(':mupdf-lib')
	...
}

The lib module depends on the JNI library com.artifex.mupdf:fitz, so do
not remove the Maven repository from your top build.gradle.

Edit the viewer activity

If all has gone well, you can now build your project with the local viewer library,
and access the MuPDF viewer activity just as you used to.

You're now free to customize the resources in mupdf-lib/src/main/res and behavior in
mupdf-lib/src/main/java as you desire.

Working on the MuPDF Library

If you want to work on the library itself, rather than just use it, you will need
to check out the following git repositories.

  • mupdf.git
    This repository contains the low-level "fitz" C library and the JNI bindings.

  • mupdf-android-fitz.git
    This repository contains an Android project to build the C library and JNI bindings.
    It uses mupdf.git as a Git submodule.

  • mupdf-android-viewer.git
    This repository contains the Android viewer library and app.
    It uses mupdf-android-fitz.git as either a Maven artifact or Git submodule.

  • mupdf-android-viewer-mini.git
    This repository contains the minimalist Android viewer library and app.
    It uses mupdf-android-fitz.git as either a Maven artifact or Git submodule.

These repositories are set up with Git submodules. If you're a Git expert,
you can clone one of the viewer repositories recursively and get the fitz
and mupdf git repositories all at once.

If you only want to work with one of the viewer repositories, you can clone it
non-recursively and use the Maven artifact for the JNI bindings library and not
worry about the mupdf.git and mupdf-android-fitz.git repositories.


Ext Mupdf Docs Guide Using With Java (`ext/mupdf/docs/guide/using-with-java.md`)

Using with Java

There is also a Java library, which uses JNI to provide access to the C
library. The Java classes provide an interface very similar to that available
to Javascript. This Java library also powers the Android versions of MuPDF.

Android

If you want to build an application for Android, you have several options. You
can base it off one of the existing viewers, or build a new app using the Java
library directly.

See the "Using with Android" section to get started using the MuPDF library for
Android.

Building

Check out (or download) the MuPDF repository.

The Java bindings are in the platform/java directory.

You can build them using make:

text
make java

The resulting shared library are in build/java/release. You need to make sure
the Java runtime can find the JAR archive (with class-path) and the native
library (with java.library.path).

To test the bindings you can use the Java shell:

text
$ jshell --class-path=build/java/release -R-Djava.library.path=build/java/release
jshell> import com.artifex.mupdf.fitz.*
jshell> var doc = Document.openDocument("pdfref17.pdf")
jshell> System.out.println(doc.countPages())

Examples

There are several more examples in the Java directory.

To build and run the example Swing viewer:

text
make -C platform/java run

Ext Mupdf Docs Guide Using With Javascript (`ext/mupdf/docs/guide/using-with-javascript.md`)

Using with Javascript

MuPDF can be used with Javascript in two ways:

  • With the MuPDF.js module for Javascript.
  • With the mutool run command line interpreter.

MuPDF.js

We've created a portable WebAssembly build of MuPDF that works with all modern
Javascript environments such as Node, Bun, Firefox, Chrome, etc. This module
provides an object oriented interface to the core library, similar to the Java
library. There are also TypeScript definition files.

The easy way to get started using this is by using Node.js and install
the latest release from NPM:

text
npm install mupdf

Note that the mupdf module is only usable with ESM imports!

Then run the following script to verify that everything works by listing
all the public classes and functions in the mupdf module:

text
import * as mupdf from mupdf
console.log(Object.keys(mupdf))

Once that is working, try out some of the
Javascript examples!

See the mupdfjs.readthedocs.io site for more extensive
examples of using MuPDF.js in modern Javascript frameworks.

mutool run

The mutool command line has a built-in ES5 interpreter that can run scripts
with the same high level API as MuPDF.js (with some minor differences) without
needing to install Node or Bun.

The main limitation is that you can only use ES5 language features (so no
"let", arrow functions, or "class" syntactic sugar).

How to build MuPDF.js

See the README and BUILDING files in the platform/wasm directory of the MuPDF source.


Ext Mupdf Docs Guide What Is Mupdf (`ext/mupdf/docs/guide/what-is-mupdf.md`)

What is MuPDF?

MuPDF is an open source software framework for viewing, converting, and
manipulating PDF, XPS, and E-book documents. There are viewers for various
platforms, several command line tools, and a software library for building
tools and applications.

Formats

As you can tell by the name, we support reading PDF files. But that's not all!

  • PDF
  • XPS and OpenXPS
  • EPUB (DRM-free 2.0, limited support for 3.0)
  • Mobipocket (MOBI)
  • FictionBook 2 (FB2)
  • ComicBook (CBZ and CBT)
  • Images (TIFF, JPEG, PNG, etc)
  • SVG (a limited subset only)
  • Markdown (MD)

Viewers

There are many different viewer applications that build on the MuPDF library
for a large variety of platforms.

Linux, Windows, and BSD

For Linux and Windows there are two viewer applications.
The main viewer (mupdf-gl) has many features such as a table of contents
sidebar, full unicode search, annotation editing, redaction, etc.
On systems where this viewer cannot be built, we still support the older
legacy viewer which does not have as many features.

[mupdf-gl](../tools/mupdf-gl.html)
The main viewer program that sports the most features.
mupdf-x11
The legacy X11 viewer that works everywhere.
mupdf-win32
The legacy win32 viewer for Windows.

Android

Android currently has two different viewers with varying degrees of complexity:

[MuPDF viewer](https://play.google.com/store/apps/details?id=com.artifex.mupdf.viewer.app)
A high performance PDF viewer with a smooth and polished interface.
[MuPDF mini](https://play.google.com/store/apps/details?id=com.artifex.mupdf.mini.app)
An example of how to create a PDF viewer with the least amount of code.

Web Browser

There's also a commercial license only MuPDF WebViewer product.

Here is a demo of it.

Third party viewers

Here's a non-exhaustive list of other non-affiliated open source projects that use MuPDF:

Command Line

The command line tools are all gathered into one umbrella command: mutool.
This swiss army knife has a lot of sub-commands for performing different
tasks on PDF documents.

For rendering and converting documents use these two commands:

[mutool draw](../tools/mutool-draw.html)
This is the more customizable tool, but also has a more difficult set of command line options. It is primarily used for rendering a document to image files.
[mutool convert](../tools/mutool-convert.html)
This tool is used for converting documents into other formats, and is easier to use.

A highlight of some other tools useful for working with PDF documents:

[mutool show](../tools/mutool-show.html)
A tool for displaying the internal objects in a PDF file.
[mutool extract](../tools/mutool-extract.html)
Extract images and embedded font resources.
[mutool clean](../tools/mutool-clean.html)
Rewrite PDF file. Used to fix broken files, or to make a PDF file human editable.
[mutool create](../tools/mutool-create.html)
Create a new PDF file from a text file with graphics commands.
[mutool merge](../tools/mutool-merge.html)
Merge pages from multiple input files into a new PDF.
[mutool poster](../tools/mutool-poster.html)
Divide pages of a PDF into pieces that can be printed and merged into a large poster.

And finally, there is a tool for doing anything you can imagine:

[mutool run](../tools/mutool-run.html)
A tool for running Javascript programs with access to the MuPDF library functions.

Library

The library is written in portable C.

To learn more about the C interface, read the MuPDF Explored book.

Javascript

There is a library available to use MuPDF from Javascript and Typescript,
powered by WebAssembly. You can use this library to build applications that run
in a web browser, or that run server side using Node or Bun.

The Javascript library is available as a module on NPM.

Java

There is also a Java library, which uses JNI to provide access to the C library.

The Java classes provide an interface very similar to that available in the
mutool run command line tool.
This Java library also powers the Android viewers.

If you want to build an application for Android, you have several options. You
can base it off one of the existing viewers, or build a new app using the Java
library directly.

Python

The popular PyMuPDF library makes it trivial to use MuPDF from Python!


Ext Mupdf Docs Other Changes (`ext/mupdf/docs/other/changes.md`)

Change Log

For the full list of changes between versions of MuPDF see
CHANGES.

Major changes to existing interfaces are listed here.

Why does the API change?

From time to time, during development, it becomes clear that the public API
needs to change; either because the existing API has flaws in it, or to
accommodate new functionality. Such changes are not undertaken lightly, and
they are kept as minimal as possible.

The alternative would be to freeze the API and to introduce more and more
compatibility veneers, ultimately leading to a bloated API and additional
complexity. We have done this in the past, and will do it again in future if
circumstances demand it, but we consider small changes in the API to be a price
worth paying for clarity and simplicity.

To minimise the impact of such changes, we undertake to list the API changes
between different versions of the library here, to make it as simple as
possible for integrators to make the small changes that may be require to
update between versions.

The changes listed below only affect existing APIs.

Changes from 1.27 to 1.28

  • Setting overprint simulation in mutool draw moved from -O to -M.
  • The mutool draw command now accepts device specific options with -O.
  • PDF write option "decrypt" is deprecated; use "encrypt=none" instead.
  • pdf_validate_signature renamed pdf_validate_signature_widget. The
    new pdf_validate_signature operates directly on a signature field.

Changes from 1.26 to 1.27

  • Use fz_quad in fz_warp_pixmap, fz_autowarp_pixmap, and fz_detect_document.

Changes from 1.25 to 1.26

  • fz_skew_detect renamed fz_detect_skew.
  • Filespec renamed FileSpec to be consistent across bindings and documentation:
    • PDFAnnotation.hasFilespec renamed PDFAnnotation.hasFileSpec
    • PDFAnnotation.getFilespec renamed PDFAnnotation.getFileSpec
    • PDFAnnotation.setFilespec renamed PDFAnnotation.setFileSpec
    • PDFDocument.getFilespecParams renamed PDFDocument.getFileSpecParams.
    • PDFObject.isFilespec renamed PDFObject.isFileSpec.
  • StrokeState.getDashes renamed StrokeState.getDashPattern.

Changes from 1.24 to 1.25

  • New wants_file member for fz_document_handler.
  • fz_text_item now has an explicit pen advance member.
  • pdf_annot_rect works with the "design" rectangle.
  • mutool recolor can now be used as a function.
  • structured text "color" field renamed to "argb" and now includes alpha channel

Changes from 1.23 to 1.24

  • You must call pdf_report_error in the final fz_catch. Any unreported errors will be automatically reported when a new error is raised, or when closing the fitz context.
  • The deprecated pdf_check_signature was removed.

Changes from 1.22 to 1.23

  • pdf_field_name renamed to pdf_load_field_name.
  • mutool run changed many methods to match Java and new WASM library.

Changes from 1.21 to 1.22

None.

Changes from 1.20 to 1.21

None.

Changes from 1.19 to 1.20

Text search API extended to be able to distinguish between separate search hits.

Changes from 1.18 to 1.19

We were inconsistent with the behaviour of fz_create_ and pdf_create_ functions, in that sometimes they returned a borrowed reference, rather than a reference that the caller had to drop. We now always return a reference that the caller owns and must drop.

Changes from 1.17 to 1.18

None.

Changes from 1.16 to 1.17

The accessors for reading and creating QuadPoints, InkList and Vertices data for Highlight, Underline, StrikeOut, Squiggle, Ink, Polygon, and PolyLine annotation types have been simplified. They now take and return fz_quad and fz_point structs instead of float arrays. We have also added functions to construct the datastructures one piece at a time, removing the need to allocate a temporary array to pass.

  • typedef struct fz_quad { fz_point ul, ur, ll, lr; };

To facilitate loading EPUB documents without laying out the entire document at once, we have introduced chapters into the document structure. Each document instead of having just a plain list of pages, now has a list of chapters, and each chapter has a list of pages. Most of the old functions have the same functionality, but we have added several new functions, which if used, will provide significant speedups when jumping to a random page in large EPUB documents.

  • int fz_count_chapters(ctx, doc)
  • int fz_count_chapter_pages(ctx, doc, chapter)
  • fz_page *fz_load_chapter_page(ctx, doc, chapter, page)
  • int fz_search_chapter_page_number(ctx, doc, chapter, page, needle, hit_quads, hit_size)

However, in order to support bookmarks and links using chapters we have had to introduce a new struct fz_location, and the functions to resolve links have changed to use this new struct.

  • typedef struct { int chapter, page; } fz_location;
  • fz_location fz_resolve_link(ctx, doc, uri, x, y);

Functions to map between a fixed page number and a fz_location have been added to help facilitate migration to the new API.

  • int fz_page_number_from_location(ctx, doc, location);
  • fz_location fz_location_from_page_number(ctx, doc, number);
  • fz_location fz_next_page(ctx, doc, location);
  • fz_location fz_previous_page(ctx, doc, location);
  • fz_location fz_last_page(ctx, doc);

The layout information for each chapter can at your option be cached in an "accelerator" file for even faster loading. This will help performance when using the old page number based rather than location based functions.

  • fz_document fz_open_accelerated_document(ctx, filename, accelerator_filename);
  • void fz_save_accelerator(ctx, document, accelerator_filename);

Changes from 1.15 to 1.16

There has been a major overhaul of the color management architecture. Unless you're implementing custom devices or interpreters, this should only have a minor impact.

  • Argument order when passing color and colorspace to functions regularized: sourceColorspace, sourceColorArray, destinationColorspace, destinationColorArray, proofColorspace, colorParams.
  • Pass fz_color_params argument by value.
  • Changed fz_default_parameters from a function to a global constant.
  • Replaced fz_set_icc_engine with fz_enable_icc and fz_disable_icc to toggle color management at runtime.
  • Replaced pixmap color converter struct with a single fz_convert_pixmap function call.
  • Replaced fz_cal_colorspace struct with constructor to create an ICC-backed calibrated colorspace directly.
  • Passing NULL is not a shortcut for DeviceGray any more!
  • Added public definitions for Indexed and Separation colorspaces.
  • Changed colorspace constructors.

The fz_set_stdout and fz_set_stderr functions have been removed. If you were using these to capture warning and error messages, use the new user callbacks for warning and error logging instead: fz_set_warning_callback and fz_set_error_callback.

The structured text html and xhtml output formats take an additional argument: the page number. This number is used to create an id attribute for each page to use as a hyperlink target.

Changes from 1.14 to 1.15

  • PDF Portfolios
    This functionality has been removed. We do not believe anyone was using this. If you were, please contact us for assistance. This functionality can be achieved using "mutool run" and docs/examples/pdf-portfolio.js.
  • FZ_ERROR_TRYLATER
    This functionality has been removed. We do not believe anyone was using this. If you were, please contact us for assistance.
  • Annotations/Forms
    We are undertaking a significant rework of this functionality at the moment. We do not believe anyone is using this at the moment, and would therefore encourage anyone who is to contact us for help in upgrading.
  • Various functions involving fz_colorspace have lost consts.
    fz_colorspaces are immutable once created, other than changes due to reference counting. Passing a const fz_colorspace to a function that might keep a reference to it either has to take a non const fz_colorspace pointer, or take a const one, and 'break' the const. Having some functions take const fz_colorspace and some not is confusing, so therefore, for simplicity, all fz_colorspaces are now passed as non const. This should not affect any user code.
  • fz_process_shade()
    This now takes an extra 'scissor' argument. To upgrade old code, if you don't have an appropriate scissor rect available, it is safe (but unwise) to pass fz_infinite_rect.
  • fz_tint_pixmap()
    Rather than taking r, g and b, values to tint with, the function now takes 8 bit hex rgb values for black and white, enabling greater control, allowing "low contrast" and "night view" effects.
  • pdf_add_image()
    This no longer requires a mask flag. The image already knows if it is a mask.
  • pdf_processor.op_BI()
    The op_BI callback is now passed an additional colorspace resource name.

Ext Mupdf Docs Other Third Party (`ext/mupdf/docs/other/third-party.rst`)

.. Copyright (C) 2001-2026 Artifex Software, Inc.
.. All Rights Reserved.

.. meta::
:description: MuPDF documentation
:keywords: MuPDF, pdf, epub

Third Party Libraries

These are the third party libraries used by :title:MuPDF.

.. list-table::
:header-rows: 1

    • Library
    • Version
    • Function
    • License
    • Required
    • freetype_
    • 2.14.3
    • Font scaling and rendering
    • BSD-style
    • harfbuzz_
    • 6.0.0 with patches
    • Text shaping
    • MIT-style
    • libjpeg_
    • 10.0 with patches
    • JPEG decoding
    • BSD-style
    • Incompatible fork of lcms2_
    • 2.16 with patches
    • Color management
    • MIT-style
    • openjpeg_
    • 2.5.4 with upstream and local patches
    • JPEG 2000 decoding
    • BSD-style
    • zlib_
    • 1.3.2
    • Deflate compression
    • zlib License
    • gumbo-parser_
    • 0.10.1 with patches
    • HTML5 parser
    • Apache 2.0
    • brotli_
    • 1.1.0 with upstream and local patches
    • Brotli compression
    • MIT-style
    • cmark-gfm_
    • 0.29.0.gfm.13 with patches
    • Markdown conversion to HTML
    • MIT-style
    • Optional
    • FreeGLUT_
    • 3.0.0 with patches
    • OpenGL API for UI
    • MIT-style
    • curl_
    • 7.66.0 with patches
    • HTTP data transfer
    • MIT-style
    • JPEG-XR reference_
    • 1.32 with patches
    • JPEG-XR decoding
    • special
    • Tesseract_
    • 5.5.2 with patches
    • OCR
    • Apache 2.0
    • Leptonica_
    • 1.87.0 with patches
    • Tesseract dependency
    • BSD-style
    • Zint_
    • 2.13.0.9
    • Zxing-cpp dependency
    • BSD-style
    • Zxing-cpp_
    • 2.3.0 with patches
    • Barcode decoding/encoding
    • Apache 2.0

.. note::

jbig2dec_ and MuJS_ are included in "thirdparty" but are copyright :title:Artifex Software Inc.

.. External links

.. _freetype: http://www.freetype.org/
.. _harfbuzz: http://www.harfbuzz.org/
.. _libjpeg: http://www.ijg.org/
.. _Incompatible fork of lcms2: http://cgit.ghostscript.com/cgi-bin/cgit.cgi/thirdparty-lcms2.git/
.. _openjpeg: http://www.openjpeg.org/
.. _zlib: http://www.zlib.net/
.. _gumbo-parser: https://github.com/google/gumbo-parser
.. _brotli: https://brotli.org/
.. _FreeGLUT: http://freeglut.sourceforge.net/
.. _curl: http://curl.haxx.se/
.. _JPEG-XR reference: https://www.itu.int/rec/T-REC-T.835/
.. _Tesseract: https://tesseract-ocr.github.io/
.. _Leptonica: https://github.com/DanBloomberg/leptonica
.. _jbig2dec: https://jbig2dec.com/?utm_source=rtd-mupdf&utm_medium=rtd&utm_content=inline-link
.. _MuJS: https://mujs.com/?utm_source=rtd-mupdf&utm_medium=rtd&utm_content=inline-link
.. _Zint: https://www.zint.org.uk/
.. _Zxing-cpp: https://github.com/zxing-cpp/zxing-cpp
.. _cmark-gfm: https://github.com/github/cmark-gfm


Ext Mupdf Docs Reference Cxx And Derived Bindings (`ext/mupdf/docs/reference/cxx-and-derived-bindings.rst`)

.. Copyright (C) 2001-2026 Artifex Software, Inc.
.. All Rights Reserved.

.. meta::
:description: MuPDF documentation
:keywords: MuPDF, pdf, epub

C++, Python, and C#

..
We define crude substitutions that implement simple expand/contract blocks
in html. Unfortunately it doesn't seem possible to pass parameters to
substitutions so we can't specify text to be shown next to html's details
triangle.

.. |expand_begin| raw:: html

text
**Show/hide**

.. |expand_end| raw:: html

Overview

Auto-generated abstracted :title:C++, :title:Python and :title:C#
versions of the :title:MuPDF C API are available.

  • The C++ API is machine-generated from the C API header files and adds various
    abstractions such as automatic contexts and automatic reference counting.

  • The Python and C# APIs are generated from the C++ API using SWIG, so
    automatically include the C++ API's abstractions.

.. graphviz::

text
digraph
{
  size="4,4";
  labeljust=l;

  "MuPDF C API" [shape="rectangle"]
  "MuPDF C++ API" [shape="rectangle"]
  "SWIG" [shape="oval"]
  "MuPDF Python API" [shape="rectangle"]
  "MuPDF C# API" [shape="rectangle"]

  "MuPDF C API" -> "MuPDF C++ API" [label=" Parse C headers with libclang,\l generate abstractions.\l"]

  "MuPDF C++ API" -> "SWIG" [label=" Parse C++ headers with SWIG."]
  "SWIG" -> "MuPDF Python API"
  "SWIG" -> "MuPDF C# API"
}

The C++ MuPDF API

Basics

text
* Auto-generated from the MuPDF C API's header files.

* Everything is in C++ namespace ``mupdf``.

* All functions and methods do not take ``fz_context*`` arguments.
  (Automatically-generated per-thread contexts are used internally.)

* All MuPDF ``setjmp()``/``longjmp()``-based exceptions are converted into C++ exceptions.

Low-level C++ API

The MuPDF C API is provided as low-level C++ functions with ll_ prefixes.

  • No fz_context* arguments.

  • MuPDF exceptions are converted into C++ exceptions.

Class-aware C++ API

text
C++ wrapper classes wrap most ``fz_*`` and ``pdf_*`` C structs:

* Class names are camel-case versions of the wrapped struct's
  name, for example ``fz_document``'s wrapper class is ``mupdf::FzDocument``.

* Classes automatically handle reference counting of the underlying C structs,
  so there is no need for manual calls to ``fz_keep_*()`` and ``fz_drop_*()``, and
  class instances can be treated as values and copied arbitrarily.

Class-aware functions and methods take and return wrapper class instances
instead of MuPDF C structs:

* No ``fz_context*`` arguments.

* MuPDF exceptions are converted into C++ exceptions.

* Class-aware functions have the same names as the underlying C API function.

* Args that are pointers to a MuPDF struct will be changed to take a reference to
  the corresponding wrapper class.

* Where a MuPDF function returns a pointer to a struct, the class-aware C++
  wrapper will return a wrapper class instance by value.

* Class-aware functions that have a C++ wrapper class as their first parameter
  are also provided as a member function of the wrapper class, with the same
  name as the class-aware function.

* Wrapper classes are defined in ``mupdf/platform/c++/include/mupdf/classes.h``.

* Class-aware functions are declared in ``mupdf/platform/c++/include/mupdf/classes2.h``.

*
  Wrapper classes for reference-counted MuPDF structs:

  *
    The C++ wrapper classes will have a public ``m_internal`` member that is a
    pointer to the underlying MuPDF struct.

  *
    If a MuPDF C function returns a null pointer to a MuPDF struct, the
    class-aware C++ wrapper will return an instance of the wrapper class with a
    null ``m_internal`` member.

  *
    The C++ wrapper class will have an ``operator bool()`` that returns true if
    the ``m_internal`` member is non-null.

    [Introduced 2024-07-08.]

Usually it is more convenient to use the class-aware C++ API rather than the
low-level C++ API.

C++ Exceptions

C++ exceptions use classes for each FZ_ERROR_* enum, all derived from a class
mupdf::FzErrorBase which in turn derives from std::exception.

For example if MuPDF C code does fz_throw(ctx, FZ_ERROR_GENERIC, "something failed"), this will appear as a C++ exception with type
mupdf::FzErrorGeneric. Its what() method will return code=2: something failed, and it will have a public member m_code set to FZ_ERROR_GENERIC.

Example wrappers

text
The MuPDF C API function ``fz_new_buffer_from_page()`` is available as these
C++ functions/methods:

.. code-block:: c++

    // MuPDF C function.
    fz_buffer *fz_new_buffer_from_page(fz_context *ctx, fz_page *page, const fz_stext_options *options);

    // MuPDF C++ wrappers.
    namespace mupdf
    {
        // Low-level wrapper:
        ::fz_buffer *ll_fz_new_buffer_from_page(::fz_page *page, const ::fz_stext_options *options);

        // Class-aware wrapper:
        FzBuffer fz_new_buffer_from_page(const FzPage& page, FzStextOptions& options);

        // Method in wrapper class FzPage:
        struct FzPage
        {
            ...
            FzBuffer fz_new_buffer_from_page(FzStextOptions& options);
            ...
        };
    }


Extensions beyond the basic C API
  • Some generated classes have extra begin() and end() methods to allow
    standard C++ iteration:

    |expand_begin|

    .. code-block:: c++

    text
    #include "mupdf/classes.h"
    #include "mupdf/functions.h"
    
    #include 
    
    void show_stext(mupdf::FzStextPage& page)
    {
        for (mupdf::FzStextPage::iterator it_page: page)
        {
            mupdf::FzStextBlock block = *it_page;
            for (mupdf::FzStextBlock::iterator it_block: block)
            {
                mupdf::FzStextLine line = *it_block;
                for (mupdf::FzStextLine::iterator it_line: line)
                {
                    mupdf::FzStextChar stextchar = *it_line;
                    fz_stext_char* c = stextchar.m_internal;
                    using namespace mupdf;
                    std::cout << "FzStextChar("
                            << "c=" << c->c
                            << " color=" << c->color
                            << " origin=" << c->origin
                            << " quad=" << c->quad
                            << " size=" << c->size
                            << " font_name=" << c->font->name
                            << "\n";
                }
            }
        }
    }

    |expand_end|

  • There are various custom class methods and constructors.

  • There are extra functions for generating a text representation of 'POD'
    (plain old data) structs and their C++ wrapper classes.

    For example for fz_rect we provide these functions:

    .. code-block:: c++

    text
    std::ostream& operator<< (std::ostream& out, const fz_rect& rhs);
    std::ostream& operator<< (std::ostream& out, const FzRect& rhs);
    std::string to_string_fz_rect(const fz_rect& s);
    std::string to_string(const fz_rect& s);
    std::string Rect::to_string() const;

    These each generate text such as: (x0=90.51 y0=160.65 x1=501.39 y1=1215.6)

Runtime environmental variables

text
All builds
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

* **MUPDF_mt_ctx**

  Controls support for multi-threading on startup.

  * If set with value ``0``, a single ``fz_context*`` is used for all threads; this
    might give a small performance increase in single-threaded programmes, but
    will be unsafe in multi-threaded programmes.

  * Otherwise each thread has its own ``fz_context*``.

  One can instead call ``mupdf::reinit_singlethreaded()`` on startup to force
  single-threaded mode. This should be done before any other use of MuPDF.

Debug builds only
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

Debug builds contain diagnostics/checking code that is activated via these
environmental variables:

* **MUPDF_check_refs**

  If ``1``, generated code checks MuPDF struct reference counts at
  runtime.

* **MUPDF_check_error_stack**

  If ``1``, generated code outputs a diagnostic if a MuPDF function changes the
  current ``fz_context``'s error stack depth.

* **MUPDF_trace**

  If ``1`` or ``2``, class-aware code outputs a diagnostic each time it calls a
  MuPDF function (apart from keep/drop functions).

  If ``2``, low-level wrappers output a diagnostic each time they are
  called. We also show arg POD and pointer values.

* **MUPDF_trace_director**

  If ``1``, generated code outputs a diagnostic when doing special
  handling of MuPDF structs containing function pointers.

* **MUPDF_trace_exceptions**

  If ``1``, generated code outputs diagnostics when it converts MuPDF
  ``setjmp()``/``longjmp()`` exceptions into C++ exceptions.

* **MUPDF_trace_keepdrop**

  If ``1``, generated code outputs diagnostics for calls to ``*_keep_*()`` and
  ``*_drop_*()``.

Limitations
  • We do not wrap variadic functions such as fz_write_printf().

  • Global instances of C++ wrapper classes are not supported.

    This is because:

    • C++ wrapper class destructors generally call MuPDF functions (for example
      fz_drop_*()).

    • The C++ bindings use internal thread-local objects to allow per-thread
      fz_context's to be efficiently obtained for use with underlying MuPDF
      functions.

    • C++ globals are destructed after thread-local objects are destructed.

    So if a global instance of a C++ wrapper class is created, its destructor
    will attempt to get a fz_context* using internal thread-local objects
    which will have already been destroyed.

    We attempt to display a diagnostic when this happens, but this cannot be
    relied on as behaviour is formally undefined.

The Python and C# MuPDF APIs

  • A Python module called mupdf.

  • A C# namespace called mupdf.

  • Auto-generated from the C++ MuPDF API using SWIG, so inherits the abstractions of the C++ API:

    • No fz_context* arguments.
    • Automatic reference counting, so no need to call fz_keep_*() or fz_drop_*(), and we have value-semantics for class instances.
    • Native Python and C# exceptions.
  • Output parameters are returned as tuples.

    For example MuPDF C function fz_read_best() has prototype::

    text
    fz_buffer *fz_read_best(fz_context *ctx, fz_stream *stm, size_t initial, int *truncated);

    The class-aware Python wrapper is::

    text
    mupdf.fz_read_best(stm, initial)

    and returns (buffer, truncated), where buffer is a SWIG proxy for a
    mupdf::FzBuffer instance and truncated is an integer.

  • Allows implementation of mutool in Python - see
    mupdf:scripts/mutool.py <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/scripts/mutool.py>_
    and
    mupdf:scripts/mutool_draw.py <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/scripts/mutool_draw.py>_.

  • Provides text representation of simple 'POD' structs:

    .. code-block:: python

    text
    rect = mupdf.FzRect(...)
    print(rect) # Will output text such as: (x0=90.51 y0=160.65 x1=501.39 y1=215.6)
    • This works for classes where the C++ API defines a to_string() method as described above.

      • Python classes will have a __str__()` method, and an identical `__repr__() method.
      • C# classes will have a ToString() method.
  • Uses SWIG Director classes to allow C function pointers in MuPDF structs to call Python code.

Installing the Python mupdf module using ``pip``

The Python mupdf module is available on the Python Package Index (PyPI) website <https://pypi.org/>_.

  • Install with pip install mupdf.
  • Pre-built Wheels (binary Python packages) are provided for Windows and Linux.
  • For more information on the latest release, see changelog below and: https://pypi.org/project/mupdf/

Doxygen/Pydoc API documentation

Auto-generated documentation for the C, C++ and Python APIs is available at:
https://ghostscript.com/~julian/mupdf-bindings/

  • All content is generated from the comments in MuPDF header files.

  • This documentation is generated from an internal development tree, so may
    contain features that are not yet publicly available.

  • It is updated only intermittently.

Example client code

Using the Python API

text
Minimal Python code that uses the ``mupdf`` module::

    import mupdf
    document = mupdf.FzDocument('foo.pdf')

A simple example Python test script (run by ``scripts/mupdfwrap.py -t``) is:

* `scripts/mupdfwrap_test.py <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/scripts/mupdfwrap_test.py>`_

More detailed usage of the Python API can be found in:

* `scripts/mutool.py <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/scripts/mutool.py>`_
* `scripts/mutool_draw.py <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/scripts/mutool_draw.py>`_


**Example Python code that shows all available information about a document's Stext blocks, lines and characters**:

|expand_begin|
::

    #!/usr/bin/env python3

    import mupdf

    def show_stext(document):
        '''
        Shows all available information about Stext blocks, lines and characters.
        '''
        for p in range(document.fz_count_pages()):
            page = document.fz_load_page(p)
            stextpage = mupdf.FzStextPage(page, mupdf.FzStextOptions())
            for block in stextpage:
                block_ = block.m_internal
                log(f'block: type={block_.type} bbox={block_.bbox}')
                for line in block:
                    line_ = line.m_internal
                    log(f'    line: wmode={line_.wmode}'
                            + f' dir={line_.dir}'
                            + f' bbox={line_.bbox}'
                            )
                    for char in line:
                        char_ = char.m_internal
                        log(f'        char: {chr(char_.c)!r} c={char_.c:4} color={char_.color}'
                                + f' origin={char_.origin}'
                                + f' quad={char_.quad}'
                                + f' size={char_.size:6.2f}'
                                + f' font=('
                                    +  f'is_mono={char_.font.flags.is_mono}'
                                    + f' is_bold={char_.font.flags.is_bold}'
                                    + f' is_italic={char_.font.flags.is_italic}'
                                    + f' ft_substitute={char_.font.flags.ft_substitute}'
                                    + f' ft_stretch={char_.font.flags.ft_stretch}'
                                    + f' fake_bold={char_.font.flags.fake_bold}'
                                    + f' fake_italic={char_.font.flags.fake_italic}'
                                    + f' has_opentype={char_.font.flags.has_opentype}'
                                    + f' invalid_bbox={char_.font.flags.invalid_bbox}'
                                    + f' name={char_.font.name}'
                                    + f')'
                                )

    document = mupdf.FzDocument('foo.pdf')
    show_stext(document)

|expand_end|

Basic PDF viewers written in Python and C#
  • scripts/mupdfwrap_gui.py <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/scripts/mupdfwrap_gui.py>_

  • scripts/mupdfwrap_gui.cs <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/scripts/mupdfwrap_gui.cs>_

  • Build and run with:

    • ./scripts/mupdfwrap.py -b all --test-python-gui
    • ./scripts/mupdfwrap.py -b --csharp all --test-csharp-gui

Building the C++, Python and C# MuPDF APIs from source

General requirements

text
* Windows, Linux, MacOS or OpenBSD.

*
  Build should take place inside a Python `venv <https://docs.python.org/3.8/library/venv.html>`_.

*
  `libclang Python interface onto <https://libclang.readthedocs.io/en/latest/index.html>`_ the `clang C/C++ parser <https://clang.llvm.org/>`_.

* `swig <https://swig.org/>`_, for Python and C# bindings.

*
  `Mono <https://www.mono-project.com/>`_, for C# bindings on platforms
  other than Windows.


Setting up

Windows only
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

  • Install Python.

    • Use the Python Windows installer from the python.org website:
      http://www.python.org/downloads

    • Don't use other installers such as the Microsoft Store Python package.

      • If Microsoft Store Python is already installed, leave it in place and install
        from python.org on top of it - uninstalling before running the python.org
        installer has been known to cause problems.
    • A default installation is sufficient.

    • Debug binaries are required for debug builds of the MuPDF Python API.

    • If "Customize Installation" is chosen, make sure to include "py launcher" so
      that the py command will be available.

    • Also see: https://docs.python.org/3/using/windows.html

  • Install Visual Studio.

  • Note that MuPDF's Visual Studio project files specify the 142 tools.

    With Visual Studio 2022 and later these tools may not be installed.

    Possible workarounds are:

    • Install the 142 tools into Visual Studio.

    • Or build with scripts/mupdfwrap.py --vs-upgrade 1 ....

      This will create a parallel set of upgraded project files
      (with devenv's /upgrade option)
      and use them in preference to the originals.

      This should allow the build to work.

All platforms
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

  • Get the latest version of MuPDF in git.

    .. code-block:: shell

    text
    git clone --recursive git://git.ghostscript.com/mupdf.git
  • Create and enter a Python venv <https://docs.python.org/3.8/library/venv.html>_ and upgrade pip.

    • Windows.

      .. code-block:: bat

      text
      py -m venv pylocal
      .\pylocal\Scripts\activate
      python -m pip install --upgrade pip
    • Linux, MacOS, OpenBSD

      .. code-block:: shell

      text
      python3 -m venv pylocal
      . pylocal/bin/activate
      python -m pip install --upgrade pip

General build flags

text
In all of the commands below, one can set environmental variables to control
the build of the underlying MuPDF C API, for example ``USE_SYSTEM_LIBJPEG=yes``.

In addition, ``XCXXFLAGS`` can be used to set additional C++ compiler flags when
building the C++ and Python bindings (the name is analogous to the ``XCFLAGS``
used by MuPDF's makefile when compiling the core library).


Building and installing the Python bindings using ``pip``
  • Windows, Linux, MacOS.

    .. code-block:: shell

    text
    cd mupdf && pip install -vv .
  • OpenBSD.

    Building using pip is not supported because libclang is not
    available from pypi.org so pip will fail to install prerequisites from
    pypackage.toml.

    Instead one can run setup.py directly:

    .. code-block:: shell

    text
    cd mupdf && setup.py install

Building the Python bindings

text
* Windows, Linux, MacOS.

  .. code-block:: shell

      pip install libclang swig setuptools
      cd mupdf && python scripts/mupdfwrap.py -b all

* OpenBSD.

  ``libclang`` is not available from pypi.org, but we can instead use
  the system ``py3-llvm`` package.

  .. code-block:: shell

      sudo pkg_add py3-llvm
      pip install swig setuptools
      cd mupdf && python scripts/mupdfwrap.py -b all

Building the C++ bindings
  • Windows, Linux, MacOS.

    .. code-block:: shell

    text
    pip install libclang setuptools
    cd mupdf && python scripts/mupdfwrap.py -b m01
  • OpenBSD.

    libclang is not available from pypi.org, but we can instead use
    the system py3-llvm package.

    .. code-block:: shell

    text
    sudo pkg_add py3-llvm
    pip install setuptools
    cd mupdf && python scripts/mupdfwrap.py -b m01

Building the C# bindings

text
* Windows.

  .. code-block:: shell

      pip install libclang swig setuptools
      cd mupdf && python scripts/mupdfwrap.py -b --csharp all

* Linux.

  .. code-block:: shell

      sudo apt install mono-devel
      pip install libclang swig
      cd mupdf && python scripts/mupdfwrap.py -b --csharp all

* MacOS.

  Building the C# bindings on MacOS is not currently supported.

* OpenBSD.

  .. code-block:: shell

      sudo pkg_add py3-llvm mono
      pip install swig setuptools
      cd mupdf && python scripts/mupdfwrap.py -b --csharp all


Using the bindings

To use the bindings, one has to tell the OS where to find the MuPDF
runtime files.

  • C++ and C# bindings:

    • Windows.

      .. code-block:: shell

      text
      set PATH=.../mupdf/build/shared-release-x64-py3.11;%PATH%
      • Replace x64 with x32 if using 32-bit.

      • Replace 3.11 with the appropriate python version number.

    • Linux, OpenBSD.

      .. code-block:: shell

      text
      LD_LIBRARY_PATH=.../mupdf/build/shared-release

      (LD_LIBRARY_PATH must be an absolute path.)

    • MacOS.

      .. code-block:: shell

      text
      DYLD_LIBRARY_PATH=.../mupdf/build/shared-release
  • Python bindings:

    If the bindings have been built and installed using pip install,
    they will already be available within the venv.

    Otherwise:

    • Windows.

      .. code-block:: shell

      text
      PYTHONPATH=.../mupdf/build/shared-release-x64-py3.11
      • Replace x64 with x32 if using 32-bit.

      • Replace 3.11 with the appropriate python version number.

    • Linux, MacOS, OpenBSD.

      .. code-block:: shell

      text
      PYTHONPATH=.../mupdf/build/shared-release

Notes

text
* Running tests.

  Basic tests can be run by appending args to the ``scripts/mupdfwrap.py``
  command.

  This will also demonstrate how to set environment variables such as
  ``PYTHONPATH`` or ``LD_LIBRARY_PATH`` to the MuPDF build directory.

  * Python tests.

    * ``--test-python``
    * ``--test-python-gui``

  * C# tests.

    * ``--test-csharp``
    * ``--test-csharp-gui``

  * C++ tests.

    * ``--test-cpp``

* C++ bindings and ``NDEBUG``.

  When building client code that uses the C++ bindings, ``NDEBUG`` must
  be defined/undefined to match how the C++ bindings were built. By
  default the C++ bindings are a release build with ``NDEBUG`` defined, so
  usually client code must also be built with ``NDEBUG`` defined. Otherwise
  there will be build errors for missing C++ destructors, for example
  ``mupdf::FzMatrix::~FzMatrix()``.

  [This is because we define some destructors in debug builds only; this allows
  internal reference counting checks.]

* Specifying the location of Visual Studio's ``devenv.com`` on Windows.

  ``scripts/mupdfwrap.py`` looks for Visual Studio's ``devenv.com`` in
  standard locations; this can be overridden with:

  .. code-block:: shell

      python scripts/mupdfwrap.py -b --devenv <devenv.com-location> ...

* Specifying compilers.

  On non-Windows, we use ``cc`` and ``c++`` as default C and C++ compilers;
  override by setting environment variables ``$CC`` and ``$CXX``.

* OpenBSD ``libclang``.

  *
    ``libclang`` cannot be installed with pip on OpenBSD - wheels are not
    available and building from source fails.

    However unlike on other platforms, the system python-clang package
    (``py3-llvm``) is integrated with the system's libclang and can be
    used directly.

    So the above examples use ``pkg_add py3-llvm``.

* Alternatives to Python package ``libclang`` generally do not work.

  For example pypi.org's `clang <https://pypi.org/project/clang/>`_, or
  Debian's `python-clang <https://packages.debian.org/search?keywords=python+clang&searchon=names&suite=stable&section=all>`_.

  These are inconvenient to use because they require explicit setting of
  ``LD_LIBRARY_PATH`` to point to the correct libclang dynamic library.

* Debug builds.

  One can specify a debug build using the ``-d `` arg
  before ``-b``.

  .. code-block:: shell

      python ./scripts/mupdfwrap.py -d build/shared-debug -b ...

  *
    Debug builds of the Python and C# bindings on Windows have not been
    tested. There may be issues with requiring a debug version of the Python
    interpreter, for example ``python311_d.lib``.

*
  C# build failure: ``cstring.i not implemented for this target`` and/or
  ``Unknown directive '%cstring_output_allocate'``.

  This is probably because SWIG does not include support for C#. This
  has been seen in the past but as of 2023-07-19 pypi.org's default swig
  seems ok.

  A possible solution is to install SWIG using the system package
  manager, for example ``sudo apt install swig`` on Linux, or use
  ``./scripts/mupdfwrap.py --swig-windows-auto ...`` on Windows.

*
  C# omissions.

  Some functions are omitted from the C# API due to C# restrictions, for
  example functions that return void* and have out-params (because tuples
  cannot contain void* items). These will be marked with comments in the
  generated mupdf.cs file.


* More information about running ``scripts/mupdfwrap.py``.

  * Run ``python ./scripts/mupdfwrap.py -h``.
  * Read the doc-string at beginning of ``scripts/wrap/__main__.py+``.


How ``scripts/mupdfwrap.py`` builds the APIs

Building the MuPDF C API
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

  • On Unix, runs make on MuPDF's Makefile with shared=yes.

  • On Windows, runs devenv.com on .sln and .vcxproj files within MuPDF's platform/win32/ <https://cgit.ghostscript.com/cgi-bin/cgit.cgi/mupdf.git/tree/platform/win32>_
    directory.

Generation of the MuPDF C++ API
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

  • Uses clang-python to parse MuPDF's C API.

  • Generates C++ code that wraps the basic C interface, converting MuPDF
    setjmp()/longjmp() exceptions into C++ exceptions and automatically
    handling fz_context's internally.

  • Generates C++ wrapper classes for each fz_* and pdf_* struct, and uses various
    heuristics to define constructors, methods and static methods that call
    fz_*() and pdf_*() functions. These classes' constructors and destructors
    automatically handle reference counting so class instances can be copied
    arbitrarily.

  • C header file comments are copied into the generated C++ header files.

  • Compile and link the generated C++ code to create shared libraries.

Generation of the MuPDF Python and C# APIs
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

  • Uses SWIG to parse the previously-generated C++ headers and generate C++,
    Python and C# code.

  • Defines some custom-written Python and C# functions and methods, for
    example so that out-params are returned as tuples.

  • If SWIG is version 4+, C++ comments are converted into Python doc-comments.

  • Compile and link the SWIG-generated C++ code to create shared libraries.

Building auto-generated MuPDF API documentation

text
Build HTML documentation for the C, C++ and Python APIs (using Doxygen and pydoc):

.. code-block:: shell

    python ./scripts/mupdfwrap.py --doc all

This will generate the following tree:

.. code-block:: text

    mupdf/docs/generated/
        index.html
        c/
        c++/
        python/

All content is ultimately generated from the MuPDF C header file comments.

As of 2022-2-5, it looks like ``swig -doxygen`` (swig-4.02) ignores
single-line ``/** ... */`` comments, so the generated Python code (and
hence also Pydoc documentation) is missing information.

Generated files

All generated files are within the MuPDF checkout.

  • C++ headers for the MuPDF C++ API are in platform/c++/include/.

  • Files required at runtime are in build/shared-release/.

Details

.. code-block:: text

text
mupdf/
    build/
        shared-release/    [Unix runtime files.]
            libmupdf.so    [MuPDF C API, not MacOS.]
            libmupdf.dylib [MuPDF C API, MacOS.]
            libmupdfcpp.so [MuPDF C++ API.]
            mupdf.py       [MuPDF Python API.]
            _mupdf.so      [MuPDF Python API internals.]
            mupdf.cs       [MuPDF C# API.]
            mupdfcsharp.so [MuPDF C# API internals.]

        shared-debug/
            [as shared-release but debug build.]

        shared-release-x*-py*/      [Windows runtime files.]
            mupdfcpp.dll            [MuPDF C and C++ API, x32.]
            mupdfcpp64.dll          [MuPDF C and C++ API, x64.]
            mupdf.py                [MuPDF Python API.]
            _mupdf.pyd              [MuPDF Python API internals.]
            mupdf.cs                [MuPDF C# API.]
            mupdfcsharp.dll         [MuPDF C# API internals.]

    platform/
        c++/
            include/    [MuPDF C++ API header files.]
                mupdf/
                    classes.h
                    classes2.h
                    exceptions.h
                    functions.h
                    internal.h

            implementation/ [MuPDF C++ implementation source files.]
                classes.cpp
                classes2.cpp
                exceptions.cpp
                functions.cpp
                internal.cpp

            generated.pickle    [Information from clang parse step, used by later stages.]
            windows_mupdf.def   [List of MuPDF public global data, used when linking mupdfcpp.dll.]

        python/ [SWIG Python files.]
            mupdfcpp_swig.i     [SWIG input file.]
            mupdfcpp_swig.i.cpp [SWIG output file.]

        csharp/  [SWIG C# files.]
            mupdf.cs            [SWIG output file, no out-params helpers.]
            mupdfcpp_swig.i     [SWIG input file.]
            mupdfcpp_swig.i.cpp [SWIG output file.]

        win32/
            Release/    [Windows 32-bit .dll, .lib, .exp, .pdb etc.]
            x64/
                Release/    [Windows 64-bit .dll, .lib, .exp, .pdb etc.]
                    mupdfcpp64.dll  [Copied to build/shared-release*/mupdfcpp64.dll]
                    mupdfpyswig.dll [Copied to build/shared-release*/_mupdf.pyd]
                    mupdfcpp64.lib
                    mupdfpyswig.lib

        win32-vs-upgrade/   [used instead of win32/ if PYMUPDF_SETUP_MUPDF_VS_UPGRADE is '1'.]

Windows-specifics

Required predefined macros

text
Code that will use the MuPDF DLL must be built with ``FZ_DLL_CLIENT``
predefined.

The MuPDF DLL itself is built with ``FZ_DLL`` predefined.

DLLs

There is no separate C library, instead the C and C++ APIs are
both in mupdfcpp.dll, which is built by running devenv on
platform/win32/mupdf.sln.

The Python SWIG library is called _mupdf.pyd which, despite the name, is a
standard Windows DLL, built from platform/python/mupdfcpp_swig.i.cpp.

DLL export of functions and data

text
On Windows, ``include/mupdf/fitz/export.h`` defines ``FZ_FUNCTION`` and
``FZ_DATA` to `__declspec(dllexport)` and/or `__declspec(dllimport)``
depending on whether ``FZ_DLL`` or ``FZ_DLL_CLIENT`` are defined.

All MuPDF C headers prefix declarations of public global data with ``FZ_DATA``.

In generated C++ code:

* Data declarations and definitions are prefixed with ``FZ_DATA``.
* Function declarations and definitions are prefixed with ``FZ_FUNCTION``.
* Class method declarations and definitions are prefixed with ``FZ_FUNCTION``.

When building ``mupdfcpp.dll`` on Windows we link with the auto-generated
``platform/c++/windows_mupdf.def`` file; this lists all C public global data.

For reasons that are not fully understood, we don't seem to need to tag
C functions with ``FZ_FUNCTION``, but this is required for C++ functions
otherwise we get unresolved symbols when building MuPDF client code.

Building the DLLs

We build Windows binaries by running devenv.com directly.

Building _mupdf.pyd is tricky because it needs to be built with a
specific Python.h and linked with a specific python.lib. This is
done by setting environmental variables MUPDF_PYTHON_INCLUDE_PATH and
MUPDF_PYTHON_LIBRARY_PATH when running devenv.com, which are referenced
by platform/win32/mupdfpyswig.vcxproj. Thus one cannot easily build
_mupdf.pyd directly from the Visual Studio GUI.

[In the git history there is code that builds _mupdf.pyd by running the
Windows compiler and linker cl.exe and link.exe directly, which avoids
the complications of going via devenv, at the expense of needing to know where
cl.exe and link.exe are.]

C++ bindings details

Wrapper functions

text
Wrappers for a MuPDF function ``fz_foo()`` are available in multiple forms:

* Functions in the ``mupdf`` namespace.

  * ``mupdf::ll_fz_foo()``

    * Low-level wrapper:

      * Does not take ``fz_context*`` arg.
      * Translates MuPDF exceptions into C++ exceptions.
      * Takes/returns pointers to MuPDF structs.
      * Code that uses these functions will need to make explicit calls to
        ``fz_keep_*()`` and ``fz_drop_*()``.

  * ``mupdf::fz_foo()``

    * High-level class-aware wrapper:

      * Does not take ``fz_context*`` arg.
      * Translates MuPDF exceptions into C++ exceptions.
      * Takes references to C++ wrapper class instances instead of pointers to
        MuPDF structs.
      * Where applicable, returns C++ wrapper class instances instead of
        pointers to MuPDF structs.
      * Code that uses these functions does not need to call ``fz_keep_*()``
        and ``fz_drop_*()`` - C++ wrapper class instances take care of reference
        counting internally.

* Class methods

  * Where ``fz_foo()`` has a first arg (ignoring any ``fz_context*`` arg) that
    takes a pointer to a MuPDF struct ``foo_bar``, it is generally available as a
    member function of the wrapper class ``mupdf::FooBar``:

    * ``mupdf::FooBar::fz_foo()``

  * Apart from being a member function, this is identical to class-aware
    wrapper ``mupdf::fz_foo()``, for example taking references to wrapper classes
    instead of pointers to MuPDF structs.


Constructors using MuPDF functions

Wrapper class constructors are created for each MuPDF function that returns an
instance of a MuPDF struct.

Sometimes two such functions do not have different arg types so C++
overloading cannot distinguish between them as constructors (because C++
constructors do not have names).

We cope with this in two ways:

  • Create a static method that returns a new instance of the wrapper class
    by value.

    • This is not possible if the underlying MuPDF struct is not copyable - i.e.
      not reference counted and not POD.
  • Define an enum within the wrapper class, and provide a constructor that takes
    an instance of this enum to specify which MuPDF function to use.

Default constructors

text
All wrapper classes have a default constructor.

* For POD classes each member is set to a default value with ``this->foo =
  {};``. Arrays are initialised by setting all bytes to zero using
  ``memset()``.
* For non-POD classes, class member ``m_internal`` is set to ``nullptr``.
* Some classes' default constructors are customized, for example:

  * The default constructor for ``fz_color_params`` wrapper
    ``mupdf::FzColorParams`` sets state to a copy of
    ``fz_default_color_params``.
  * The default constructor for ``fz_md5`` wrapper ``mupdf::FzMd5`` sets
    state using ``fz_md5_init()``.
  * These are described in class definition comments in
    ``platform/c++/include/mupdf/classes.h``.


Raw constructors

Many wrapper classes have constructors that take a pointer to the underlying
MuPDF C struct. These are usually for internal use only. They do not call
fz_keep_*() - it is expected that any supplied MuPDF struct is already
owned.

POD wrapper classes

text
Class wrappers for MuPDF structs default to having a ``m_internal`` member which
points to an instance of the wrapped struct. This works well for MuPDF structs
which support reference counting, because we can automatically create copy
constructors, ``operator=`` functions and destructors that call the associated
``fz_keep_*()`` and ``fz_drop_*()`` functions.

However where a MuPDF struct does not support reference counting and contains
simple data, it is not safe to copy a pointer to the struct, so the class
wrapper will be a POD class. This is done in one of two ways:

* ``m_internal`` is an instance of the MuPDF struct, not a pointer.

  * Sometimes we provide members that give direct access to fields in
    ``m_internal``.

* An 'inline' POD - there is no ``m_internal`` member; instead the wrapper class
  contains the same members as the MuPDF struct. This can be a little more
  convenient to use.


Extra static methods

Where relevant, wrapper class can have static methods that wrap selected MuPDF
functions. For example FzMatrix does this for fz_concat(), fz_scale() etc,
because these return the result by value rather than modifying a fz_matrix
instance.

Miscellaneous custom wrapper classes

text
The wrapper for ``fz_outline_item`` does not contain a ``fz_outline_item`` by
value or pointer. Instead it defines C++-style member equivalents to
``fz_outline_item``'s fields, to simplify usage from C++ and Python/C#.

The fields are initialised from a ``fz_outline_item`` when the wrapper class
is constructed. In this particular case there is no need to hold on to a
``fz_outline_item``, and the use of ``std::string`` ensures that value semantics
can work.


Extra functions in C++, Python and C#
---------------------------------------------------------------

[These functions are available as low-level functions, class-aware
functions and class methods.]

.. code-block:: c++

        /**
        C++ alternative to ``fz_lookup_metadata()`` that returns a ``std::string``
        or calls ``fz_throw()`` if not found.
        */
        FZ_FUNCTION std::string fz_lookup_metadata2(fz_context* ctx, fz_document* doc, const char* key);

        /**
        C++ alternative to ``pdf_lookup_metadata()`` that returns a ``std::string``
        or calls ``fz_throw()`` if not found.
        */
        FZ_FUNCTION std::string pdf_lookup_metadata2(fz_context* ctx, pdf_document* doc, const char* key);

        /**
        C++ alternative to ``fz_md5_pixmap()`` that returns the digest by value.
        */
        FZ_FUNCTION std::vector fz_md5_pixmap2(fz_context* ctx, fz_pixmap* pixmap);

        /**
        C++ alternative to fz_md5_final() that returns the digest by value.
        */
        FZ_FUNCTION std::vector fz_md5_final2(fz_md5* md5);

        /** */
        FZ_FUNCTION long long fz_pixmap_samples_int(fz_context* ctx, fz_pixmap* pixmap);

        /**
        Provides simple (but slow) access to pixmap data from Python and C#.
        */
        FZ_FUNCTION int fz_samples_get(fz_pixmap* pixmap, int offset);

        /**
        Provides simple (but slow) write access to pixmap data from Python and
        C#.
        */
        FZ_FUNCTION void fz_samples_set(fz_pixmap* pixmap, int offset, int value);

        /**
        C++ alternative to fz_highlight_selection() that returns quads in a
        std::vector.
        */
        FZ_FUNCTION std::vector<fz_quad> fz_highlight_selection2(fz_context* ctx, fz_stext_page* page, fz_point a, fz_point b, int max_quads);

        struct fz_search_page2_hit
        {{
            fz_quad quad;
            int mark;
        }};

        /**
        C++ alternative to fz_search_page() that returns information in a std::vector.
        */
        FZ_FUNCTION std::vector<fz_search_page2_hit> fz_search_page2(fz_context* ctx, fz_document* doc, int number, const char* needle, int hit_max);

        /**
        C++ alternative to fz_string_from_text_language() that returns information in a std::string.
        */
        FZ_FUNCTION std::string fz_string_from_text_language2(fz_text_language lang);

        /**
        C++ alternative to fz_get_glyph_name() that returns information in a std::string.
        */
        FZ_FUNCTION std::string fz_get_glyph_name2(fz_context* ctx, fz_font* font, int glyph);

        /**
        Extra struct containing fz_install_load_system_font_funcs()'s args,
        which we wrap with virtual_fnptrs set to allow use from Python/C# via
        Swig Directors.
        */
        typedef struct fz_install_load_system_font_funcs_args
        {{
            fz_load_system_font_fn* f;
            fz_load_system_cjk_font_fn* f_cjk;
            fz_load_system_fallback_font_fn* f_fallback;
        }} fz_install_load_system_font_funcs_args;

        /**
        Alternative to fz_install_load_system_font_funcs() that takes args in a
        struct, to allow use from Python/C# via Swig Directors.
        */
        FZ_FUNCTION void fz_install_load_system_font_funcs2(fz_context* ctx, fz_install_load_system_font_funcs_args* args);

        /** Internal singleton state to allow Swig Director class to find
        fz_install_load_system_font_funcs_args class wrapper instance. */
        FZ_DATA extern void* fz_install_load_system_font_funcs2_state;

        /** Helper for calling ``fz_document_handler::open`` function pointer via
        Swig from Python/C#. */
        FZ_FUNCTION fz_document* fz_document_handler_open(fz_context* ctx, const fz_document_handler *handler, fz_stream* stream, fz_stream* accel, fz_archive* dir, void* recognize_state);

        /** Helper for calling a ``fz_document_handler::recognize`` function
        pointer via Swig from Python/C#. */
        FZ_FUNCTION int fz_document_handler_recognize(fz_context* ctx, const fz_document_handler *handler, const char *magic);

        /** Swig-friendly wrapper for pdf_choice_widget_options(), returns the
        options directly in a vector. */
        FZ_FUNCTION std::vector<std::string> pdf_choice_widget_options2(fz_context* ctx, pdf_annot* tw, int exportval);

        /** Swig-friendly wrapper for fz_new_image_from_compressed_buffer(),
        uses specified ``decode`` and ``colorkey`` if they are not null (in which
        case we assert that they have size ``2*fz_colorspace_n(colorspace)``). */
        FZ_FUNCTION fz_image* fz_new_image_from_compressed_buffer2(
                fz_context* ctx,
                int w,
                int h,
                int bpc,
                fz_colorspace* colorspace,
                int xres,
                int yres,
                int interpolate,
                int imagemask,
                const std::vector<float>& decode,
                const std::vector& colorkey,
                fz_compressed_buffer* buffer,
                fz_image* mask
                );

        /** Swig-friendly wrapper for pdf_rearrange_pages(). */
        void pdf_rearrange_pages2(
                fz_context* ctx,
                pdf_document* doc,
                const std::vector& pages,
                pdf_clean_options_structure structure
                );

        /** Swig-friendly wrapper for pdf_subset_fonts(). */
        void pdf_subset_fonts2(fz_context *ctx, pdf_document *doc, const std::vector& pages);

        /** Swig-friendly and typesafe way to do fz_snprintf(fmt, value). ``fmt``
        must end with one of 'efg' otherwise we throw an exception. */
        std::string fz_format_double(fz_context* ctx, const char* fmt, double value);

        struct fz_font_ucs_gid
        {{
            unsigned long ucs;
            unsigned int gid;
        }};

        /** SWIG-friendly wrapper for fz_enumerate_font_cmap(). */
        std::vector<fz_font_ucs_gid> fz_enumerate_font_cmap2(fz_context* ctx, fz_font* font);

        /** SWIG-friendly wrapper for pdf_set_annot_callout_line(). */
        void pdf_set_annot_callout_line2(fz_context *ctx, pdf_annot *annot, std::vector<fz_point>& callout);

        /** SWIG-friendly wrapper for fz_decode_barcode_from_display_list(),
        avoiding leak of the returned string. */
        std::string fz_decode_barcode_from_display_list2(fz_context *ctx, fz_barcode_type *type, fz_display_list *list, fz_rect subarea, int rotate);

        /** SWIG-friendly wrapper for fz_decode_barcode_from_pixmap(), avoiding
        leak of the returned string. */
        std::string fz_decode_barcode_from_pixmap2(fz_context *ctx, fz_barcode_type *type, fz_pixmap *pix, int rotate);

        /** SWIG-friendly wrapper for fz_decode_barcode_from_page(), avoiding
        leak of the returned string. */
        std::string fz_decode_barcode_from_page2(fz_context *ctx, fz_barcode_type *type, fz_page *page, fz_rect subarea, int rotate);


Python/C# bindings details
---------------------------------------------------------------

Extra Python functions

Access to raw C arrays
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

The following functions can be used from Python to get access to raw data:

  • mupdf.bytes_getitem(array, index): Gives access to individual items
    in an array of unsigned char's, for example in the data returned by
    mupdf::FzPixmap's samples() method.

  • mupdf.floats_getitem(array, index): Gives access to individual items in an
    array of float's, for example in fz_stroke_state's float dash_list[32]
    array. Generated with SWIG code carrays.i and array_functions(float, floats);.

  • mupdf.python_buffer_data(b): returns a SWIG wrapper for a const unsigned char* pointing to a Python buffer instance's raw data. For example b can
    be a Python bytes or bytearray instance.

  • mupdfpython_mutable_buffer_data(b): returns a SWIG wrapper for an unsigned char* pointing to a Python buffer instance's raw data. For example b can
    be a Python bytearray instance.

[These functions are implemented internally using SWIG's carrays.i and
pybuffer.i.

Python differences from C API

text
[The functions described below are also available as class methods.]


Custom methods
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

Python and C# code does not easily handle functions that return raw data, for example
as an ``unsigned char*`` that is not a zero-terminated string. Sometimes we provide a
C++ method that returns a ``std::vector`` by value, so that Python and C# code can
wrap it in a systematic way.

For example ``Md5::fz_md5_final2()``.

For all functions described below, there is also a ``ll_*`` variant that
takes/returns raw MuPDF structs instead of wrapper classes.


New functions
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

* ``fz_buffer_extract_copy()``: Returns copy of buffer data as a Python ``bytes``.
* ``fz_buffer_storage_memoryview(buffer, writable)``: Returns a readonly/writable Python memoryview onto ``buffer``.
  Relies on ``buffer`` existing and not changing size while the memory view is used.
* ``fz_pixmap_samples_memoryview()``: Returns Python ``memoryview`` onto ``fz_pixmap`` data.

* ``fz_lookup_metadata2(fzdocument, key)``: Return key value or raise an exception if not found:
* ``pdf_lookup_metadata2(pdfdocument, key)``: Return key value or raise an exception if not found:

Implemented in Python
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

* ``fz_format_output_path()``
* ``fz_story_positions()``
* ``pdf_dict_getl()``
* ``pdf_dict_putl()``

Non-standard API or implementation
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

* ``fz_buffer_extract()``: Returns a *copy* of the original buffer data as a Python ``bytes``. Still clears the buffer.
* ``fz_buffer_storage()``: Returns ``(size, data)`` where ``data`` is a low-level SWIG representation of the buffer's storage.
* ``fz_convert_color()``: No ``float* fv`` param, instead returns ``(rgb0, rgb1, rgb2, rgb3)``.
* ``fz_fill_text()``: ``color`` arg is tuple/list of 1-4 floats.
* ``fz_lookup_metadata(fzdocument, key)``: Return key value or None if not found:
* ``fz_new_buffer_from_copied_data()``: Takes a Python ``bytes`` (or other Python buffer) instance.
* ``fz_set_error_callback()``: Takes a Python callable; no ``void* user`` arg.
* ``fz_set_warning_callback()``: Takes a Python callable; no ``void* user`` arg.
* ``fz_warn()``: Takes single Python ``str`` arg.
* ``pdf_dict_putl_drop()``: Always raises exception because not useful with automatic ref-counts.
* ``pdf_load_field_name()``: Uses extra C++ function ``pdf_load_field_name2()`` which returns ``std::string`` by value.
* ``pdf_lookup_metadata(pdfdocument, key)``: Return key value or None if not found:
* ``pdf_set_annot_color()``: Takes single ``color`` arg which must be float or tuple of 1-4 floats.
* ``pdf_set_annot_interior_color()``: Takes single ``color`` arg which must be float or tuple of 1-4 floats.
* ``fz_install_load_system_font_funcs()``: Takes Python callbacks with no ``ctx`` arg,
  which can return ``None``, ``fz_font*`` or a ``mupdf.FzFont``.

  Example usage (from ``scripts/mupdfwrap_test.py:test_install_load_system_font()``)::

    def font_f(name, bold, italic, needs_exact_metrics):
        print(f'font_f(): Looking for font: {name=} {bold=} {italic=} {needs_exact_metrics=}.')
        return mupdf.fz_new_font_from_file(...)
    def f_cjk(name, ordering, serif):
        print(f'f_cjk(): Looking for font: {name=} {ordering=} {serif=}.')
        return None
    def f_fallback(script, language, serif, bold, italic):
        print(f'f_fallback(): looking for font: {script=} {language=} {serif=} {bold=} {italic=}.')
        return None
    mupdf.fz_install_load_system_font_funcs(font_f, f_cjk, f_fallback)


Making MuPDF function pointers call Python code

Overview
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

For MuPDF structs with function pointers, we provide a second C++ wrapper
class for use by the Python bindings.

  • The second wrapper class has a 2 suffix, for example PdfFilterOptions2.

  • This second wrapper class has a virtual method for each function pointer, so
    it can be used as a SWIG Director class <https://swig.org/Doc4.0/SWIGDocumentation.html#SWIGPlus_target_language_callbacks>_.

  • Overriding a virtual method in Python results in the Python method being
    called when MuPDF C code calls the corresponding function pointer.

  • One needs to activate the use of a Python method as a callback by calling the
    special method use_virtual_<method-name>(). [It might be possible in future
    to remove the need to do this.]

  • It may be possible to use similar techniques in C# but this has not been
    tried.

Callback args
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

Python callbacks have args that are more low-level than in the rest of the
Python API:

  • Callbacks generally have a first arg that is a SWIG representation of a MuPDF
    fz_context*.

  • Where the underlying MuPDF function pointer has an arg that is a pointer to
    an MuPDF struct, unlike elsewhere in the MuPDF bindings we do not translate
    this into an instance of the corresponding wrapper class. Instead Python
    callbacks will see a SWIG representation of the low-level C pointer.

    • It is not safe to construct a Python wrapper class instance directly from
      such a SWIG representation of a C pointer, because it will break MuPDF's
      reference counting - Python/C++ constructors that take a raw pointer to a
      MuPDF struct do not call fz_keep_*() but the corresponding Python/C++
      destructor will call fz_drop_*().

    • It might be safe to create an wrapper class instance using an explicit call
      to mupdf.fz_keep_*(), but this has not been tried.

  • As of 2023-02-03, exceptions from Python callbacks are propagated back
    through the Python, C++, C, C++ and Python layers. The resulting Python
    exception will have the original exception text, but the original Python
    backtrace is lost.

Exceptions in callbacks
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

Python exceptions in Director callbacks are propagated back through the
language layers (from Python to C++ to C, then back to C++ and finally to
Python).

For convenience we add a text representation of the original Python backtrace
to the exception text, but the C layer's fz_try/catch exception handling only
holds 256 characters of exception text, so this backtrace information may be
truncated by the time the exception reaches the original Python code's except ... block.

Example
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

Here is an example PDF filter written in Python that removes alternating items:

Details

|expand_begin|

.. code-block::

text
import mupdf

def test_filter(path):
    class MyFilter( mupdf.PdfFilterOptions2):
        def __init__( self):
            super().__init__()
            self.use_virtual_text_filter()
            self.recurse = 1
            self.sanitize = 1
            self.state = 1
            self.ascii = True
        def text_filter( self, ctx, ucsbuf, ucslen, trm, ctm, bbox):
            print( f'text_filter(): ctx={ctx} ucsbuf={ucsbuf} ucslen={ucslen} trm={trm} ctm={ctm} bbox={bbox}')
            # Remove every other item.
            self.state = 1 - self.state
            return self.state

    filter_ = MyFilter()

    document = mupdf.PdfDocument(path)
    for p in range(document.pdf_count_pages()):
        page = document.pdf_load_page(p)
        print( f'Running document.pdf_filter_page_contents on page {p}')
        document.pdf_begin_operation('test filter')
        document.pdf_filter_page_contents(page, filter_)
        document.pdf_end_operation()

    document.pdf_save_document('foo.pdf', mupdf.PdfWriteOptions())

|expand_end|

.. External links


Ext Mupdf Docs Tools Mupdf Gl (`ext/mupdf/docs/tools/mupdf-gl.rst`)

mupdf-gl

The mupdf-gl viewer can show PDF, XPS, CBZ, EPUB, & FB2 documents as well as typical image formats including SVG.

Command Line Options

.. code-block::

text
mupdf-gl [options] document [page]

[options]
The options are:

text
``-p`` password
	The password needed to open a password protected PDF file.
``-c`` profile.cc
	Load an ICC profile to use for display (default is sRGB).
``-r`` resolution
	Set the initial zoom level, specified as DPI. The default value is ``72``.
``-W`` width
	Set the page width in points for EPUB layout.
``-H`` height
	Set the page height in points for EPUB layout.
``-S`` size
	Set the default font size in points for EPUB layout.
``-U`` stylesheet
	Specify a CSS file containing user styles to load for EPUB layout.
``-X``
	Ignore publisher styles for EPUB layout.
``-J``
	Disable Javascript in PDF forms.
``-A`` level
	Set anti-aliasing level or pixel rendering rule.
	- ``0``: off.
	- ``2``: 4 levels.
	- ``4``: 16 levels.
	- ``8``: 256 levels.
	- ``9``: using centre of pixel rule.
	- ``10``: using any part of a pixel rule.

``-I``
	Start in inverted color mode.
``-B`` hex-color
	Set black tint color (default ``303030``).
``-C`` hex-color
	Set white tint color (default ``FFFFF0``).
``-Y`` factor
	Set UI scaling factor (default calculated from screen DPI).

[page]
The initial page number to show.

Examples

To open the viewer with a file browser, just start it:

.. code-block:: bash

text
mupdf-gl

To open a specific file, in inverted color mode, on page 10:

.. code-block:: bash

text
mupdf-gl -I mupdf_explored.pdf 10

Mouse Bindings

The middle mouse button (scroll wheel button) pans the document view.

The right mouse button selects a region and copies the marked text to the clipboard.

Key Bindings

Several key bindings can take a number argument entered before the key, to modify the command.
For example, to zoom to 150 dpi, type "150z".

.. list-table::
:header-rows: 0
:align: left
:width: 80%
:widths: auto

text
* - **F1**
  - Display help.
* - **i**
  - Show document information.
* - **o**
  - Show document outline.
* - **L**
  - Highlight links.
* - **F**
  - Highlight form fields.
* - **a**
  - Show annotation editor.
* - **r**
  - Reload document.
* - **S**
  - Save document (only for PDF).
* - **q**
  - Quit viewer.

.. list-table::
:header-rows: 0
:align: left
:width: 80%
:widths: auto

text
* - **<**
  - Decrease E-book font size.
* - **>**
  - Increase E-book font size.
* - **I**
  - Toggle inverted color mode.
* - **C**
  - Toggle tinted color mode.
* - **E**
  - Toggle ICC color management.
* - **e**
  - Toggle spot color emulation.
* - **A**
  - Toggle anti-aliasing.

.. list-table::
:header-rows: 0
:align: left
:width: 80%
:widths: auto

text
* - **f**
  - Toggle fullscreen.
* - **w**
  - Shrinkwrap window to fit page.
* - **W**
  - Fit page width to window.
* - **H**
  - Fit page height to window.
* - **Z**
  - Fit page size to window.
* - **[number] z**
  - Set zoom resolution in DPI.
* - **+**
  - Zoom in.
* - **-**
  - Zoom out.
* - **[**
  - Rotate counter-clockwise.
* - **]**
  - Rotate clockwise.
* - **⬅️➡️⬆️⬇️** or **h**, **j**, **k**, **l**
  - Pan page in small increments.

.. list-table::
:header-rows: 0
:align: left
:width: 80%
:widths: auto

text
* - **b**
  - Smart move one screenful backward.
* - **[space]**
  - Smart move one screenful forward.
* - **[comma]** or **[page up]**
  - Go one page backward.
* - **[period]** or **[page down]**
  - Go one page forward.
* - **[number] g**
  - Go to page number.
* - **G**
  - Go to last page.

.. list-table::
:header-rows: 0
:align: left
:width: 80%
:widths: auto

text
* - **m**
  - Save current page to navigation history.
* - **t**
  - Go back in navigation history.
* - **T**
  - Go forward in navigation history.
* - **[number] m**
  - Save current page in numbered bookmark.
* - **[number] t**
  - Go to numbered bookmark.

.. list-table::
:header-rows: 0
:align: left
:width: 80%
:widths: auto

text
* - **/**
  - Start searching forward.
* - **?**
  - Start searching backward.
* - **n**
  - Continue searching forward.
* - **N**
  - Continue searching backward.

Ext Mupdf Docs Tools Mutool Audit (`ext/mupdf/docs/tools/mutool-audit.rst`)

mutool audit

The audit command prints a report of operator and space usage for a PDF file.

.. code-block:: bash

text
mutool audit [options] file

[options]
Options are as follows:

text
``-o`` output file
	Use the specified filename for the produced HTML report.

file
Input file name. The input must be a PDF file.

The report takes the form of an HTML document. The report assumes a fair degree
of familiarity with the PDF format.

The first section of the file gives figures for the proportions of the file
that are objects vs the overheads for storing those objects, for both objects
inside and outside of ObjStms. It also lists the size of as a percentage of the
whole file.

The second section of the file gives figures for the usage of the different
objects within the file. The classification of objects is simplistic, but works
in most cases.

The final section of the file gives figures for the frequency of use of
different graphical operators within operator streams. These figures should be
taken as being indicative, rather than 100% accurate.


Ext Mupdf Docs Tools Mutool Bake (`ext/mupdf/docs/tools/mutool-bake.rst`)

mutool bake

The bake command bakes PDF forms into static content.

.. code-block:: bash

text
mutool bake [options] input.pdf [output.pdf]

[options]
Options are as follows:

text
``-A``
	Do not bake annotations into page contents.

``-F``
	Do not bake form fields widgets into page contents.

``-O`` options
	See :doc:`/reference/common/pdf-write-options`.
	If none are given ``garbage`` is used by default.

input.pdf
Name of input PDF file.

output.pdf
Name of output PDF file. If none is given out.pdf is used by default.

Normally annotations and form field widgets are separate content streams
that get drawn on top of the page contents. This command bakes annotations
and/or widgets into the page contents, making them inseparable.
After baking in annotations and widgets they are just visual representaitons
of what the original annotation/widget looked like, their properties can no
longer be edited since they no longer exist as proper annotation/widgets.


Ext Mupdf Docs Tools Mutool Barcode (`ext/mupdf/docs/tools/mutool-barcode.rst`)

mutool barcode

The barcode command is used to either decode a barcode from an image or
document, or to create a new barcode image.

text
This command may not be available!

It is only present if MuPDF was compiled with the ZXing barcode library.

Decoding barcodes

text
.. code-block:: bash

	mutool barcode -d [options] file1.pdf [pages1] [file2.pdf [pages2] ...]

``[options]``
	Options are as follows:

	``-p`` password
		Use the specified password if the file is encrypted.

	``-o`` output
		The output file name (e.g. "output.txt"). If this option is not
		present, any text from decoded barcodes will be printed to
		standard out.

	``-r`` rotation
		How much to rotate the input pages in degrees (0-360), before trying to decode any barcodes.

``file1`` and ``file2``, etc.
	Input file name. The input can be any of the supported document formats.

``[pages1]`` and ``pages2``, etc.
	Comma-separated list of page ranges. The first page is "1", and the last page is "N". The default is "1-N".

Encoding barcodes

.. code-block:: bash

text
mutool barcode -c [options] text

[options]
Options are as follows:

text
``-o`` output
	The output file name. PNG or PDF format is chosen depending on
	the file extension. If none is given, the default is
	``out.png``.

``-F`` format
	The desired output barcode format:

	- ``aztec``
	- ``codabar``
	- ``code39``
	- ``code93``
	- ``code128``
	- ``databar``
	- ``databarexpanded``
	- ``datamatrix``
	- ``ean8``
	- ``ean13``
	- ``itf``
	- ``maxicode``
	- ``pdf417``
	- ``qrcode``
	- ``upca``
	- ``upce``
	- ``microqrcode``
	- ``rmqrcode``
	- ``dxfilmedge``
	- ``databarlimited``

``-s`` size
	Set size in pixels for the output barcode. If not specified,
	the smallest size that can be decoded is chosen.

``-q``
	Add quiet zones around the barcode. This puts an empty margin
	around created barcodes.

``-t``
	Add human-readable text, when available. Some barcodes, e.g.
	EAN-13, may have the barcode contents printed in human-readable
	text next to the barcode, which is enabled by this flag.

``-e`` level
	Set error correction level (0-8). Some barcodes, e.g. QR-codes,
	support several levels of error correction, which can be
	customized through this option.

text
The text to be encoded into a barcode.


Ext Mupdf Docs Tools Mutool Clean (`ext/mupdf/docs/tools/mutool-clean.rst`)

mutool clean

The clean command pretty prints and rewrites the syntax of a PDF file. It can be used to repair broken files, expand compressed streams, filter out a range of pages, etc.

.. code-block:: bash

text
mutool clean [options] input.pdf [output.pdf] [pages]

[options]
Options are as follows:

text
``-p`` password
	 Use the specified password if the file is encrypted.

``-g``
	 Garbage collect unused objects.

``-gg``
	 In addition to ``-g`` compact xref table.

``-ggg``
	 In addition to ``-gg`` merge duplicate objects.

``-gggg``
	 In addition to ``-ggg`` check streams for duplication.

``-l``
	 Linearize PDF (no longer supported!).

``-D``
	 Save file without encryption.

``-E`` encryption
	 Save file with new encryption (``rc4-40``, ``rc4-128``, ``aes-128``, or ``aes-256``).

``-O`` owner_password
	 Owner password (only if encrypting).

``-U`` user_password
	 User password (only if encrypting).

``-P`` permission
	 Permission flags (only if encrypting).

``-a``
	 ASCII hex encode binary streams.

``-d``
	 Decompress streams.

``-z``
	 Deflate uncompressed streams.

``-e`` effort
	 Effort spent compressing the output file. 0 is default amount, otherwise a percentage of the maximum effort, 1 for minimum and 100 for maximum effort.

``-f``
	 Compress font streams.

``-i``
	 Compress image streams.

``-c``
	 Pretty-print graphics commands in content streams.

``-s``
	 Sanitize graphics commands in content streams.

``-t``
	 Compact object syntax.

``-tt``
	 Use indented object syntax to make PDF objects more readable.

``-L``
	 Print comments containing labels showing how each object can be reached from the Root.

``-v``
	 Transform all text into vector graphics commands.

``-A``
	 Create appearance streams for annotations that are missing appearance streams.

``-AA``
	 Recreate appearance streams for all annotations.

``-m``
	 Preserve metadata.

``-S``
	 Subset fonts if possible. (EXPERIMENTAL!)

``-Z``
	 Use object streams cross reference streams for extra compression.

``--(color|gray|bitonal)-(|lossy-|lossless-)image-subsample-method=method``
	 Set the subsampling method (``average``, or ``bicubic``) for the desired image types, for example color-lossy and bitonal-lossless.

``--(color|gray|bitonal)-(|lossy-|lossless-)image-subsample-dpi=dpi``
	 Set the resolution at which to subsample.

``--(color|gray|bitonal)-(|lossy-|lossless-)image-recompress-method=quality``
	 Set the recompression quality to either of ``never``, ``same``, ``lossless``, ``jpeg``, ``j2k``, ``fax``, or ``jbig2``.

``--recompress-images-when=when``
	 Recompress images when the rewritten images by the subsampling- or recompression methods above are ``smaller`` (default) or ``always`` recompress them.

``--structure=keep|drop``
	 Keep or drop the structure tree.

input.pdf
Input file name. Must be a PDF file.

[output.pdf]
The output file. Must be a PDF file.

text
If no output file is specified, it will write the cleaned PDF to "out.pdf" in the current directory.

[pages]
Comma-separated list of page numbers and ranges (for example:
1,5,10-15,20-N), where the character N denotes the last page. If no
pages are specified, then all pages will be included.


Ext Mupdf Docs Tools Mutool Convert (`ext/mupdf/docs/tools/mutool-convert.rst`)

.. _mutool_convert:

mutool convert

The convert command converts an input file into another format.

.. code-block::

text
mutool convert [options] file [pages]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.
``-o`` output
	The output file name. The output format is inferred from the output filename. Embed ``%d`` in the name to indicate the page number (for example: "page%d.png"). Printf modifiers are supported, for example "%03d". If no output is specified, the output will go to ``stdout``.
``-F`` output format (default inferred from output file name)
	- raster: ``cbz``, ``png``, ``pnm``, ``pgm``, ``ppm``, ``pam``, ``pbm``, ``pkm``.
	- print-raster: ``pcl``, ``pclm``, ``ps``, ``pwg``.
	- vector: ``pdf``, ``svg``.
	- text: ``html``, ``xhtml``, ``text``, ``stext``.
``-O`` comma-separated list of options for output format.
	See
	:doc:`/reference/common/pdf-write-options`
	and
	:doc:`/reference/common/document-writer-options`.

``-b`` box
	Use named page box (``MediaBox``, ``CropBox``, ``BleedBox``, ``TrimBox``, or ``ArtBox``).
``-A`` bits
	Specify how many bits of anti-aliasing to use. The default is ``8``.
``-W`` width
	Page width in points for EPUB layout.
``-H`` height
	Page height in points for EPUB layout.
``-S`` size
	Font size in points for EPUB layout.
``-U`` filename
	User CSS stylesheet for EPUB layout.
``-X``
	Disable document styles for EPUB layout.

file
Input file name. The file can be any of the supported input formats.

[pages]
Comma-separated list of page ranges. The first page is "1", and the
last page is "N". The default is "1-N".


Ext Mupdf Docs Tools Mutool Create (`ext/mupdf/docs/tools/mutool-create.rst`)

mutool create

The create command creates a new PDF file with the contents created from one or more input files containing graphics commands.

.. code-block:: bash

text
mutool create [-o output.pdf] [-O options] page.txt [page2.txt ...]

[-o output.pdf]
The output filename. Defaults to "out.pdf" if not supplied.

[-O options]
See :doc:/reference/common/pdf-write-options.

page.txt
Files containing the contents of a page.

Content Streams

A page is created for each input file, with the contents of the file copied into the content stream.

Special comments in the input files are parsed to define the page dimensions and to create font and image resources.

%%MediaBox LLX LLY URX URY
Set the page size (default A4).

%%Rotate Angle
Set the page rotation (default 90 degrees).

%%Font Name Filename Encoding
- Create a simple font resource.
- The filename can be either a font file or one of the Base-14 font names (Times-Roman, etc).
- Encoding is one of Latin (for ISO-8859-1), Greek (for ISO-8859-7) or Cyrillic (for KOI-8).

%%CJKFont Name Language WMode Style Language
- Create a CID font resource for CJK scripts.
- Language is one of zh-Hant, zh-Hans, ja, ko.
- Wmode is one of H or V.
- Style is serif or sans.

%%Image Name Filename
Create an image resource.

Example #1

text
Define an image:

.. code-block::

	%%MediaBox 0 0 500 800
	%%Rotate 90
	%%Image Im0 path/to/image.png

Example #2

Font resources can be created by either giving the name of a standard PDF font, or by giving the path to a font file. If a third argument is present and either "Greek" or "Cyrillic" the font will be encoded using ISO 8859-7 or KOI8-U, respectively:

.. code-block::

text
%%Font Tm Times-Roman
%%Font TmG Times-Roman Greek
%%Font TmC Times-Roman Cyrillic
%%Font Fn0 path/to/font/file.ttf
%%Font Fn1 path/to/font/file.ttf Cyrillic

Example #3

text
CJK fonts can be created by passing a language tag for one of the 4 CID orderings: zh-Hant, zh-Hans, ja, or ko (Traditional Chinese, Simplified Chinese, Japanese, Korean). The CJK font will use the UTF-16 encoding. A font file will not be embedded, so a PDF viewer will use a substitute font:

.. code-block::

	%%CJKFont Batang ko
	%%CJKFont Mincho ja
	%%CJKFont Ming zh-Hant
	%%CJKFont Song zh-Hans

Putting it all together

An example input file, which adds an image, a triangle and some text:

.. code-block::

text
%%MediaBox 0 0 595 842
%%Font TmRm Times-Roman
%%Font Helv-C Helvetica Cyrillic
%%Font Helv-G Helvetica Greek
%%CJKFont Song zh-Hant
%%CJKFont Mincho ja
%%CJKFont Batang ko
%%Image I0 docs/examples/huntingofthesnark.png

% Draw an image.
q
480 0 0 480 50 250 cm
/I0 Do
Q

% Draw a triangle.
q
1 0 0 rg
50 50 m
100 200 l
200 50 l
f
Q

% Show some text.
q
0 0 1 rg
BT /TmRm 24 Tf 50 760 Td (Hello, world!) Tj ET
BT /Helv-C 24 Tf 50 730 Td <fac4d2c1d7d3d4d7d5cad4c521> Tj ET
BT /Helv-G 24 Tf 50 700 Td <eae1ebe7ecddf1e1> Tj ET
BT /Song 24 Tf 50 670 Td <4F60 597D> Tj ET
BT /Mincho 24 Tf 50 640 Td <3053 3093 306b 3061 306f> Tj ET
BT /Batang 24 Tf 50 610 Td <c548 b155 d558 c138 c694> Tj ET
Q

Ext Mupdf Docs Tools Mutool Draw (`ext/mupdf/docs/tools/mutool-draw.rst`)

mutool draw

The draw command will render a document to image files, convert to another vector format, or extract the text content.

  • The supported input document formats are: pdf, xps, cbz, md, and epub.

  • The supported output image formats are: pbm, pgm, ppm, pam, png, pwg, pcl and ps.

  • The supported output vector formats are: svg, pdf, and debug trace (as xml).

  • The supported output text formats are: plain text, html, and structured text (as xml or json).

.. code-block:: bash

text
mutool draw [options] file [pages]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.
``-o`` output
	The output file name. The output format is inferred from the output filename. Embed ``%d`` in the name to indicate the page number (for example: "page%d.png"). Printf modifiers are supported, for example "%03d". If no output is specified, the output will go to ``stdout`` for text output formats, for image output formats nothing is outputted.
``-F`` format
	Enforce a specific output format. Only necessary when outputting to ``stdout`` since normally the output filename is used to infer the output format.
``-q``
	Be quiet, do not print progress messages.
``-R`` angle
	Rotate clockwise by given number of degrees.
``-r`` resolution
	Render the page at the specified resolution. The default resolution is 72 dpi.
``-w`` width
	Render the page at the specified width (or, if the ``-r`` flag is used, render with a maximum width).
``-h`` height
	Render the page at the specified height (or, if the ``-r`` flag is used, render with a maximum height).
``-f``
	Fit exactly; ignore the aspect ratio when matching specified width/heights.
``-b`` box
	Use named page box (``MediaBox``, ``CropBox``, ``BleedBox``, ``TrimBox``, or ``ArtBox``).
``-B`` bandheight
	Render in banded mode with each band no taller than the given height. This uses less memory during rendering. Only compatible with ``pam``, ``pgm``, ``ppm``, ``pnm`` and ``png`` output formats. Banded rendering and md5 checksumming may not be used at the same time.
``-T`` threads
	Number of threads to use for rendering (banded mode only).
``-W`` width
	Page width in points for EPUB layout.
``-H`` height
	Page height in points for EPUB layout.
``-S`` size
	Font size in points for EPUB layout.
``-U`` filename
	User CSS stylesheet for EPUB layout.
``-X``
	Disable document styles for EPUB layout.
``-a``
	Disable usage of accelerator file.
``-c`` colorspace
	Render in the specified colorspace. Supported colorspaces are: ``mono``, ``gray``, ``grayalpha``, ``rgb``, ``rgbalpha``, ``cmyk``, ``cmykalpha``. Some abbreviations are allowed: ``m``, ``g``, ``ga``, ``rgba``, ``cmyka``. The default is chosen based on the output format.
``-e`` filename
	Proof ICC profile filename for rendering.
``-G`` gamma
	Apply gamma correction. Some typical values are 0.7 or 1.4 to thin or darken text rendering.
``-I``
	Invert colors.
``-s`` [mft5]
	Show various bits of information: ``m`` for glyph cache and total memory usage, ``f`` for page features such as whether the page is grayscale or color, ``t`` for per page rendering times as well statistics, and ``5`` for md5 checksums of rendered images that can be used to check if rendering has changed.
``-A`` bits
	Specify how many bits of anti-aliasing to use. The default is ``8``. ``0`` means no anti-aliasing, ``9`` means no anti-aliasing, centre-of-pixel rule, ``10`` means no anti-aliasing, any-part-of-a-pixel rule.
``-A`` graphics-bits/text-bits
	Specify separate numbers of bits for anti-aliasing for graphics and for text, use a slash ``/`` as separator.
``-l`` width
	Minimum stroke line width (in pixels).
``-K``
	Do not draw text.
``-KK``
	Only draw text.
``-D``
	Disable use of display lists. May cause slowdowns, but should reduce the amount of memory used.
``-i``
	Ignore errors.
``-m`` limit
	Limit memory usage in bytes.
``-L``
	Low memory mode (avoid caching objects by clearing cache after each page).
``-P``
	Run interpretation and rendering at the same time.
``-N``
	Disable ICC workflow.
``-M`` overprint
	Control spot/overprint rendering: ``0`` for no spot rendering, ``1`` for Overprint simulation (default), or ``2`` for full spot rendering.
``-t`` language
	Specify language/script for OCR (default: eng)
``-d`` ocr-file-path
	Specify path for OCR files (default: rely on ``TESSDATA_PREFIX`` environment variable.
``-k`` correction
	Set the skew correction, either one of ``auto``, ``0`` for increase size, ``1`` for maintain size, or ``2`` for decrease size.
``-k`` correction,angle
	Set the skew correction as well as the angle.
``-y l``
	Print the layer configs to stderr.
``-y`` layer-number
	Select layer config (by number from ``-y l``).
``-y`` layer-number,item1,item2,...
	Select layer config (by number from ``-y l``) and toggle the listed items.
``-Y``
	Print the individual layers to stderr.
``-z`` layer-number
	Hide individual layer.
``-Z`` layer-number
	Show individual layer.

file
Input file name.

[pages]
Comma-separated list of page ranges. The first page is "1", and the last page is "N". The default is "1-N".


Ext Mupdf Docs Tools Mutool Extract (`ext/mupdf/docs/tools/mutool-extract.rst`)

mutool extract

The extract command can be used to extract images and font files from a PDF file. The image and font files will be saved out to the same folder which the file originates from.

.. code-block:: bash

text
mutool extract [options] input.pdf [object numbers]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.

``-r``
	Convert images to RGB when extracting them.

``-a``
	Embed SMasks as alpha channel.

``-N``
	Do not use ICC color conversions.

input.pdf
Input file name. Must be a PDF file.

[object numbers]
An array of object ids to extract from. If no object numbers are given
on the command line, all images and fonts will be extracted from the
file.


Ext Mupdf Docs Tools Mutool Grep (`ext/mupdf/docs/tools/mutool-grep.rst`)

mutool grep

The grep command searches for text in a document and prints each matching line.

.. code-block::

text
mutool grep [options] pattern file [ file2 ...]

pattern

file
Document to search in.

[options]

text
Basic options:

``-p`` password
	Use the specified password if the file is encrypted.
``-F``
	The pattern is a fixed string.
``-a``
	Ignore diacritics and accents (treat ä and a as the same).
``-i``
	Ignore case (treat A and a as the same).
``-H``
	Print filename for each match.
``-n``
	Print page number for each patch.

Advanced options:

``-S``
	See :doc:`/reference/common/search-options`.
``-O``
	See :doc:`/reference/common/stext-options`.
``-b``
	Search backwards, starting from the end.
``-[`` start-mark
	Insert mark at the start of each match when printing results.
``-]`` end-mark
	Insert mark at the end of each match when printing results.
``-v``
	Show verbose search debugging feedback.
``-q``
	Hide all warnings and error messages. Use at your own risk!

Ext Mupdf Docs Tools Mutool Info (`ext/mupdf/docs/tools/mutool-info.rst`)

mutool info

The info command shows info about the objects on pages in an input file. For each page the page object number is shown, and then detailed information about the desired objects.

.. code-block:: bash

text
mutool info [options] file [pages]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.

``-F``
	List fonts.

``-I``
	List images.

``-M``
	List dimensions.

``-P``
	List patterns.

``-S``
	List shadings.

``-X``
	List form and postscript xobjects.

``-Z``
	List ZUGFeRD info.

file
The input file.

[pages]
Comma-separated list of page numbers and ranges. If no pages are
supplied then all document pages will be considered for the output
file.


Ext Mupdf Docs Tools Mutool Merge (`ext/mupdf/docs/tools/mutool-merge.rst`)

mutool merge

The merge command is used to pick out pages from two or more files and merge them into a new output file.

.. code-block:: bash

text
mutool merge [-o output.pdf] [-O options] input1.pdf [pages1] [input2.pdf] [pages2] ...

[-o output.pdf]
The output filename. Defaults to "out.pdf" if not supplied.

[-O options]
See :doc:/reference/common/pdf-write-options.

input1.pdf
The first document.

[pages1]
Comma-separated list of page ranges for the first document (input.pdf). The first page is "1", and the last page is "N". The default is "1-N".

[input2.pdf]
The second document.

[pages2]
Comma-separated list of page ranges for the second document (input2.pdf). The first page is "1", and the last page is "N". The default is "1-N".

...
Indicates that we add as many additional [input] & [pages] pairs as required to merge multiple documents.


Ext Mupdf Docs Tools Mutool Pages (`ext/mupdf/docs/tools/mutool-pages.rst`)

mutool pages

The pages command lists information about each page: MediaBox, CropBox, Rotation, UserUnit, etc.

.. code-block:: bash

text
mutool pages [options] input.pdf [pages]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.

input.pdf
Input file name. Must be a PDF file.

[pages]
Comma-separated list of page numbers and ranges. If no pages are
supplied then all document pages will be considered for the output
file.


Ext Mupdf Docs Tools Mutool Poster (`ext/mupdf/docs/tools/mutool-poster.rst`)

mutool poster

The poster command reads the input PDF file and for each page chops it up into x by y pieces. Each piece becomes its own page in the output PDF file. This makes it possible for each page to be printed upscaled and can then be merged into a large poster.

.. code-block:: bash

text
mutool poster [options] input.pdf [output.pdf]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.

``-m`` margin
	Set the margin (overlap) between pages in points or percent.

``-x`` x decimation factor
	Pieces to horizontally divide each page into.

``-y`` y decimation factor
	Pieces to vertically divide each page into.

``-r``
	Split horizontally from right to left (default splits from left to right).

input.pdf
The input PDF document.

[output.pdf]
The output filename. Defaults to "out.pdf" if not supplied.


Ext Mupdf Docs Tools Mutool Recolor (`ext/mupdf/docs/tools/mutool-recolor.rst`)

mutool recolor

The recolor command changes the colorspaces used in
a PDF file.

.. code-block:: bash

text
mutool recolor [options] file

[options]
Options are as follows:

text
``-c`` colorspace
	The desired colorspace used in the output document: ``gray`` (default), ``rgb`` or ``cmyk``.

``-o`` output file
	Use the specified filename for the output PDF file with altered colorspaces.

``-r``
	Remove any output intents present in the input file.

file
Input file name. The input must be a PDF file.


Ext Mupdf Docs Tools Mutool Run (`ext/mupdf/docs/tools/mutool-run.rst`)

mutool run

The run command executes a Javascript program, which has access to most of the features of the MuPDF library.
The command supports ECMAScript 5 syntax in strict mode.

.. code-block:: bash

text
mutool run script.js [ arguments ... ]

script.js
The Javascript file which you would like to run.

[ arguments ... ]
Any extra arguments you want to pass to the script.
These are available in the global scriptArgs array.

If invoked without any arguments, it will drop you into an interactive REPL (read-eval-print-loop).
To exit call the quit() function or press Ctrl-D.

Environment

Command line arguments are accessible from the global scriptArgs array.
The name of the script is in the global scriptPath variable.

The following functions are only available in the mutool run shell:

require(module)
Load a Javascript module.

repr(value)
Convert any Javascript value to a string.

gc(report)
Run the garbage collector to free up memory. Optionally report statistics on the garbage collection.

load(fileName)
Load and execute script in "fileName".

print(...)
Print arguments to stdout, separated by spaces and followed by a newline.

quit()
Exit the shell.

read(fileName)
Read the contents of a file and return them as a UTF-8 decoded string.

readFile(fileName)
Read the contents from a file and return them as a Buffer.

readline()
Read one line of input from standard input and return it as a string.

write(...)
Print arguments to stdout, separated by spaces.

Node Compatibility

For compatibility with Node the following objects are provided:

process.argv
The command line arguments.

fs.readFileSync
Read a file into a buffer.

fs.writeFileSync
Write a buffer to file.

The following ESM import statements are ignored:

.. code-block::

text
import * as fs from "fs"
import * as mupdf from "mupdf"

If you only use these functions and otherwise stick to ES5 syntax and CommonJS
imports, your scripts should be able to run both with mutool run and Node.


Ext Mupdf Docs Tools Mutool Show (`ext/mupdf/docs/tools/mutool-show.rst`)

mutool show

The show command will print the specified objects and streams to stdout.
Streams are decoded and non-printable characters are represented with a period
by default.

.. code-block::

text
mutool show [options] input.pdf ...

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.
``-o`` output
	The output file name instead of using ``stdout``. Should be a plain text file format.
``-e``
	Leave stream contents in their original form.
``-b``
	Print only stream contents, as raw binary data.
``-g``
	Print only object, one line per object, suitable for grep.
``-r``
	Force repair before showing any objects.
``-L``
	Show object labels (how to reach an object from the root)

input.pdf
Input file name. Must be a PDF file.

...
Specify what to show by using one of the following keywords, or a path to an object:

text
``trailer``
	Print the trailer dictionary.

``xref``
	Print the cross reference table.

``pages``
	List the object numbers for every page.

``grep``
	Print all the objects in the file in a compact one-line format
	suitable for piping to grep.

``outline``
	Print the outline (also known as "table of contents" or
	"bookmarks").

``js``
	Print document level Javascript.

``form``
	Print form objects.

````
	A path starts with either an object number, a property in the
	trailer dictionary, or the keyword "trailer" or "pages".
	Separate elements with a period '.' or slash '/'. Select a page
	object by using pages/N where N is the page number. The first
	page is number 1.

``*``
	You can use ``*`` as an element to iterate over all array
	indices or dictionary properties in an object.

Examples

Find the number of pages in a document:

.. code-block:: bash

text
mutool show input.pdf trailer/Root/Pages/Count

Print the content stream data of the first page:

.. code-block:: bash

text
mutool show -b input.pdf pages/1/Contents

Print the names of all the font resources on all the pages:

.. code-block:: bash

text
mutool show input.pdf pages/*/Resources/Font/*/BaseFont

Show all JPEG compressed stream objects:

.. code-block:: bash

text
mutool show input.pdf grep | grep '/Filter/DCTDecode'

Ext Mupdf Docs Tools Mutool Sign (`ext/mupdf/docs/tools/mutool-sign.rst`)

mutool sign

The sign command reads an input PDF file and by default prints information
about each signature field object. The command applies to the signature field
objects given, or every signature field if none are specified. Given suitable
option signature fields can be verified, cleared or signed using a given
certificate and certificate password.

.. code-block:: bash

text
mutool sign [options] input.pdf [signature object numbers]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.

``-v``
	Verify each signature field and check whether the document has changed since signing.

``-c``
	Revert each signed signature field back to its unsigned state.

``-s`` certificate file
	Sign each signature field with the certificate in the given file.

``-P`` certificate password
	The password used together with the certificate to sign a signature field.

``-o`` filename
	Output PDF file name.

input.pdf
The input PDF document.

[signature object numbers]
Can be used to specify a particular set of signature field objects to apply the sign command to.

Signing

Signing digital signatures in MuPDF requires that you have a PFX certificate.
You can create a self-signed certificate using OpenSSL by following these
steps:

  1. Generate a self-signed certificate and private key:

.. code-block:: bash

text
openssl req -x509 -days 365 -newkey rsa:2048 -keyout cert.pem -out cert.pem
  1. Convert to PFX format:

.. code-block:: bash

text
openssl pkcs12 -export -in cert.pem -out cert.pfx

Examples

The following command should show all signature fields in the PDF whether signed or unsigned:

.. code-block:: bash

text
mutool sign -v unsigned.pdf

Once you know the object number of an unsigned signature field, then do the following:

.. code-block:: bash

text
mutool sign -s certificate.pfx -P password123 -o signed.pdf unsigned.pdf 4242

This assumes that object 4242 0 R is the signature field. Re-running sign -v on signed.pdf should then show that the signature was signed.

To clear a signature use the -c option:

.. code-block:: bash

text
mutool sign -c -o unsigned.pdf signed.pdf 4242

Ext Mupdf Docs Tools Mutool Trace (`ext/mupdf/docs/tools/mutool-trace.rst`)

mutool trace

The trace command prints a trace of all the device calls used to render a page.

.. code-block:: bash

text
mutool trace [options] file [pages]

[options]
Options are as follows:

text
``-p`` password
	Use the specified password if the file is encrypted.
``-b`` box
	Use named page box (``MediaBox``, ``CropBox``, ``BleedBox``, ``TrimBox``, or ``ArtBox``).
``-W`` width
	Page width in points for EPUB layout.
``-H`` height
	Page height in points for EPUB layout.
``-S`` size
	Font size in points for EPUB layout.
``-U`` filename
	User CSS stylesheet for EPUB layout.
``-X``
	Disable document styles for EPUB layout.
``-d``
	Use display list.

file
Input file name. The file can be any of the supported input formats.

[pages]
Comma-separated list of page ranges. The first page is "1", and the
last page is "N". The default is "1-N".

The trace takes the form of an XML document, with the root element being the
document, its children each page, and one page child element for each device
call on that page.

An example trace

.. code-block:: xml

text
<document filename="hello.pdf">

<fill_path winding="nonzero" colorspace="DeviceRGB" color="1 0 0" transform="1 0 0 -1 0 842">
<moveto x="50" y="50"/>
<lineto x="100" y="200"/>
<lineto x="200" y="50"/>
</fill_path>
<fill_text colorspace="DeviceRGB" color="0" transform="1 0 0 -1 0 842">

<g unicode="H" glyph="H" x="50" y="500" />
<g unicode="e" glyph="e" x="122.2" y="500" />
<g unicode="l" glyph="l" x="166.6" y="500" />
<g unicode="l" glyph="l" x="194.4" y="500" />
<g unicode="o" glyph="o" x="222.2" y="500" />
<g unicode="!" glyph="exclam" x="272.2" y="500" />

</fill_text>

</document>

Ext Mupdf Docs Tools Mutool Trim (`ext/mupdf/docs/tools/mutool-trim.rst`)

mutool trim

This command allows you to make a modified version of a PDF with content that falls inside or outside of various "boxes" removed.

See :doc:/tools/mutool-pages for how to list out the boxes used on a page.

.. code-block:: bash

text
mutool trim [options] input.pdf

[options]
Options are as follows:

text
``-b`` box
	Which :term:`page box` to trim to (``mediabox`` (default), ``cropbox``, ``bleedbox``, ``trimbox``, ``artbox``).

``-f``
	Fallback to ``mediabox`` if specified box not available.

``-m`` margins
	Add margins to box (positive for inwards, negative for outwards).

	*<All>* or *<V>,<H>* or *<T>,<R>,,<L>*

``-e``
	Exclude contents of box, rather than include them.

``-o`` filename
	Output file.

Examples

Trim a document by trimming the MediaBox elements with a margin of 200 points inwards:

.. code-block::

text
mutool trim -b mediabox -m 200 -o out.pdf in.pdf

Trim a document by trimming the MediaBox elements with a margin of 20 points from the top & bottom (vertical) and 30 points from the left & right (horizontal):

.. code-block::

text
mutool trim -b mediabox -m 20,30 -o out.pdf in.pdf

Trim a document by trimming the MediaBox elements to a box offset 10 points from the top & right, 50 points from the bottom and 20 points in from the left:

.. code-block::

text
mutool trim -b mediabox -m 10,10,50,20 -o out.pdf in.pdf

Exclude the contents of the ArtBox element:

.. code-block::

text
mutool trim -b artbox -e -o out.pdf in.pdf

Ext Mupdf Docs Tools Mutool (`ext/mupdf/docs/tools/mutool.rst`)

mutool

Run mutool without any options to see a list of available commands.

.. code-block::

text
mutool

Here are a few highlights:

mutool draw
This is the more customizable tool, but also has a more difficult set of command line options.
It is primarily used for rendering a document to image files.

mutool convert
This tool is used for converting documents into other formats, and is easier to use.

mutool show
A tool for displaying the internal objects in a PDF file.

mutool grep
Search text content of documents.

mutool extract
Extract images and embedded font resources.

mutool clean
Rewrite PDF file. Used to fix broken files, or to make a PDF file human editable.

mutool create
Create a new PDF file from a text file with graphics commands.

mutool merge
Merge pages from multiple input files into a new PDF.

mutool poster
Divide pages of a PDF into pieces that can be printed and merged into a large poster.

mutool run
Do anything you can imagine!


Ext Mupdf Docs Cookbook C Example (`ext/mupdf/docs/cookbook/c/example.rst`)

.. highlight:: c

Render Images

This example renders a PDF file to a series of PPM images.

.. literalinclude:: ../../examples/example.c


Ext Mupdf Docs Cookbook C Multi Threaded (`ext/mupdf/docs/cookbook/c/multi-threaded.rst`)

.. highlight:: c

Multi-threaded

This example renders a PDF to PNG images using multiple threads.

.. literalinclude:: ../../examples/multi-threaded.c


Ext Mupdf Docs Cookbook C Storytest (`ext/mupdf/docs/cookbook/c/storytest.rst`)

.. highlight:: c

Stories

The storytest.c example uses the Story API to create a document
using an HTML template and save it as a PDF.

.. literalinclude:: ../../examples/storytest.c


Ext Mupdf Docs Cookbook Javascript Advanced (`ext/mupdf/docs/cookbook/javascript/advanced.rst`)

.. highlight:: javascript

Advanced

Create PDF

.. literalinclude:: ../../examples/pdf-create.js

Create PDF (low level)

.. literalinclude:: ../../examples/pdf-create-lowlevel.js

Merge PDF files

.. literalinclude:: ../../examples/pdf-merge.js

Create PDF with thumbnails

.. literalinclude:: ../../examples/create-thumbnail.js

Use Device to draw graphics

.. literalinclude:: ../../examples/draw-device.js

Trace Device

.. literalinclude:: ../../examples/trace-device.js


Ext Mupdf Docs Cookbook Javascript Basics (`ext/mupdf/docs/cookbook/javascript/basics.md`)

Basics

Loading a PDF

The following example demonstrates how to load a document and then print out the page count.
Ensure you have a PDF file named "test.pdf" alongside this example before running it.

text
import * as mupdf from "mupdf"

var doc = mupdf.Document.openDocument("test.pdf")
console.log(doc.countPages())

Creating a PDF

How to create a new PDF file with a single blank page:

text
import * as mupdf from "mupdf"

var doc = new mupdf.PDFDocument()
doc.insertPage(-1, doc.addPage([0, 0, 595, 842], 0, null, ""))
doc.save("blank.pdf")

Adding an annotation

How to add a simple annotation to a PDF file:

text
import * as mupdf from "mupdf"

var doc = mupdf.Document.openDocument("blank.pdf")

var page = doc.loadPage(0)

var annot = page.createAnnotation("FreeText")
annot.setRect([10, 10, 200, 100])
annot.setContents("Hello, world!")

page.update()

doc.save("annotation.pdf")

Converting a PDF to plain text

text
import * as mupdf from "mupdf"

var doc = mupdf.Document.openDocument("test.pdf")
for (var i = 0; i < doc.countpages(); ++i) {
	var page = doc.loadPage(i)
	var text = page.toStructuredText().asText()
	console.log(text)
}

Converting a PDF to image files

text
import * as fs from "fs"
import * as mupdf from "mupdf"

var doc = mupdf.Document.openDocument("test.pdf")
for (var i = 0; i < doc.countpages(); ++i) {
	var page = doc.loadPage(i)
	var pixmap = page.toPixmap(
		mupdf.Matrix.scale(96 / 72, 96 / 72),
		mupdf.ColorSpace.DeviceRGB
	)
	var buffer = pixmap.asPNG()
	fs.writeFileSync("page" + (i+1) + ".png", buffer)
}

Ext Mupdf Docs Cookbook Javascript Index (`ext/mupdf/docs/cookbook/javascript/index.rst`)

Javascript Examples

.. toctree::
:glob:

text
basics
advanced

Ext Mupdf Docs Reference C Coding Style (`ext/mupdf/docs/reference/c/coding-style.md`)

Coding Style

This is a basic overview of the MuPDF coding style used in the C library.

Indentation

There are two hard rules:

  1. Indent with hard tabs.
  2. Do not vertically align anything beyond the left edge!

If you want a different indentation level, configure your editor to use
whichever tab width you prefer. If you follow the two rules above, everything
will work out perfectly.

The rest you should be able to infer from the source around you:

  • Curly braces on their own line.
  • Put a space between if, for, while, and their expression.

Names

Functions should be named according to one of the following schemes:

  • verb_noun
  • verb_noun_with_noun
  • noun_attribute
  • set_noun_attribute
  • noun_from_noun -- convert from one type to another (avoid noun_to_noun)

Prefixes are mandatory for exported functions, macros, enums, globals and
types.

  • fz for common code
  • pdf, xps, etc., for interpreter specific code

Prefixes are optional (but encouraged) for private functions and types.

Avoid using 'get' as this is a meaningless and redundant filler word.

These words are reserved for reference counting schemes:

  • new, create, find, load, open, keep -- return objects that you are responsible for freeing.
  • drop -- relinquish ownership of the object passed in.

When searching for an object or value, the name used depends on whether
returning the value is passing ownership:

  • lookup -- return a value or borrowed pointer
  • find -- return an object that the caller is responsible for freeing

Types

Various different integer types are used throughout MuPDF.

In general:

  • int is assumed to be 32bit at least.
  • short is assumed to be exactly 16 bits.
  • char is assumed to be exactly 8 bits.
  • array sizes, string lengths, and allocations are measured using size_t.
  • size_t is 32bit in 32bit builds, and 64bit on all 64bit builds.
  • buffers of data use unsigned chars (or uint8_t).
  • Offsets within files/streams are represented using int64_t.

In addition, we use floats (and avoid doubles when possible), assumed to be
IEEE compliant.

Reference counting

Reference counting uses special words in functions to make it easy to remember
and follow the rules.

Words that take ownership: new, find, load, open, keep.

Words that release ownership: drop.

If an object is returned by a function with one of the special words that take
ownership, you are responsible for freeing it by calling "drop" or "free", or
"close" before you return. You may pass ownership of an owned object by return
it only if you name the function using one of the special words.

Any objects returned by functions that do not have any of these special words,
are borrowed and have a limited life time. Do not hold on to them past the
duration of the current function, or stow them away inside structs. If you need
to keep the object for longer than that, you have to either "keep" it or make
your own copy.


Ext Mupdf Docs Reference C Index (`ext/mupdf/docs/reference/c/index.rst`)

C Library

.. toctree::

text
introduction
overview
coding-style
fitz/index
api/index

Ext Mupdf Docs Reference C Introduction (`ext/mupdf/docs/reference/c/introduction.md`)

Introduction

This is the MuPDF C API guide for developers. In this document we will guide
you through the public and stable bits of the MuPDF library. This is intended
to both provide an introduction to new developers wanting to use low level
features of MuPDF, and as a reference guide to the public interface.

We will be explaining the basics, enough to get you started on a single
threaded application. There are more functions and data structures used
internally, and there are also ways to use MuPDF from multiple threads, but
those are beyond the scope of this document.

The functions and structures documented in this document are what we consider
stable APIs. They will rarely change, and if they do, any such changes will be
noted in the "api-changes" document along with instructions for how to update
your code.

Core

The core contains the runtime context, exception handling, and various string
manipulation, math, hash table, binary tree, and other useful functions.

I/O

The I/O module contains structures and functions for buffers of data, reading
and writing to streams, compression, and encryption.

Graphics

The graphics module contains graphic resource objects like colors, fonts,
shadings, and images.

Device

The device interface is how we provide access to the contents of a document. A
device is a callback structure, that gets called for each piece of text, line
art, and image on a page. There are several device implementations. The most
important one renders the contents to a raster image, and another one gathers
all the text into a structure that can be used to select and copy and search
the text content on a page.

Document

The document module handles reading and writing documents in various formats,
and ties together all the preceding modules to provide rendering, format
conversion, and search functionality for the document types we support.

PDF

The PDF module provides access to the low level PDF structure, letting you
query, modify, and create PDF objects and streams. It allows you to create new
documents, modify existing documents, or examine features, extract data, or do
almost anything you could want at the PDF object and stream level that we don't
provide with the higher level APIs.


Ext Mupdf Docs Reference Common Coordinate System (`ext/mupdf/docs/reference/common/coordinate-system.rst`)

Coordinate Systems

In PDF, the origin (0, 0) of a page is located at its bottom-left corner. In
MuPDF, the origin of a page is located at its top-left corner.

.. image:: /images/img-coordinate-space.webp
:width: 75%

The exact coordinate of the page's corner may not always be (0, 0) but is
defined by its CropBox in relation to the MediaBox.

The rotation of the page also affects the PDF to MuPDF coordinate space
relationship; the rotation automatically adjusted for and hidden in the MuPDF
space.

Coordinates are real numbers and measured in points (1/72 of an inch).

The PDF page may override this convention by setting a UserUnit (must be larger than 1/72").

Example

In MuPDF interfaces we use the MuPDF coordinate space.

.. code-block:: javascript

text
var rectangle = [0,0,200,200]

Results in:

.. image:: /images/200x200-rect.webp
:width: 200px

Working at the PDF object level

Sometimes you may want to work directly with the PDF objects without using the MuPDF high level functions.
When doing this, you need to write the PDF coordinates as the PDF file expects them.

.. code-block:: javascript

text
var contents = document.addStream(`
	q			% Save graphics state
	200 0 0 200 10 10 cm	% Scale and translate to position image
	/Image1 Do		% Fill image
	Q			% Restore graphics state
`)

Then in this case we are working in the PDF coordinate space!

Use the PDFPage.prototype.getTransform to get a matrix that can be used to transform between the coordinate spaces.


Ext Mupdf Docs Reference Common Document Writer Options (`ext/mupdf/docs/reference/common/document-writer-options.md`)

Document Writer Options

The options are specified using an option string of key-value pairs.

Supported output formats

Raster formats:
: cbz, png, pnm, pgm, ppm, pam, pbm, pkm

Print-raster formats:
: pcl, pclm, ps, pwg

Vector formats:
: pdf, svg

Text formats:
: html, xhtml, text, stext

Raster output options

rotate=N
: Rotate rendered pages N degrees counterclockwise.

resolution=N
: Set both X and Y resolution in pixels per inch.

x-resolution=N
: X resolution of rendered pages in pixels per inch.

y-resolution=N
: Y resolution of rendered pages in pixels per inch.

width=N
: Render pages to fit N pixels wide (ignore resolution option).

height=N
: Render pages to fit N pixels tall (ignore resolution option).

colorspace=(gray|rgb|cmyk)
: Render using specified colorspace.

alpha
: Render pages with alpha channel and transparent background.

graphics=(aaN|cop|app)
: Set the rasterizer to use for graphics.
- aaN Antialias with N bits (0 to 8).
- cop Center of pixel.
- app Any part of pixel.

text=(aaN|cop|app)
: Set the rasterizer to use for text.
- aaN Antialias with N bits (0 to 8).
- cop Center of pixel.
- app Any part of pixel.

PCL output options

colorspace=mono
: Render 1-bit black and white page.

colorspace=rgb
: Render full color page.

preset
: Possible values are: generic|ljet4|dj500|fs600|lj|lj2|lj3|lj3d|lj4|lj4pl|lj4d|lp2563b|oce9050

spacing=0
: No vertical spacing capability.

spacing=1
: PCL 3 spacing (<ESC>*p+\<n\>Y).

spacing=2
: PCL 4 spacing (<ESC>*b\<n\>Y).

spacing=3
: PCL 5 spacing (<ESC>*b\<n\>Y and clear seed row).

mode2
: Enable mode 2 graphics compression.

mode3
: Enable mode 3 graphics compression.

eog_reset
: End of graphics (<ESC>*rB) resets all parameters.

has_duplex
: Duplex supported (<ESC>&l\<duplex\>S).

has_papersize
: Papersize setting supported (<ESC>&l\<sizecode\>A).

has_copies
: Number of copies supported (<ESC>&l\<copies\>X).

is_ljet4pjl
: Disable/Enable HP 4PJL model-specific output.

is_oce9050
: Disable/Enable Oce 9050 model-specific output.

PCLm output options

compression=none
: No compression (default).

compression=flate
: Flate compression.

strip-height=N
: Strip height (default 16).

PDF output options

See PDF Write Options.

PWG output options

media_class=<string>
: Set the media_class field.

media_color=<string>
: Set the media_color field.

media_type=<string>
: Set the media_type field.

output_type=<string>
: Set the output_type field.

rendering_intent=<string>
: Set the rendering_intent field.

page_size_name=<string>
: Set the page_size_name field.

advance_distance=
: Set the advance_distance field.

advance_media=
: Set the advance_media field.

collate=
: Set the collate field.

cut_media=
: Set the cut_media field.

duplex=
: Set the duplex field.

insert_sheet=
: Set the insert_sheet field.

jog=
: Set the jog field.

leading_edge=
: Set the leading_edge field.

manual_feed=
: Set the manual_feed field.

media_position=
: Set the media_position field.

media_weight=
: Set the media_weight field.

mirror_print=
: Set the mirror_print field.

negative_print=
: Set the negative_print field.

num_copies=
: Set the num_copies field.

orientation=
: Set the orientation field.

output_face_up=
: Set the output_face_up field.

page_size_x=
: Set the page_size_x field.

page_size_y=
: Set the page_size_y field.

separations=
: Set the separations field.

tray_switch=
: Set the tray_switch field.

tumble=
: Set the tumble field.

media_type_num=
: Set the media_type_num field.

compression=
: Set the compression field.

row_count=
: Set the row_count field.

row_feed=
: Set the row_feed field.

row_step=
: Set the row_step field.

SVG output options

text=text
: Emit text as <text> elements (inaccurate fonts).

text=path
: Emit text as \ elements (accurate fonts).

no-reuse-images
: Do not reuse images using <symbol> definitions.

Text output options

See Structured Text Options.


Ext Mupdf Docs Reference Common Glossary (`ext/mupdf/docs/reference/common/glossary.rst`)

Glossary

.. glossary::
:sorted:

text
Alpha
Opacity

	Alpha values are real numbers in the closed interval from 0 to 1,
	where 0 means full transparency, and 1 means full opacity.

Blend Mode

	Blend modes define how a layer of graphics combines with a backdrop.

	Below is an illustration of the visual effect for RGB colors for the blend
	modes defined for PDF.

	For further details refer to the PDF specification's `section
	on blend modes
	<https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G12.12449365>`_.

	.. imagesvg:: ../../images/blendmodes.svg
		:tagtype: object
		:width: 75%

File specification

	In PDF a file specification names a file.

	If a file specification only contains a file name or a path,
	then it represents an external file. Such a file is assumed
	to be found at the specified location in the file system.

	A file specification may also embed the file contents as a
	stream inside the PDF. In this case the file specification
	represents an embedded file. For embedded files, further
	metadata may be stored (e.g. file size, creation and
	modification date).

	For more details read the PDF specification's section on
	`file specifications
	<https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G8.1640832>`_.

Knockout and Isolation

	In a knockout transparency group each element overwrites the prior
	elements in the group instead of compositing with them.

	In an isolated transparency group the group does not composite with
	the group's backdrop, but rather a fully transparent backdrop.

	For further details refer to the PDF specification's sections on
	`isolation <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G12.1689467>`_
	and
	`knockout <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G12.1689478>`_.

	.. imagesvg:: ../../images/knockout-isolated.svg
		:tagtype: object
		:width: 75%

Winding
Non-zero Winding Number Rule
Even-Odd Rule

	These rules in PDF defined what parts of a `Path` are inside and
	outside the curve respectively. This is used to determine what parts
	of a curve should be filled.

	See the PDF specification sections on the
	`Non-zero Winding Number Rule <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G9.1850134>`_
	and
	`Even-Odd Rule <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G9.1850155>`_.

Line Cap Style

	PDFs define three different shapes for the ends of unclosed subpaths:
	butt cap, round cap, and square cap. See below for the visual effect and
	refer to the PDF specifications section about `Line Cap Styles
	<https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G9.1849678>`_
	for details.

	.. imagesvg:: ../../images/line-caps.svg
		:tagtype: object
		:width: 75%

Line Join Style

	PDFs define three different shapes for the joining of two lines in a
	subpath: miter join, round join, and bevel join. See below for the
	visual effects and refer to the PDF specifications section about `Line
	Join Styles <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G9.1849699>`_
	for details.

	.. imagesvg:: ../../images/line-joins.svg
		:tagtype: object
		:width: 50%

Miter Limit

	When mitered line joins are used, then lines joining at sharper and
	sharper angles will produce longer and longer miters. The miter may stick
	out longer than anticipated. Therefore PDF defines a miter limit value,
	at which a longer miter join will be changed to a bevel join. See below
	for the visual effects and refer to the PDF specifications section about
	`Miter Limit
	<https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G9.3859627>`_
	for details.

	.. imagesvg:: ../../images/miter-limit.svg
		:tagtype: object
		:width: 75%

QuadPoint

	A QuadPoint in PDF is a non-axis aligned quadrilateral, used to define
	areas on the page that typically cover text (which may be rotated, or
	skewed). It is given as an array of 8 numbers (four x, y pairs).

	QuadPoints are used with Link and text markup annotations.

	The order of the points is a matter of confusion, because the order
	used in the PDF reference doesn't match the order that Adobe uses.

	This is the order that is typically used:
		``[ ulx uly urx ury llx lly lrx lry ]``

Page Box

	The PDF reference defines several boxes to determine different parts
	of a page. See the chapter on
	`Page Boundaries <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G15.2260711>`_
	for more details.

	MediaBox
		The MediaBox defines the size of the physical medium on
		which the page is to be printed. It includes items that
		will be physically trimmed from the final product like
		crop marks, registration marks, etc.

	CropBox
		The CropBox defines the visible region of the page to
		be displayed or printed. This has no real meaning, but
		is used to clip the page contents when rendering.

	BleedBox
		The BleedBox defines the region to which the page
		contents expect to be clipped. This includes any extra
		bleed area to account for imprecision in the printing
		process.

	TrimBox
		The TrimBox defines the intended dimensions of the
		finished page after trimming.

	ArtBox
		The ArtBox defines the area where it is considered safe
		to place graphical elements.

Standard Structure Type

	The PDF specification defines how a PDF can specify a logical structure
	hierarchy of elements, similar to of HTML or XML. Each of the
	`defined structure elements <https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G15.2259688>`_,
	e.g. Div, BlockQuote, P, H1-H6 etc., are associated with some visual
	content.

Line Ending Style

	Styles used to draw line endings for certain annotations.
	Below is an illustration with the name of each style.
	The example uses black line color, with both none and
	blue interior color.

	.. imagesvg:: ../../images/line-ending-styles.svg
		:tagtype: object
		:width: 75%

Border Style

	Annotations have have two border styles:
		Solid, Dashed

	More fancy borders are defined by the :term:`border effect`.

Border Effect

	Fancier borders can be drawn with a border effect:
		None, Cloudy

Annotation Type

	There are many annotation types defined in the PDF reference.

	MuPDF supports the following types:
		Text, FreeText, Square, Circle, Line, Polygon,
		PolyLine, Highlight, Underline, Squiggly, StrikeOut,
		Redact, Stamp, Caret, Ink, Popup, FileAttachment,
		Redaction

	These types are not supported yet:
		Sound, Movie, RichMedia, Widget, Screen, PrinterMark,
		TrapNet, Watermark, 3D, Projection

Widget Type

	Widgets are a type of annotation.
	There are a few different subtypes:

	Btn
		Pushbutton, Check Boxes, Radio Buttons
	Tx
		Text Fields
	Ch
		Choice Fields (list box, combo box)
	Sig
		Signature Field

Icon Name

	Some annotations appear as an icon.
	The available icons differ per annotation type.

	Text
		Comment,
		Help,
		Insert,
		Key,
		NewParagraph,
		Note,
		Paragraph
	FileAttachment
		Graph,
		PaperClip,
		PushPin,
		Tag
	Sound
		Mic,
		Speaker
	Stamp
		Approved,
		AsIs,
		Confidential,
		Departmental,
		Draft,
		Experimental,
		Expired,
		Final,
		ForComment,
		ForPublicRelease,
		NotApproved,
		NotForPublicRelease,
		Sold,
		TopSecret

MIME-type

	A MIME-type is a string describing the type of data.
	PDF data has the MIME-type "application/pdf", while
	unknown data has the MIME-type
	"application/octet-stream". For further details see the
	specification that the PDF specification references:
	`RFC 2048 - Multipurpose Internet Mail Extensions
	(MIME) Part Two: Media Types
	<https://www.rfc-editor.org/info/rfc2048>`_.

Language code

	Language codes consists of a primary code, followed
	zero or more by subcodes each preceded by a hyphen,
	e.g. "en", "en-US", "kr", "zh-CN", "zh-TW".

	For further details see the PDF specification's section
	on `Language Identifiers
	<https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf#G15.2262093>`_.

Associated File

	TODO

CMYK JPEG

	The situation with JPEG and CMYK colorspaces is complicated, and depends on
	many factors such as App markers, the ColorTransform PDF parameter, and whether
	a JPEG is intended as a standalone or embedded in PDF.

	TODO: explain our behavior, and adobe's behavior, and when impossible situations appear

Ext Mupdf Docs Reference Common Index (`ext/mupdf/docs/reference/common/index.rst`)

Concepts

.. toctree::
:maxdepth: 1

text
glossary
coordinate-system
option-strings
document-writer-options
pdf-write-options
stext-options
search-options
table-hunt-options

Ext Mupdf Docs Reference Common Option Strings (`ext/mupdf/docs/reference/common/option-strings.md`)

Option Strings

Many functions in MuPDF take a generic set of options to customize behavior in
an extensible manner.

  • PDF writer options
  • Generic document writer options
  • Structured text extraction options
  • etc.

These options strings may use any one of the three supported syntaxes to
represent key-value pairs.

Boolean values can be represented in several equivalent ways:

  • true, yes, on, enable, 1
  • false, no, off, disable, 0

An option with an empty value counts as true.

Comma-separated values

Classic syntax with comma-separated key-value pairs.

Values may be enclosed in double quotes to allow embedding commas
and equal signs. To represent a double quote within a quoted value,
use two double quotes in a row.

text
compress,encrypt=aes-256,owner-password="Hello, ""world""!"

URL query string

If the option string starts with a question mark, it will be parsed using URL query string syntax.

text
?compress=true&encrypt=aes-256&owner-password=Hello,%20%22world%22%21

Encode special characters using %HH hexadecimal escapes.

JSON

The string can also be in a subset of JSON -- a single JSON object containing only booleans,
numbers, strings, and arrays of numbers.

text
{"compress":true,"encrypt":"aes-256","owner-password":"Hello, \"world\"!"}

Escape double quotes and backslashes with a backslash.


Ext Mupdf Docs Reference Common Pdf Write Options (`ext/mupdf/docs/reference/common/pdf-write-options.md`)

PDF Write Options

These are the common options to functions that write PDF files.

The options are specified using an option string of key-value pairs.

decompress
: Decompress all streams (except compress-fonts/images)

compress
: Compress all streams. Bi-level images are compressed with CCITT Fax and generic data is compressed with flate.

compress=flate
: Compress streams with Flate (default).

compress=brotli
: Compress streams with Brotli (WARNING: this is a proposed PDF feature)

compress-fonts
: Compress embedded fonts

compress-images
: Compress images

compress-effort=0|percentage
: Effort spent compressing, 0 is default, 100 is max effort

ascii
: ASCII hex encode binary streams

pretty
: Pretty-print objects with indentation

labels
: Print object labels

linearize
: Optimize for web browsers (no longer supported!)

clean
: Pretty-print graphics commands in content streams

sanitize
: Sanitize graphics commands in content streams

garbage
: Garbage collect unused objects

garbage=compact
: Garbage collect unused objects, and compact cross reference table

garbage=deduplicate
: Garbage collect unused objects, compact cross reference tables, and remove duplicate objects

incremental
: Write changes as incremental update

objstms
: Use object streams and cross reference streams

appearance=yes|all
: Synthesize just missing, or all, annotation/widget appearance streams

continue-on-error
: Continue saving the document even if there is an error

decrypt
: This option is deprecated: instead of decrypt use encrypt=none

encrypt=none|keep|rc4-40|rc4-128|aes-128|aes-256
: Write encrypted document (default keep)

permissions=NUMBER
: Document permissions to grant when encrypting

user-password=PASSWORD
: Password required to read document

owner-password=PASSWORD
: Password required to edit document

regenerate-id
: Regenerate document id (default yes)

reproducible
: Attempt to make writes reproducible (default no)


Ext Mupdf Docs Reference Common Search Options (`ext/mupdf/docs/reference/common/search-options.md`)

Search Options

The options are specified using an option string of key-value pairs.

exact
: Search for the given string exactly as written.

regexp
: The search needle is interpreted as a JS regular expression.

ignore-case
: Ignore case differences in search needle and page text.

ignore-diacritics
: Ignore diacritics differences in search needle and page text.

keep-lines
: Keep line endings as \n, otherwise they are transformed into spaces.

keep-paragraphs
: Keep paragraph endings as \n, otherwise they are transformed into spaces.

: Combining keep-lines with keep-paragraphs means that lines end in \n and paragraphs in \n\n.

keep-hyphens
: Normally hyphenated words at the end of a line are joined together. This option disables that behavior, and searches for hyphenated words as they are.


Ext Mupdf Docs Reference Common Stext Options (`ext/mupdf/docs/reference/common/stext-options.md`)

Structured Text Options

The options are specified using an option string of key-value pairs.

preserve-images
: Keep images in output

preserve-ligatures
: Do not expand ligatures into constituent characters

preserve-spans
: Do not merge spans on the same line

preserve-whitespace
: Do not convert all whitespace into space characters

inhibit-spaces
: Don't add spaces between gaps in the text

paragraph-break
: Break blocks at paragraph boundaries

dehyphenate
: Attempt to join up hyphenated words

ignore-actualtext
: Do not apply ActualText replacements

use-cid-for-unknown-unicode
: Use character code if unicode mapping fails

use-gid-for-unknown-unicode
: Use glyph index if unicode mapping fails

accurate-bboxes
: Calculate char bboxes from the outlines

accurate-ascenders
: Calculate ascender/descender from font glyphs

accurate-side-bearings
: Expand character bboxes to completely include width of glyphs

collect-styles
: Attempt to detect text features (fake bold, strikeout, underlined etc)

clip
: Do not include text that is completely clipped

clip-rect=x0:y0:x1:y1
: Specify clipping rectangle within which to collect content

structured
: Collect structure markup

vectors
: Include vector bboxes in output

lazy-vectors
: Delay vectors in the extraction slightly if they would otherwise split an extracted text line

fuzzy-vectors
: Merge abutting horizontal and vertical vectors

segment
: Attempt to segment the page

table-hunt
: Hunt for tables within a (segmented) page

Also, options from the Table Hunt Options set can be included here.


Ext Mupdf Docs Reference Common Table Hunt Options (`ext/mupdf/docs/reference/common/table-hunt-options.md`)

Table Hunt Options

The options are specified using an option string of key-value pairs.

These are currently experimental and subject to change without notice.

vertically-collapse-bordered-cells
: If true, then the contents of any completely bordered cell will be considered as appropriate for collapsing vertically. The default is false.


Ext Mupdf Docs Reference Javascript Functions (`ext/mupdf/docs/reference/javascript/functions.rst`)

.. default-domain:: js

.. highlight:: javascript

Functions

Most functionality is provided by member functions of class objects.
There are just a couple of top level configuration functions listed
here.

.. function:: mupdf.installLoadFontFunction(callback)

text
Install a handler to load system (or missing) fonts.

The callback function will be called with four arguments:

.. code-block::

	callback(fontName, scriptName, isBold, isItalic)

The callback should return either a `Font` object for the requested
font, or ``null`` if an exact match cannot be found (so that the font
loading machinery can keep looking through the chain of fallback
fonts).

.. function:: mupdf.enableICC()

text
Enable ICC-profiles based operation.

.. function:: mupdf.disableICC()

text
Disable ICC-profiles based operation.

.. function:: mupdf.emptyStore()

text
Empty all cached entries from the store.

.. function:: mupdf.shrinkStore(percent)

text
Remove cached entries from the store until it holds less
data than the specified threshold.

If the store was initialized with a limit, the threshold is a
percentage of the limit. If the store is unlimited in size, the
threshold is a percentage of what the store currently holds.

Returns a boolean indicating whether or not the store was able to be
shrunk to below the threshold.

:param number percent:
:returns: boolean

.. function:: mupdf.setUserCSS(stylesheet, useDocumentStyles)

text
Set a style sheet to apply to all reflowable documents.

:param string stylesheet: The CSS text to use.
:param boolean useDocumentStyles:
	Whether to respect the document's own style sheet.

.. function:: mupdf.setLog(logger)

text
Install a custom logging object to catch warnings and errors.

The logger should be an object with separate "warning" and "error" methods.

.. code-block::

	mupdf.setLog({
		error(msg) {
			console.error("MuPDF ERROR: " + msg)
		},
		warning(msg) {
			console.warn("MuPDF WARNING: " + msg)
		},
	})

You can also pass in a single function to be used to report both errors and warnings:

.. code-block::

	mupdf.setLog(console.log)

Ext Mupdf Docs Reference Javascript Index (`ext/mupdf/docs/reference/javascript/index.rst`)

Javascript Library

.. toctree::
:maxdepth: 1

text
introduction
functions
types/index

Ext Mupdf Docs Reference Javascript Introduction (`ext/mupdf/docs/reference/javascript/introduction.rst`)

.. default-domain:: js

Introduction

MuPDF can be used in two ways with Javascript as follows:

  • With mutool run.
  • With mupdf.js <https://mupdfjs.readthedocs.io/en/latest/>_ package for Node and browsers.

Both ways are very similar and here we show code samples and the documentation
for both. However if a function or class is specific to only one environment,
then it is marked with the |only_mutool| or |only_mupdfjs| tags.


Ext Mupdf Docs Reference C Api Index (`ext/mupdf/docs/reference/c/api/index.rst`)

:hide-toc:

C API

.. raw:: html

text
<style>
    .toc-drawer {
        display: none;
    }
    .main .content {
        min-width: calc(100% - 6em) !important;
        padding: 0 3em !important;
    }
</style>

Ext Mupdf Docs Reference C Fitz Colors (`ext/mupdf/docs/reference/c/fitz/colors.md`)

Colors

Colors throughout MuPDF are represented as a float vector associated with a
colorspace object. Colorspaces come in many variants, the most common of
which are Gray, RGB, and CMYK. We also support Indexed (palette), L*a*b,
Separation, DeviceN, and ICC based colorspaces.

text
enum { FZ_MAX_COLORS = 32 };

enum fz_colorspace_type {
	FZ_COLORSPACE_NONE,
	FZ_COLORSPACE_GRAY,
	FZ_COLORSPACE_RGB,
	FZ_COLORSPACE_BGR,
	FZ_COLORSPACE_CMYK,
	FZ_COLORSPACE_LAB,
	FZ_COLORSPACE_INDEXED,
	FZ_COLORSPACE_SEPARATION,
};

struct fz_colorspace_s {
	enum fz_colorspace_type type;
	int n;
	char *name;
	private internal fields
};

fz_colorspace *fz_keep_colorspace(fz_context *ctx, fz_colorspace *colorspace);
void fz_drop_colorspace(fz_context *ctx, fz_colorspace *colorspace);

Most colorspaces have color components between 0 and 1.
The number of components a color uses is given by the 'n' field of the colorspace.
This is at most FZ_MAX_COLORS.

L*a*b colors have a range of 0..100 for the L* and -128..127 for a* and b*.

CMYK, Separation, and DeviceN colorspaces are subtractive colorspaces.
Separation and DeviceN colorspaces also have a tint transform function and a
base colorspace used to represent the colors when rendering to a device that
does not have these specific colorants.

Color Management

MuPDF is usually built with a color management engine, but this can be turned
off at runtime if you need faster performance at the cost of much more
inaccurate color conversions.

text
int fz_enable_icc(fz_context *ctx);
int fz_disable_icc(fz_context *ctx);

The various color conversion functions also take certain parameters that the
color management engine uses, such as rendering intent and overprint control.

text
enum {
	FZ_RI_PERCEPTUAL,
	FZ_RI_RELATIVE_COLORIMETRIC,
	FZ_RI_SATURATION,
	FZ_RI_ABSOLUTE_COLORIMETRIC,
};

typedef struct {
	int ri; // Rendering intent
	int bp; // Black point compensation
	int op;
	int opm;
} fz_color_params;

const fz_color_params fz_default_color_params = { FZ_RI_RELATIVE_COLORIMETRIC, 1, 0, 0 };

Device colorspaces

When you need to define a color, and don't care too much about the calibration,
you can use the Device colorspaces. When MuPDF is built with a color management
engine and ICC is enabled the Gray and RGB colorspaces use an sRGB ICC profile.

text
fz_colorspace *fz_device_gray(fz_context *ctx)
fz_colorspace *fz_device_rgb(fz_context *ctx)
fz_colorspace *fz_device_bgr(fz_context *ctx)
fz_colorspace *fz_device_cmyk(fz_context *ctx)
fz_colorspace *fz_device_lab(fz_context *ctx)

BGR is present to allow you to render to pixmaps that have the RGB components
in a different order, so that the data can be passed directly to the operating
system for drawing without needing yet another conversion step.

Indexed colorspaces

Indexed colors have a range of 0..N where N is one less than the number of
colors in the palette. An indexed colorspace also has a base colorspace, which
is used to define the palette of colors used.

This color space type is only used internally in PDF and some image formats.

text
fz_colorspace *fz_new_indexed_colorspace(fz_context *ctx,
	fz_colorspace *base,
	int high,
	unsigned char *lookup
)

High is the maximum value in the palette; i.e. one less than the number of colors.

The lookup argument is a packed array of color values in the base colorspace,
represented as bytes mapped to the range of 0..255.

ICC colorspaces

You can create ICC colorspaces from a buffer containing the ICC profile.

text
fz_colorspace *fz_new_icc_colorspace(fz_context *ctx,
	enum fz_colorspace_type type,
	int flags,
	const char *name,
	fz_buffer *buf
)

The type argument can be NONE if you want to automatically infer the colorspace type from the profile data. If the type is anything else, then an error will be thrown if the profile does not match the type.

Color converters

If you have a color in one space, you often need it in another (such as when
rendering CMYK images to an RGB screen).

There are several ways to convert colors. The easiest is to call a function,
but if you are converting many colors at once, it will be faster to use a color
converter object.

text
void fz_convert_color(fz_context *ctx,
	fz_colorspace *src_colorspace,
	const float *src_color,
	fz_colorspace *dst_colorspace,
	float *dst_color,
	fz_colorspace *proof_colorspace,
	const fz_color_params params
);

typedef struct {
	void (*convert)(fz_context *ctx, fz_color_converter *cc, const float *src, float *dst);
	private internal fields
} fz_color_converter;

void fz_find_color_converter(fz_context *ctx, fz_color_converter *cc,
	fz_colorspace *src_colorspace,
	fz_colorspace *dst_colorspace,
	fz_colorspace *proof_colorspace,
	fz_color_params params
);

void fz_drop_color_converter(fz_context *ctx, fz_color_converter *cc);

Here is some sample code to do a one-off CMYK to RGB color conversion.

text
float cmyk[4] = { 1, 0, 0, 0 };
float rgb[3];
fz_convert_color(ctx,
	fz_device_cmyk(ctx),
	cmyk,
	fz_device_rgb(ctx),
	rgb,
	NULL,
	fz_default_color_params(ctx)
);

Here is some sample code to do repeated CMYK to RGB color conversions on many
colors using a color converter object.

text
float cmyk[100][4] = { {1,0,0,0}, ...
float rgb[100][3];
fz_color_converter cc;
int i;

fz_find_color_converter(ctx,
	&cc,
	fz_device_cmyk(ctx),
	fz_device_rgb(ctx),
	NULL,
	fz_default_color_params(ctx)
);
for (i = 0; i < 100; ++i)
	cc.convert(ctx, &cc, cmyk[i], rgb[i]);
fz_drop_color_converter(ctx, &cc);

Ext Mupdf Docs Reference C Fitz Context (`ext/mupdf/docs/reference/c/fitz/context.md`)

Context

Almost all functions in the MuPDF library take a fz_context structure as
their first argument. The context is used for many things; primarily it holds
the exception stack for our setjmp based exception handling. It also holds
various caches and auxiliary contexts for font rendering and color management.

Here is the code to create a Fitz context. The first two arguments are used if
you need to use a custom memory allocator, and the third argument is a hint to
much memory the various caches should be allowed to grow. The limit is only a
soft limit. We may exceed it, but will start clearing out stale data to try to
stay below the limit when possible. Setting it to a lower value will prevent
the caches from growing out of hand if you are tight on memory.

text
#include <mupdf/fitz.h>

#include <stdio.h>
#include <stdlib.h>

main()
{
	fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED);
	if (!ctx) {
		fprintf(stderr, "Failed to create a new Fitz context!\n");
		return EXIT_FAILURE;
	}

	... do stuff ...

	fz_drop_context(ctx);
	return EXIT_SUCCESS;
}

Ext Mupdf Docs Reference C Fitz Data Structures (`ext/mupdf/docs/reference/c/fitz/data-structures.md`)

Data Structures

MuPDF has implementations of many generally useful data structures and algorithms
such as hash tables and binary trees.

Hash Table

We have a generic hash table structure with fixed length keys.

The keys and values are not reference counted by the hash table. Callers are responsible for manually taking care of reference counting when inserting and removing values from the table, should that be desired.

fz_hash_table *fz_new_hash_table(fz_context *ctx, int initial_size, int key_length, int lock, void (*drop_value)(fz_context *ctx, void *value));
: The lock parameter should be zero, any other value will result in
unpredictable behavior. The drop_value callback function to the
constructor is only used to release values when the hash table is
destroyed.

void fz_drop_hash_table(fz_context *ctx, fz_hash_table *table);
: Free the hash table and call the drop_value function on all the
values in the table.

void *fz_hash_find(fz_context *ctx, fz_hash_table *table, const void *key);
: Find the value associated with the key. Returns NULL if not found.

void *fz_hash_insert(fz_context *ctx, fz_hash_table *table, const void *key, void *value);
: Insert the value into the hash table. Inserting a duplicate entry will
not overwrite the old value, it will return the old value instead.
Return NULL if the value was inserted for the first time. Does not
reference count the value!

void fz_hash_remove(fz_context *ctx, fz_hash_table *table, const void *key);
: Remove the associated value from the hash table. This will not
reference count the value!

void fz_hash_for_each(fz_context *ctx, fz_hash_table *table, void *state, void (*callback)(fz_context *ctx, void *state, void *key, int key_length, void *value);
: Iterate and call a function for each key-value pair in the table.

Binary Tree

The fz_tree structure is a self-balancing binary tree that maps text strings to values.

void *fz_tree_lookup(fz_context *ctx, fz_tree *node, const char *key);
: Look up an entry in the tree. Returns NULL if not found.

fz_tree *fz_tree_insert(fz_context *ctx, fz_tree *root, const char *key, void *value);
: Insert a new entry into the tree. Do not insert duplicate entries.
Returns the new root object.

void fz_drop_tree(fz_context *ctx, fz_tree *node, void (*dropfunc)(fz_context *ctx, void *value));
: Free the tree and all the values in it.

There is no constructor for this structure, since there is no containing root
structure. Instead, the insert function returns the new root node. Use NULL
for the initial empty tree.

text
fz_tree *tree = NULL;
tree = fz_tree_insert(ctx, tree, "A", my_a_obj);
tree = fz_tree_insert(ctx, tree, "B", my_b_obj);
tree = fz_tree_insert(ctx, tree, "C", my_c_obj);
assert(fz_tree_lookup(ctx, tree, "B") == my_b_obj);

Ext Mupdf Docs Reference C Fitz Error (`ext/mupdf/docs/reference/c/fitz/error.md`)

Errors

MuPDF uses a setjmp based exception handling system. This is encapsulated by
the use of three macros: fz_try, fz_always, and fz_catch. When an error
is raised by fz_throw, or re-raised by fz_rethrow, execution will jump to
the enclosing always/catch block.

All functions you call should be guarded by a fz_try block to catch the
errors, or the program will call exit() on errors. You don't want that.

The fz_always block is optional. It is typically used to free memory or
release resources unconditionally, in both the case when the execution of the
try block succeeds, and when an error occurs.

text
fz_try(ctx) {
	// Do stuff that may throw an exception.
}
fz_always(ctx) {
	// This (optional) block is always executed.
}
fz_catch(ctx) {
	// This block is only executed when recovering from an exception.
}

Since the fz_try macro is based on setjmp, the same conditions that apply
to local variables in the presence of setjmp apply. Any locals written to
inside the try block may be restored to their pre-try state when an error
occurs. We provide a fz_var() macro to guard against this.

In the following example, if we don't guard buf with fz_var, then when an
error occurs the buf local variable might have be reset to its pre-try value
(NULL) and we would leak the memory.

text
char *buf = NULL;

fz_var(buf);

fz_try(ctx) {
	buf = fz_malloc(ctx, 100);
	// Do stuff with buf that may throw an exception.
}
fz_always(ctx) {
	fz_free(ctx, buf);
}
fz_catch(ctx) {
	fz_rethrow(ctx);
}

Carefully note that you should never return from within a fz_try or
fz_always block! Doing so will unbalance the exception stack, and things will
go catastrophically wrong. Instead, it is possible to break out of the fz_try
and fz_always block by using a break statement if you want to exit the block
early without throwing an exception.

Throwing a new exception can be done with fz_throw. Passing an exception
along after having cleaned up in the fz_catch block can be done with
fz_rethrow. fz_throw takes a printf-like formatting string.

text
enum {
	FZ_ERROR_SYSTEM, // fatal out of memory or syscall error
	FZ_ERROR_LIBRARY, // unclassified error from third-party library
	FZ_ERROR_ARGUMENT, // invalid or out-of-range arguments to functions
	FZ_ERROR_LIMIT, // failed because of resource or other hard limits
	FZ_ERROR_UNSUPPORTED, // tried to use an unsupported feature
	FZ_ERROR_FORMAT, // syntax or format errors that are unrecoverable
	FZ_ERROR_SYNTAX, // syntax errors that should be diagnosed and ignored
};

void fz_throw(fz_context *ctx, int error_code, const char *fmt, ...);
void fz_rethrow(fz_context *ctx);

Ext Mupdf Docs Reference C Fitz Index (`ext/mupdf/docs/reference/c/fitz/index.rst`)

Fitz

If you wonder where the prefix "fz" and name Fitz come from, MuPDF originally
started out as a prototype of a new rendering library architecture for
Ghostscript. It was to be a fusion of libart and Ghostscript. History turned
out differently, and the project mutated into a standalone PDF renderer now
called MuPDF. The "fz" prefix for the graphics library and core modules remains
to this day.

.. toctree::
:glob:

text
*

.. TODO
archive.h
band-writer.h
barcode.h
bidi.h
bitmap.h
buffer.h
color.h
compress.h
compressed-buffer.h
config.h
context.h
crypt.h
deskew.h
device.h
display-list.h
document.h
export.h
filter.h
font.h
geometry.h
getopt.h
glyph-cache.h
glyph.h
hash.h
heap-imp.h
heap.h
image.h
json.h
link.h
log.h
outline.h
output-svg.h
output.h
path.h
pixmap.h
pool.h
separation.h
shade.h
store.h
story-writer.h
story.h
stream.h
string-util.h
structured-text.h
system.h
text.h
track-usage.h
transition.h
tree.h
types.h
util.h
version.h
write-pixmap.h
writer.h
xml.h


Ext Mupdf Docs Reference C Fitz Io (`ext/mupdf/docs/reference/c/fitz/io.md`)

Buffers & Streams

Buffers

In order to represent a generic chunks of data we use the fz_buffer structure.

text
typedef struct {
	unsigned char *data;
	size_t len; // current length
	size_t cap; // total capacity
	... reserved internal fields ...
} fz_buffer;

fz_buffer *fz_keep_buffer(fz_context *ctx, fz_buffer *buf);
void fz_drop_buffer(fz_context *ctx, fz_buffer *buf);

There are many ways to create a buffer. Some create a buffer shared immutable data, others with data you can edit, and others by copying or decoding other data.

fz_buffer *fz_new_buffer(fz_context *ctx, size_t capacity);
: Create a new empty buffer, with the given initial capacity.

fz_buffer *fz_new_buffer_from_shared_data(fz_context *ctx, const unsigned char *data, size_t size);
: Create a new buffer wrapping the given data pointer. The data is only referenced, and will not be freed when the buffer is destroyed. The data pointer must not change or disappear while the buffer lives.

fz_buffer *fz_new_buffer_from_copied_data(fz_context *ctx, const unsigned char *data, size_t size);
: Create a buffer containing a copy of the data pointed to.

fz_buffer *fz_new_buffer_from_base64(fz_context *ctx, const char *data, size_t size);
: Create a buffer containing the decoded BASE64 data.

Sometimes you want to create buffers piece by piece by appending strings or other data to it, while dynamically growing the underlying storage.

text
void fz_append_data(fz_context *ctx, fz_buffer *buf, const void *data, size_t len);
void fz_append_string(fz_context *ctx, fz_buffer *buf, const char *string);
void fz_append_byte(fz_context *ctx, fz_buffer *buf, int byte);
void fz_append_rune(fz_context *ctx, fz_buffer *buf, int rune);
void fz_append_int16_be(fz_context *ctx, fz_buffer *buf, int x);
void fz_append_int16_le(fz_context *ctx, fz_buffer *buf, int x);
void fz_append_int32_be(fz_context *ctx, fz_buffer *buf, int x);
void fz_append_int32_le(fz_context *ctx, fz_buffer *buf, int x);
void fz_append_printf(fz_context *ctx, fz_buffer *buffer, const char *fmt, ...);
void fz_append_vprintf(fz_context *ctx, fz_buffer *buffer, const char *fmt, va_list args);

You can also write a bit stream with the following functions. The buffer length always covers all the bits in the buffer, including any unused ones in the last byte, which will always be zero.

void fz_append_bits(fz_context *ctx, fz_buffer *buf, int value, int count);
: Write the lower count bits from value into the buffer.

void fz_append_bits_pad(fz_context *ctx, fz_buffer *buf);
: Write enough zero bits to be byte aligned.

You can use the buffer data as a zero-terminated C string by calling the following function. This will ensure that there is a zero terminator after the last byte and return a pointer to the first byte. This pointer is only borrowed, and should only be used briefly, before the buffer is changed again (which may reallocate or free the data).

const char *fz_string_from_buffer(fz_context *ctx, fz_buffer *buf);
: You can also read and write the contents of a buffer to file.

fz_buffer *fz_read_file(fz_context *ctx, const char *filename);
: Read the contents of a file into a buffer.

void fz_save_buffer(fz_context *ctx, fz_buffer *buf, const char *filename);
: Save the contents of a buffer to a file.

Input Streams

An input stream reads data from a source. Some stream types can decompress and decrypt data, and these streams can be chained together in a pipeline.

text
typedef struct { internal } fz_stream;

fz_stream *fz_keep_stream(fz_context *ctx, fz_stream *stm);
void fz_drop_stream(fz_context *ctx, fz_stream *stm);

fz_stream *fz_open_file(fz_context *ctx, const char *filename);
: Open a stream reading the contents of a file.

fz_stream *fz_open_memory(fz_context *ctx, const unsigned char *data, size_t len);
: Open a stream reading from the data pointer.

fz_stream *fz_open_buffer(fz_context *ctx, fz_buffer *buf);
: Open a stream reading from a buffer.

The basic stream operations you expect are available.

text
int64_t fz_tell(fz_context *ctx, fz_stream *stm);
void fz_seek(fz_context *ctx, fz_stream *stm, int64_t offset, int whence);
size_t fz_read(fz_context *ctx, fz_stream *stm, unsigned char *data, size_t len);
size_t fz_skip(fz_context *ctx, fz_stream *stm, size_t len);

fz_buffer *fz_read_all(fz_context *ctx, fz_stream *stm, size_t initial);
: Read the remaining data into a new buffer.

char *fz_read_line(fz_context *ctx, fz_stream *stm, char *buf, size_t n);
: Behaves like fgets().

text
int fz_read_byte(fz_context *ctx, fz_stream *stm);
int fz_peek_byte(fz_context *ctx, fz_stream *stm);
int fz_is_eof(fz_context *ctx, fz_stream *stm);

You can read binary data one integer at a time. The default is big endian, but LE versions are also provided.

text
uint16_t fz_read_uint16(fz_context *ctx, fz_stream *stm);
uint32_t fz_read_uint24(fz_context *ctx, fz_stream *stm);
uint32_t fz_read_uint32(fz_context *ctx, fz_stream *stm);
uint64_t fz_read_uint64(fz_context *ctx, fz_stream *stm);

uint16_t fz_read_uint16_le(fz_context *ctx, fz_stream *stm);
uint32_t fz_read_uint24_le(fz_context *ctx, fz_stream *stm);
uint32_t fz_read_uint32_le(fz_context *ctx, fz_stream *stm);
uint64_t fz_read_uint64_le(fz_context *ctx, fz_stream *stm);

int16_t fz_read_int16(fz_context *ctx, fz_stream *stm);
int32_t fz_read_int32(fz_context *ctx, fz_stream *stm);
int64_t fz_read_int64(fz_context *ctx, fz_stream *stm);

int16_t fz_read_int16_le(fz_context *ctx, fz_stream *stm);
int32_t fz_read_int32_le(fz_context *ctx, fz_stream *stm);
int64_t fz_read_int64_le(fz_context *ctx, fz_stream *stm);

Reading bit streams is also possible:

text
unsigned int fz_read_bits(fz_context *ctx, fz_stream *stm, int n);
unsigned int fz_read_rbits(fz_context *ctx, fz_stream *stm, int n);
void fz_sync_bits(fz_context *ctx, fz_stream *stm);
int fz_is_eof_bits(fz_context *ctx, fz_stream *stm);

Filters

Various decoding, decompression, and decryption filters can be chained together.

text
fz_stream *fz_open_null_filter(fz_context *ctx, fz_stream *chain, int len, int64_t offset);
fz_stream *fz_open_arc4(fz_context *ctx, fz_stream *chain, unsigned char *key, unsigned keylen);
fz_stream *fz_open_aesd(fz_context *ctx, fz_stream *chain, unsigned char *key, unsigned keylen);
fz_stream *fz_open_a85d(fz_context *ctx, fz_stream *chain);
fz_stream *fz_open_ahxd(fz_context *ctx, fz_stream *chain);
fz_stream *fz_open_rld(fz_context *ctx, fz_stream *chain);
fz_stream *fz_open_flated(fz_context *ctx, fz_stream *chain, int window_bits);

fz_stream *fz_open_dctd(fz_context *ctx, fz_stream *chain,
	int color_transform,
	int invert_cmyk,
	int l2factor,
	fz_stream *jpegtables);

fz_stream *fz_open_faxd(fz_context *ctx, fz_stream *chain,
	int k,
	int end_of_line,
	int encoded_byte_align,
	int columns,
	int rows,
	int end_of_block,
	int black_is_1);

fz_stream *fz_open_lzwd(fz_context *ctx, fz_stream *chain,
	int early_change,
	int min_bits,
	int reverse_bits,
	int old_tiff);

fz_stream *fz_open_predict(fz_context *ctx, fz_stream *chain,
	int predictor,
	int columns,
	int colors,
	int bpc);

Output Streams

Output streams let us write data to a sink, usually a file on disk or a buffer. As with the input streams, output streams can be chained together to compress, encrypt, and encode data.

text
typedef struct { internal } fz_output;

Because output may be buffered in the writer, we need a separate close function to ensure that an output stream is properly flushed and any end of data markers are written. This is separate to the drop function, which just frees data. If a writing operation has succeeded, you need to call close on the output stream before dropping it. If you encounter an error while writing data, you can just drop the stream directly, since we couldn't finish writing it and closing it properly would be irrelevant.

text
void fz_close_output(fz_context *ctx, fz_output *out);
void fz_drop_output(fz_context *ctx, fz_output *out);

Outputs can be created to write to files or buffers. You can also implement your own data sink by providing a state pointer and callback functions.

text
fz_output *fz_new_output_with_path(fz_context *, const char *filename, int append);
fz_output *fz_new_output_with_buffer(fz_context *ctx, fz_buffer *buf);

fz_output *fz_new_output(fz_context *ctx,
	int buffer_size,
	void *state,
	void (*write)(fz_context *ctx, void *state, const void *data, size_t n),
	void (*close)(fz_context *ctx, void *state),
	void (*drop)(fz_context *ctx, void *state));

The usual suspects are available, as well as functions to write integers of various sizes and byte orders.

void fz_seek_output(fz_context *ctx, fz_output *out, int64_t off, int whence);
: Seek to a location in the output. This is not available for all output types.

int64_t fz_tell_output(fz_context *ctx, fz_output *out);
: Tell the current write location of the output stream.

text
void fz_write_data(fz_context *ctx, fz_output *out, const void *data, size_t size);
void fz_write_string(fz_context *ctx, fz_output *out, const char *s);
void fz_write_byte(fz_context *ctx, fz_output *out, unsigned char x);
void fz_write_rune(fz_context *ctx, fz_output *out, int rune);
void fz_write_int16_be(fz_context *ctx, fz_output *out, int x);
void fz_write_int16_le(fz_context *ctx, fz_output *out, int x);
void fz_write_int32_be(fz_context *ctx, fz_output *out, int x);
void fz_write_int32_le(fz_context *ctx, fz_output *out, int x);
void fz_write_printf(fz_context *ctx, fz_output *out, const char *fmt, ...);
void fz_write_vprintf(fz_context *ctx, fz_output *out, const char *fmt, va_list ap);
void fz_write_base64(fz_context *ctx, fz_output *out, const unsigned char *data, size_t size, int newline);

Output streams can be chained together to add encryption, compression, and encoding. Note that these do not take ownership of the chained stream, they only write to it. For example, you can write a header, create a compression filter stream, write some data to the filter to compress the data, close the filter and then keep writing more data to the original stream.

text
fz_output *fz_new_arc4_output(fz_context *ctx, fz_output *chain, unsigned char *key, size_t keylen);
fz_output *fz_new_ascii85_output(fz_context *ctx, fz_output *chain);
fz_output *fz_new_asciihex_output(fz_context *ctx, fz_output *chain);
fz_output *fz_new_deflate_output(fz_context *ctx, fz_output *chain, int effort, int no_header);
fz_output *fz_new_rle_output(fz_context *ctx, fz_output *chain);

File Archives

The archive structure is a read-only collection of files. This is typically a Zip file or directory on disk, but other formats are also supported.

text
typedef struct { internal } fz_archive;

void fz_drop_archive(fz_context *ctx, fz_archive *arch);

int fz_is_directory(fz_context *ctx, const char *path);

fz_archive *fz_open_directory(fz_context *ctx, const char *path);
fz_archive *fz_open_archive(fz_context *ctx, const char *filename);
fz_archive *fz_open_archive_with_stream(fz_context *ctx, fz_stream *file);

int fz_count_archive_entries(fz_context *ctx, fz_archive *arch);
const char *fz_list_archive_entry(fz_context *ctx, fz_archive *arch, int idx);

int fz_has_archive_entry(fz_context *ctx, fz_archive *arch, const char *name);
fz_stream *fz_open_archive_entry(fz_context *ctx, fz_archive *arch, const char *name);
fz_buffer *fz_read_archive_entry(fz_context *ctx, fz_archive *arch, const char *name);

We can also create new Zip archives.

text
typedef struct { internal } fz_zip_writer;

fz_zip_writer *fz_new_zip_writer(fz_context *ctx, const char *filename);
fz_zip_writer *fz_new_zip_writer_with_output(fz_context *ctx, fz_output *out);
void fz_write_zip_entry(fz_context *ctx, fz_zip_writer *zip, const char *name, fz_buffer *buf, int compress);
void fz_close_zip_writer(fz_context *ctx, fz_zip_writer *zip);
void fz_drop_zip_writer(fz_context *ctx, fz_zip_writer *zip);

Ext Mupdf Docs Reference C Fitz Math (`ext/mupdf/docs/reference/c/fitz/math.md`)

Math

We obviously need to deal with lots of points, rectangles, and transformations
in MuPDF.

fz_point

Points are fairly self evident. The fz_make_point utility function is for use
with Visual Studio that doesn't yet support the C99 struct initializer syntax.

text
typedef struct {
	float x, y;
} fz_point;

fz_point fz_make_point(float x, float y);

fz_rect and fz_irect

Rectangles are represented by two pairs of coordinates. The x0, y0 pair
have the smallest values, and in the normal coordinate space used by MuPDF that
is the upper left corner. The x1, y1 pair have the largest values,
typically the lower right corner.

In order to represent an infinite unbounded area, we use an x0 that is larger
than the x1.

text
typedef struct {
	float x0, y0;
	float x1, y1;
} fz_rect;

const fz_rect fz_infinite_rect = { 1, 1, -1, -1 };
const fz_rect fz_empty_rect = { 0, 0, 0, 0 };
const fz_rect fz_unit_rect = { 0, 0, 1, 1 };

fz_rect fz_make_rect(float x0, float y0, float x1, float y1);

fz_matrix

Our matrix structure is a row-major 3x3 matrix with the last column always
[ 0 0 1 ]. This is represented as a struct with six fields, in the same order as
in PDF and Postscript. The identity matrix is a global constant, for easy
access.

text
/ a b 0 \
| c d 0 |
\ e f 1 /

typedef struct {
	float a, b, c, d, e, f;
} fz_matrix;

const fz_matrix fz_identity = { 1, 0, 0, 1, 0, 0 };

fz_matrix fz_make_matrix(float a, float b, float c, float d, float e, float f);

fz_quad

Sometimes we need to represent a non-axis aligned rectangular-ish area, such as
the area covered by some rotated text. For this we use a quad representation,
using a points for each of the upper/lower/left/right corners as seen from the
reading direction of the text represented.

text
typedef struct {
	fz_point ul, ur, ll, lr;
} fz_quad;

List of math functions

These are simple mathematical operations that can not throw errors, so do not need a context argument.

float fz_abs(float f)
: Abs for float.

float fz_min(float a, float b)
: Min for float.

float fz_max(float a, float b)
: Max for float.

float fz_clamp(float f, float min, float max)
: Clamp for float.

int fz_absi(int i)
: Abs for integer.

int fz_mini(int a, int b)
: Min for integer.

int fz_maxi(int a, int b)
: Max for integer.

int fz_clampi(int i, int min, int max)
: Clamp for integer.

int fz_is_empty_rect(fz_rect r)
: Returns whether the supplied fz_rect is empty.

int fz_is_infinite_rect(fz_rect r)
: Returns whether the supplied fz_rect is infinite.

fz_matrix fz_concat(fz_matrix left, fz_matrix right)
: Concat two matrices and returns a new matrix.

fz_matrix fz_scale(float sx, float sy)
: Scale.

fz_matrix fz_shear(float sx, float sy)
: Shear.

fz_matrix fz_rotate(float degrees)
: Rotate.

fz_matrix fz_translate(float tx, float ty)
: Translate.

fz_matrix fz_invert_matrix(fz_matrix matrix)
: Invert a matrix.

fz_point fz_transform_point(fz_point point, fz_matrix m)
: Transform a point according to the given matrix.

fz_point fz_transform_vector(fz_point vector, fz_matrix m)
: Transform a vector according to the given matrix (ignores translation).

fz_rect fz_transform_rect(fz_rect rect, fz_matrix m)
: Transform a fz_rect according to the given matrix.

fz_quad fz_transform_quad(fz_quad q, fz_matrix m)
: Transform a fz_quad according to the given matrix.

int fz_is_point_inside_rect(fz_point p, fz_rect r)
: Returns whether the point is inside the supplied fz_rect.

int fz_is_point_inside_quad(fz_point p, fz_quad q)
: Returns whether the point is inside the supplied fz_quad.

fz_matrix fz_transform_page(fz_rect mediabox, float resolution, float rotate)
: Create a transform matrix to draw a page at a given resolution and
rotation. The scaling factors are adjusted so that the page covers a
whole number of pixels. Resolution is given in dots per inch. Rotation
is expressed in 90 degree increments (0, 90, 180, and 270 are
valid values).


Ext Mupdf Docs Reference C Fitz Memory (`ext/mupdf/docs/reference/c/fitz/memory.md`)

Memory

All memory in Fitz is allocated through an allocator, which allows you to use
a custom allocator instead of the system malloc if you need to.

Malloc & Free

You should not need to do raw memory allocation using the Fitz context, but if
you do, here are the functions you need. These work just like the regular C
functions, but take a Fitz context and throw an exception if the allocation
fails. They will not return NULL; either they will succeed or they will
throw an exception.

text
void *fz_malloc(fz_context *ctx, size_t size);
void *fz_realloc(fz_context *ctx, void *old, size_t size);
void *fz_calloc(fz_context *ctx, size_t count, size_t size);
void fz_free(fz_context *ctx, void *ptr);

There are also some macros that allocate structures and arrays, together with a type cast to catch typing errors.

text
T *fz_malloc_struct(fz_context *ctx, T); // Allocate and zero the memory.
T *fz_malloc_array(fz_context *ctx, size_t count, T); // Allocate uninitialized memory!
T *fz_realloc_array(fz_context *ctx, T *old, size_t count, T);

In the rare case that you need an allocation that returns NULL on failure,
there are variants for that too: fz_malloc_no_throw, etc.

Pool Allocator

The pool allocator is used for allocating many small objects that live and die
together. All objects allocated from the pool will be freed when the pool is
freed.

text
fz_pool *fz_new_pool(fz_context *ctx);
void *fz_pool_alloc(fz_context *ctx, fz_pool *pool, size_t size);
char *fz_pool_strdup(fz_context *ctx, fz_pool *pool, const char *s);
void fz_drop_pool(fz_context *ctx, fz_pool *pool);

Reference Counting

Most objects in MuPDF use reference counting to keep track of when they are no
longer used and can be freed. We use the verbs "keep" and "drop" to increment
and decrement the reference count. For simplicity, we also use the word "drop"
for non-reference counted objects (so that in case we change our minds and
decide to add reference counting to an object, the code that uses it need not
change).


Ext Mupdf Docs Reference C Fitz Pixmap (`ext/mupdf/docs/reference/c/fitz/pixmap.md`)

Pixmaps

A pixmap is an 8-bit per component raster image. Each pixel is packed with
process colorants, spot colors, and alpha channel in that order. If an alpha
channel is present, the process colorants are pre-multiplied with the alpha
value.

text
typedef struct {
	int w, h; // Width and height
	int x, y; // X and Y offset
	int n; // Number of components in total (colors + spots + alpha)
	int s; // Number of components that are spot colors
	int alpha; // True if alpha channel is present
	int stride; // Number of bytes per row
	int xres, yres; // Resolution in dots per inch.
	fz_colorspace *colorspace; // Colorspace of samples, or NULL if alpha only pixmap.
	unsigned char *samples;
	private internal fields
} fz_pixmap;

fz_pixmap *fz_keep_pixmap(fz_context *ctx, fz_pixmap *pix);
void fz_drop_pixmap(fz_context *ctx, fz_pixmap *pix);

There are too many pixmap constructors. Here is the only one you should need.

text
fz_pixmap *fz_new_pixmap(fz_context *ctx, fz_colorspace *cs, int w, int h, fz_separations *seps, int alpha);

A newly created pixmap has uninitialized data. The samples must either be
cleared or overwritten with existing data before the pixmap can be safely used.

void fz_clear_pixmap(fz_context *ctx, fz_pixmap *pix);
: Clear the pixmap to black.

void fz_clear_pixmap_with_value(fz_context *ctx, fz_pixmap *pix, int value);
: Clear the pixmap to a grayscale value 0..255, where 0 is black and
255 is white. The value is automatically inverted for subtractive
colorspaces.

void fz_fill_pixmap_with_color(fz_context *ctx, fz_pixmap *pix, fz_colorspace *colorspace, float *color, fz_color_params color_params);
: Fill the pixmap with a solid color.

void fz_unpack_tile(fz_context *ctx, fz_pixmap *dst, unsigned char *src, int n, int depth, size_t stride, int scale);
: Unpack pixel values from source data to fill in the pixmap samples. n
is the number of samples per pixel, depth is the bit depth (1, 2,
4, 8, 16, 24, or 32), stride is the number of bytes per
row. If scale is non-zero, it is the scaling factor to apply to the
input samples to map them to the 8-bpc pixmap range. Pass 1 to the
scale for indexed images, and 0 for everything else. If there are
more components in the source data than the destination, they will be
dropped. If there are fewer components in the source data, the pixmap
will be padded with 255.

Some functions can create a pixmap and initialize its samples in one go:

.. code-block:: c

text
fz_pixmap *fz_new_pixmap_from_8bpp_data(fz_context *ctx, int x, int y, int w, int h, unsigned char *data, int stride);
fz_pixmap *fz_new_pixmap_from_1bpp_data(fz_context *ctx, int x, int y, int w, int h, unsigned char *data, int stride);

Pixmaps can be tinted, inverted, scaled, gamma corrected, and converted to other colorspaces.

void fz_invert_pixmap(fz_context *ctx, fz_pixmap *pix);
: Invert the pixmap samples.

void fz_tint_pixmap(fz_context *ctx, fz_pixmap *pix, int black, int white);
: Map black to black and white to white. The black and white colors are
represented as a packed RGB integer. 0xFFFFFF is white, 0xFF0000 is
red, and 0x000000 is black.

void fz_gamma_pixmap(fz_context *ctx, fz_pixmap *pix, float gamma);
: Apply a gamma correction curve on the samples. A typical use is to
adjust the gamma curve on an inverted image by applying a correction
factor of 1/1.4.

fz_pixmap *fz_convert_pixmap(fz_context *ctx, fz_pixmap *source_pixmap, fz_colorspace *destination_colorspace, fz_colorspace *proof_colorspace, fz_default_colorspaces *default_cs, fz_color_params color_params, int keep_alpha);
: Convert the source pixmap into the destination colorspace. Pass NULL
for the default_cs parameter.

fz_pixmap *fz_scale_pixmap(fz_context *ctx, fz_pixmap *src, float x, float y, float w, float h, const fz_irect *clip);
: Scale the pixmap up or down in size to fit the rectangle. Will return
NULL if the scaling factors are out of range. This applies fancy
filtering and will anti-alias the edges for subpixel positioning if
using non-integer coordinates. If the clip rectangle is set, the
returned pixmap may be subset to fit the clip rectangle. Pass NULL to
the clip if you want the whole pixmap scaled.


Ext Mupdf Docs Reference C Fitz Strings (`ext/mupdf/docs/reference/c/fitz/strings.md`)

Strings

All text strings in MuPDF use the UTF-8 encoding.

Unicode

The following functions encode and decode UTF-8 characters, and return the
number of bytes used by the UTF-8 character (at most FZ_UTFMAX).

text
int fz_chartorune(int *rune, const char *str);
int fz_runetochar(char *str, int rune);

Locale Independent

Since many of the C string functions are locale dependent, we also provide our
own locale independent versions of these functions. We also have a couple of
semi-standard functions like strsep and strlcpy that we can't rely on the
system providing. These should be pretty self explanatory:

text
char *fz_strdup(fz_context *ctx, const char *s);
float fz_strtof(const char *s, char **es);
char *fz_strsep(char **stringp, const char *delim);
size_t fz_strlcpy(char *dst, const char *src, size_t n);
size_t fz_strlcat(char *dst, const char *src, size_t n);
void *fz_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen);
int fz_strcasecmp(const char *a, const char *b);

There are also a couple of functions to process filenames and URLs:

char *fz_cleanname(char *path);
: Rewrite path in-place to the shortest string that names the same path.
Eliminates multiple and trailing slashes, and interprets "." and "..".

void fz_dirname(char *dir, const char *path, size_t dir_size);
: Extract the directory component from a path.

char *fz_urldecode(char *url);
: Decode URL escapes in-place.

Formatting

Our printf family handles the common printf formatting characters, with a
few minor differences. We also support several non-standard formatting
characters. The same printf syntax is used in the printf functions in the
I/O module as well.

text
size_t fz_vsnprintf(char *buffer, size_t space, const char *fmt, va_list args);
size_t fz_snprintf(char *buffer, size_t space, const char *fmt, ...);
char *fz_asprintf(fz_context *ctx, const char *fmt, ...);
char *fz_pool_asprintf(fz_context *ctx, fz_pool *pool, const char *fmt, ...);

%%, %c, %e, %f, %p, %x, %d, %u, %s
: These behave as usual, but only take padding (+,0,space), width, and precision arguments.

%g float
: Prints the float in the shortest possible format that won't lose precision, except NaN to 0, +Inf to FLT_MAX, -Inf to -FLT_MAX.

%M fz_matrix*
: Prints all 6 coefficients in the matrix as %g separated by spaces.

%R fz_rect*
: Prints all x0, y0, x1, y1 in the rectangle as %g separated by spaces.

%P fz_point*
: Prints x, y in the point as %g separated by spaces.

%C int
: Formats character as UTF-8. Useful to print unicode text.

%q char*
: Formats string using double quotes and C escapes.

%( char*
: Formats string using parenthesis quotes and Postscript escapes.

%n char*
: Formats string using prefix / and PDF name hex-escapes.


Ext Mupdf Docs Reference C Fitz Xml (`ext/mupdf/docs/reference/c/fitz/xml.md`)

XML Parser

We have a rudimentary XML parser that handles well formed XML. It does not do
any namespace processing, and it does not validate the XML syntax.

The parser supports UTF-8, UTF-16, iso-8859-1, iso-8859-7, koi8,
windows-1250, windows-1251, and windows-1252 encoded input.

If preserve_white is false, we will discard all whitespace-only text
elements. This is useful for parsing non-text documents such as XPS and SVG.
Preserving whitespace is useful for parsing XHTML.

text
typedef struct { opaque } fz_xml_doc;
typedef struct { opaque } fz_xml;

fz_xml_doc *fz_parse_xml(fz_context *ctx, fz_buffer *buf, int preserve_white);
void fz_drop_xml(fz_context *ctx, fz_xml_doc *xml);
fz_xml *fz_xml_root(fz_xml_doc *xml);

fz_xml *fz_xml_prev(fz_xml *item);
fz_xml *fz_xml_next(fz_xml *item);
fz_xml *fz_xml_up(fz_xml *item);
fz_xml *fz_xml_down(fz_xml *item);

int fz_xml_is_tag(fz_xml *item, const char *name);
: Returns true if the element is a tag with the given name.

char *fz_xml_tag(fz_xml *item);
: Returns the tag name if the element is a tag, otherwise NULL.

char *fz_xml_att(fz_xml *item, const char *att);
: Returns the value of the tag element's attribute, or NULL if not a tag or missing.

char *fz_xml_text(fz_xml *item);
: Returns the UTF-8 text of the text element, or NULL if not a text element.

fz_xml *fz_xml_find(fz_xml *item, const char *tag);
: Find the next element with the given tag name. Returns the element
itself if it matches, or the first sibling if it doesn't. Returns
NULL if there is no sibling with that tag name.

fz_xml *fz_xml_find_next(fz_xml *item, const char *tag);
: Find the next sibling element with the given tag name, or NULL if none.

fz_xml *fz_xml_find_down(fz_xml *item, const char *tag);
: Find the first child element with the given tag name, or NULL if none.


Ext Mupdf Docs Reference Javascript Types Archive (`ext/mupdf/docs/reference/javascript/types/Archive.rst`)

.. default-domain:: js

.. highlight:: javascript

Archive

|only_mutool|

Constructors

.. class:: Archive(path)

text
Create a new archive based either on a tar- or zip-file or the contents of a directory.

:param string path: Path string to the archive file or directory.

.. code-block::

	var archive1 = new mupdf.Archive("example1.zip")
	var archive2 = new mupdf.Archive("example2.tar")
	var archive3 = new mupdf.Archive("images/")

.. class:: Archive(buffer)

text
Create a new archive based either on a tar- or zip-file contained in a buffer.

:param Buffer | ArrayBuffer | Uint8Array | string buffer: Buffer containing a archive.

.. code-block::

	var archive1 = new mupdf.Archive(fs.readFileSync("example1.zip"))
	var archive2 = new mupdf.Archive(fs.readFileSync("example1.tar"))

Instance methods

.. method:: Archive.prototype.getFormat()

text
Returns a string describing the archive format.

:returns: string

.. code-block::

	var archive = new mupdf.Archive("example1.zip")
	print(archive.getFormat())

.. method:: Archive.prototype.countEntries()

text
Returns the number of entries in the archive.

:returns: number

.. code-block::

	var archive = new mupdf.Archive("example1.zip")
	var numEntries = archive.countEntries()

.. method:: Archive.prototype.listEntry(idx)

text
Returns the name of entry number idx in the archive, or null if idx is
out of range.

:param number idx: Entry index.

:returns: string | null

.. code-block::

	var archive = new mupdf.Archive("example1.zip")
	var entry = archive.listEntry(0)

.. method:: Archive.prototype.hasEntry(name)

text
Returns true if an entry of the given name exists in the archive.

:param string name: Entry name to look for.

:returns: boolean

.. code-block::

	var archive = new mupdf.Archive("example1.zip")
	var hasEntry = archive.hasEntry("file1.txt")

.. method:: Archive.prototype.readEntry(name)

text
Returns the contents of the entry of the given name.

:param string name: Name of entry to look for.

:returns: `Buffer`

.. code-block::

	var archive = new mupdf.Archive("example1.zip")
	var contents = archive.readEntry("file1.txt")

Examples

.. code-block::

text
var archive = new mupdf.Archive("example1.zip")
	var n = archive.countEntries()
	for (var i = 0; i < n; ++i) {
		var entry = archive.listEntry(i)
		var contents = archive.readEntry(entry)
		console.log("entry", entry, contents.length)
	}

Ext Mupdf Docs Reference Javascript Types Buffer (`ext/mupdf/docs/reference/javascript/types/Buffer.rst`)

.. default-domain:: js

.. highlight:: javascript

Buffer

Buffer objects are used for working with binary data, e.g. returned from
:func:asJPEG. They can be used much like arrays, but are much more
efficient since they only store bytes.

Constructors

.. class:: Buffer(data)

text
Creates a new Buffer initialized with contents from data.

:param Buffer | ArrayBuffer | Uint8Array | string | null data: Contents used to populate buffer, or leave it empty if ``null``.

.. code-block::

	var buffer = new mupdf.Buffer("hello world")

Instance properties

.. attribute:: Buffer.prototype.length

text
The number of bytes in this buffer (read-only).

:throws: TypeError on attempted writes.

:returns: number

.. attribute:: Buffer.prototype.[n]

text
|only_mutool|

Get or set the byte at index ``n``.

See `readByte()` and `writeByte()` for the equivalent in mupdf.js.

:throws: RangeError on out of bounds accesses.

.. code-block::

	var byte = buffer[0]

Instance methods

.. method:: Buffer.prototype.asString()

text
Returns the contents of this buffer as a string.

:returns: string

.. code-block::

	var str = buffer.asString()

.. method:: Buffer.prototype.asUint8Array()

text
|only_mupdfjs|

Returns the contents of this buffer as a Uint8Array.

:returns: Uint8Array

.. code-block::

	var arr = buffer.asUint8Array()

.. method:: Buffer.prototype.getLength()

text
Returns the number of bytes in this buffer.

:returns: number

.. code-block::

	var length = buffer.getLength()

.. method:: Buffer.prototype.readByte(at)

text
Returns the byte at the specified index ``at`` if within ``0 <= at < getLength()``. Otherwise returns ``undefined``.

:param number at: Index to read byte at.

:returns: number

.. code-block::

	buffer.readByte(0)

.. method:: Buffer.prototype.slice(start, end)

text
Create a new buffer containing a (subset of) the data in this buffer.
Start and end are offsets from the beginning of this buffer, and if negative from the end of this buffer.
If ``start`` points to the end of this buffer, or if ``end`` point to at or before ``start``, then an empty buffer will be returned.

:param number start: Start index.
:param number end: End index (optional).

:returns: `Buffer`

.. code-block::

	var buffer = new mupdf.Buffer()
	buffer.write("hello world") // buffer contains "hello world"
	var newBuffer = buffer.slice(1, -1) // newBuffer contains "ello worl"

.. method:: Buffer.prototype.write(str)

text
Append the string as UTF-8 to the end of this buffer.

:param string str: String to append.

.. code-block::

	buffer.write("hello world")

.. method:: Buffer.prototype.writeBuffer(data)

text
Append the contents of the ``data`` buffer to the end of this buffer.

:param Buffer | ArrayBuffer | Uint8Array | string data: Data buffer to append.

.. code-block::

	buffer.writeBuffer(anotherBuffer)

.. method:: Buffer.prototype.writeByte(byte)

text
Append a single byte to the end of this buffer.
Only the least significant 8 bits of the value are appended.

:param number byte: The byte value to append.

.. code-block::

	buffer.writeByte(0x2a)

.. method:: Buffer.prototype.writeLine(str)

text
Append string to the end of this buffer ending with a newline.

:param string str: String to append.

.. code-block::

	buffer.writeLine("a line")

.. method:: Buffer.prototype.writeRune(c)

text
|only_mutool|

Encode a unicode character as UTF-8 and append to the end of
the buffer.

:param number c: The character unicode codepoint.

.. code-block:: javascript

	buffer.writeRune(0x4f60) // To append U+4f60, 你
	buffer.writeRune(0x597d) // To append U+597d, 好
	buffer.writeRune(0xff01) // To append U+ff01, !

.. method:: Buffer.prototype.save(filename)

text
Write the contents of the buffer to a file.

:param string filename: Filename to save to.

.. code-block:: javascript

	buffer.save("buffer.dat")

Ext Mupdf Docs Reference Javascript Types Color (`ext/mupdf/docs/reference/javascript/types/Color.rst`)

.. default-domain:: js

.. highlight:: javascript

Color

Colors are specified as arrays with the appropriate number of components for
the associated ColorSpace. Each component value is stored as floating
point value between 0 and 1, where 0 means no colorant and 1
means maximum colorant.

For example:

  • In the ColorSpace.DeviceCMYK color space the components are [Cyan, Magenta, Yellow, Black]. A full intensity magenta color would therefore be [0, 1, 0, 0].
  • In the ColorSpace.DeviceRGB color space the components are [Red, Green, Blue]. A full intensity green color would therefore be [0, 1, 0].
  • In the ColorSpace.DeviceGray color space the components are [Black]. A full intensity black color would therefore be [0].

In TypeScript this is defined as follows:

.. code-block::

text
type Color = [number] | [number, number, number] | [number, number, number, number]

Ext Mupdf Docs Reference Javascript Types ColorSpace (`ext/mupdf/docs/reference/javascript/types/ColorSpace.rst`)

.. default-domain:: js

.. highlight:: javascript

ColorSpace

Defines what color system is used for an object, and which colorants exist.

For a Pixmap the interface Pixmap.prototype.getColorSpace() returns its colorspace,
which defines what colorants make up the pixel colors in the Pixmap and
how those colorants are stored. If that colorspace is e.g.,
ColorSpace.DeviceRGB, then the colorants red, green and
blue are combined to make up the color of each pixel.

Constructors

.. class:: ColorSpace(from, name)

text
Create a new ColorSpace from an ICC profile.

:param Buffer | ArrayBuffer | Uint8Array | string from: A buffer containing an ICC profile.
:param string name: A user descriptive name for the new colorspace.

.. code-block::

	var icc_colorspace = new mupdf.ColorSpace(fs.readFileSync("SWOP.icc"), "SWOP")

Static properties

.. data:: ColorSpace.DeviceGray

text
The default Grayscale colorspace

.. data:: ColorSpace.DeviceRGB

text
The default RGB colorspace

.. data:: ColorSpace.DeviceBGR

text
The default RGB colorspace, but with components in reverse order

.. data:: ColorSpace.DeviceCMYK

text
The default CMYK colorspace

.. data:: ColorSpace.Lab

text
The default Lab colorspace

Instance methods

.. method:: ColorSpace.prototype.getName()

text
Return the name of this colorspace.

.. method:: ColorSpace.prototype.getNumberOfComponents()

text
A Grayscale colorspace has one component, RGB has 3, CMYK has 4, and DeviceN may have any number of components.

:returns: number of components

.. code-block::

	var cs = mupdf.ColorSpace.DeviceRGB
	var num = cs.getNumberOfComponents() // 3

.. method:: ColorSpace.prototype.getType()

text
Returns a string indicating the type of this colorspace, one of:

:returns: ``"None" | "Gray" | "RGB" | "BGR" | "CMYK" | "Lab" | "Indexed" | "Separation"``

.. method:: ColorSpace.prototype.isCMYK()

text
Returns ``true`` if the object is a CMYK color space.

:returns: boolean

.. code-block::

	var bool = colorSpace.isCMYK()

.. method:: ColorSpace.prototype.isDeviceN()

text
Returns ``true`` if the object is a Device N color space.

:returns: boolean

.. code-block::

	var bool = colorSpace.isDeviceN()

.. method:: ColorSpace.prototype.isGray()

text
Returns ``true`` if the object is a gray color space.

:returns: boolean

.. code-block::

	var bool = colorSpace.isGray()

.. method:: ColorSpace.prototype.isIndexed()

text
Returns ``true`` if the object is an Indexed color space.

:returns: boolean

.. code-block::

	var bool = colorSpace.isIndexed()

.. method:: ColorSpace.prototype.isLab()

text
Returns ``true`` if the object is a Lab color space.

:returns: boolean

.. code-block::

	var bool = colorSpace.isLab()

.. method:: ColorSpace.prototype.isRGB()

text
Returns ``true`` if the object is an RGB color space.

:returns: boolean

.. code-block::

	var bool = colorSpace.isRGB()

.. method:: ColorSpace.prototype.isSubtractive()

text
Returns ``true`` if the object is a subtractive color space.

:returns: boolean

.. code-block::

	var bool = colorSpace.isSubtractive()

.. method:: ColorSpace.prototype.toString()

text
Return name of this colorspace.

:returns: string

.. code-block::

	var cs = mupdf.ColorSpace.DeviceRGB
	var name = cs.toString() // "[ColorSpace DeviceRGB]"

Ext Mupdf Docs Reference Javascript Types DOM (`ext/mupdf/docs/reference/javascript/types/DOM.rst`)

.. default-domain:: js

.. highlight:: javascript

DOM

|only_mutool|

This represents an HTML or an DOM node. It is a helper class intended to
access the DOM (Document Object Model) content of a Story
object.

Constructors

.. class:: DOM

text
|no_new|

Instances of this class are returned by Story.prototype.document().

Instance methods

.. method:: DOM.prototype.body()

text
Return a DOM for the body element.

:returns: `DOM`

.. code-block::

	var result = xml.body()

.. method:: DOM.prototype.documentElement()

text
Return a DOM for the top level element.

:returns: `DOM`

.. code-block::

	var result = xml.documentElement()

.. method:: DOM.prototype.createElement(tag)

text
Create an element with the given tag type, but do not link it into
the `DOM` yet.

:param string tag: Tag name to use for element.

:returns: `DOM`

.. code-block::

	var result = xml.createElement("div")

.. method:: DOM.prototype.createTextNode(text)

text
Create a text node with the given text contents, but do not link
it into the `DOM` yet.

:param string text: Text contents to put into element.

:returns: `DOM`

.. code-block::

	var result = xml.createElement("Hello world!")

.. method:: DOM.prototype.find(tag, attribute, value)

text
Find the first element matching the tag, attribute and value. Set
either of those to ``null`` to match anything.

:param string tag: The tag of the element to look for.
:param string attribute: The attribute of the element to look for.
:param string value: The value of the attribute of the element to look for.

:returns: `DOM` | null

.. code-block::

	var result = xml.find("tag", "attribute", "value")

.. method:: DOM.prototype.findNext(tag, attribute, value)

text
Find the next element matching the tag, attribute and value. Set either
of those to ``null`` to match anything.

:param string tag: The tag of the element to look for.
:param string attribute: The attribute of the element to look for.
:param string value: The value of the attribute of the element to look for.

:returns: `DOM` | null

.. code-block::

	var result = xml.findNext("tag", "attribute", "value")

.. method:: DOM.prototype.appendChild(dom, childDom)

text
Insert an element as the last child of a parent, unlinking the child
from its current position if required.

:param DOM dom: The DOM that will be parent.
:param DOM childDom: The DOM that will be a child.

.. code-block::

	xml.appendChild(dom, childDom)

.. method:: DOM.prototype.insertBefore(dom, elementDom)

text
Insert an element before this element, unlinking the new element
from its current position if required.

:param DOM dom: The reference DOM.
:param DOM elementDom: The DOM that will be inserted before.

.. code-block::

	xml.insertBefore(dom, elementDom)

.. method:: DOM.prototype.insertAfter(dom, elementDom)

text
Insert an element after this element, unlinking the new element
from its current position if required.

:param DOM dom: The reference DOM.
:param DOM elementDom: The DOM that will be inserted after.

.. code-block::

	xml.insertAfter(dom, elementDom)

.. method:: DOM.prototype.remove()

text
Remove this element from the `DOM`. The element can be
added back elsewhere if required.

.. code-block::

	var result = xml.remove()

.. method:: DOM.prototype.clone()

text
Clone this element (and its children). The clone is not yet linked
into the `DOM`.

:returns: `DOM`

.. code-block::

	var result = xml.clone()

.. method:: DOM.prototype.firstChild()

text
Return the first child of the element as a `DOM`, or
``null`` if no child exist.

:returns: `DOM` | null

.. code-block::

	var result = xml.firstChild()

.. method:: DOM.prototype.parent()

text
Return the parent of the element as a `DOM`, or ``null``
if no parent exists.

:returns: `DOM` | null

.. code-block::

	var result = xml.parent()

.. method:: DOM.prototype.next()

text
Return the next element as a `DOM`, or null if no such
element exists.

:returns: `DOM` | null

.. code-block::

	var result = xml.next()

.. method:: DOM.prototype.previous()

text
Return the previous element as a `DOM`, or null if no such
element exists.

:returns: `DOM` | null

.. code-block::

	var result = xml.previous()

.. method:: DOM.prototype.addAttribute(attribute, value)

text
Add attribute with the given value, returns the updated element as
a DOM.

:param string attribute: Desired attribute name.
:param string value: Desired attribute value.

:returns: `DOM`

.. code-block::

	var result = xml.addAttribute("attribute", "value")

.. method:: DOM.prototype.removeAttribute(attribute)

text
Remove the specified attribute from the element, returns the
updated element as a DOM.

:param string attribute: The name of the attribute to remove.

:returns: `DOM`

.. code-block::

	xml.removeAttribute("attribute")

.. method:: DOM.prototype.getAttribute(attribute)

text
Return the element's attribute value as a string, or null if no
such attribute exists.

:param string attribute: The name of the attribute to look up.

:returns: string | null

.. code-block::

	var result = xml.attribute("attribute")

.. method:: DOM.prototype.getAttributes()

text
Returns a dictionary object with properties and their values
corresponding to the element's attributes and their values.

:returns: Object

.. code-block::

	var dict = xml.getAttributes()

.. method:: DOM.prototype.getText()

text
|only_mutool|

Returns the text contents of the node.

:returns: string | null

.. code-block::

	var text = xml.getText()

.. method:: DOM.prototype.getTag()

text
|only_mutool|

Returns the tag name for the node.

:returns: string | null

.. code-block::

	var text = xml.getTag()

Ext Mupdf Docs Reference Javascript Types DefaultColorSpaces (`ext/mupdf/docs/reference/javascript/types/DefaultColorSpaces.rst`)

.. default-domain:: js

.. highlight:: javascript

DefaultColorSpaces

|only_mutool|

An object given to a Device with the Device.prototype.setDefaultColorSpaces() call so it
can obtain the current default ColorSpace objects.

Constructors

.. class:: DefaultColorSpaces()

Instance methods

.. method:: DefaultColorSpaces.prototype.getDefaultGray()

text
Get the default gray colorspace.

:returns: `ColorSpace`

.. method:: DefaultColorSpaces.prototype.getDefaultRGB()

text
Get the default RGB colorspace.

:returns: `ColorSpace`

.. method:: DefaultColorSpaces.prototype.getDefaultCMYK()

text
Get the default CMYK colorspace.

:returns: `ColorSpace`

.. method:: DefaultColorSpaces.prototype.getOutputIntent()

text
Get the output intent.

:returns: `ColorSpace`

.. method:: DefaultColorSpaces.prototype.setDefaultGray(colorspace)

text
:param ColorSpace colorspace: The new default gray colorspace.

.. method:: DefaultColorSpaces.prototype.setDefaultRGB(colorspace)

text
:param ColorSpace colorspace: The new default RGB  colorspace.

.. method:: DefaultColorSpaces.prototype.setDefaultCMYK(colorspace)

text
:param ColorSpace colorspace: The new default CMYK colorspace.

.. method:: DefaultColorSpaces.prototype.setOutputIntent(colorspace)

text
:param ColorSpace colorspace: The new default output intent.

Ext Mupdf Docs Reference Javascript Types Device (`ext/mupdf/docs/reference/javascript/types/Device.rst`)

.. default-domain:: js

.. highlight:: javascript

Device

All built-in devices have the methods listed below. Any function that
accepts a device will also accept a Javascript object with the same
methods. Any missing methods are simply ignored, so you only need to
create methods for the device calls you care about.

Many of the methods take graphics objects as arguments: Path,
Text, Image and Shade.

Colors are specified as arrays with the appropriate number of components
for the color space.

The methods that clip graphics, e.g. Device.prototype.clipPath(),
Device.prototype.clipStrokePath(), Device.prototype.clipText(), etc., must be balanced with
a corresponding Device.prototype.popClip().

Constructors

.. class:: Device(callbacks)

text
Create a Device which calls back to Javascript.

The callback object may provide functions matching the methods
on the Device class. Any device calls that don't have a corresponding
function will simply be ignored.

:param callbacks: object containing callback functions

You can create other devices with DrawDevice and DisplayListDevice.

Constants

:term:Blend mode constants for use with Device.prototype.beginGroup:

.. data:: Device.BLEND_NORMAL
.. data:: Device.BLEND_MULTIPLY
.. data:: Device.BLEND_SCREEN
.. data:: Device.BLEND_OVERLAY
.. data:: Device.BLEND_DARKEN
.. data:: Device.BLEND_LIGHTEN
.. data:: Device.BLEND_COLOR_DODGE
.. data:: Device.BLEND_COLOR_BURN
.. data:: Device.BLEND_HARD_LIGHT
.. data:: Device.BLEND_SOFT_LIGHT
.. data:: Device.BLEND_DIFFERENCE
.. data:: Device.BLEND_EXCLUSION
.. data:: Device.BLEND_HUE
.. data:: Device.BLEND_SATURATION
.. data:: Device.BLEND_COLOR
.. data:: Device.BLEND_LUMINOSITY

Instance methods

.. method:: Device.prototype.close()

text
Tell this device that we are done, and flush any pending output.

Before closing, ensure that there have been as many calls to
`popClip()` as there have been to the clipping functions:
`clipPath()`, `clipStrokePath()`, `clipText()`, etc.

.. code-block::

	device.close()

Line art
^^^^^^^^

.. method:: Device.prototype.fillPath(path, evenOdd, ctm, colorspace, color, alpha)

text
Fill a path.

:param Path path: Path object.
:param boolean evenOdd: Use :term:`even-odd rule` or :term:`non-zero winding number rule` to fill the path.
:param Matrix ctm: The transform to apply.
:param ColorSpace colorspace: The colorspace of the color to fill with.
:param Color color: The color to fill the path with.
:param number alpha: The :term:`opacity`.

.. code-block::

	device.fillPath(path, false, mupdf.Matrix.identity, mupdf.ColorSpace.DeviceRGB, [1, 0, 0], true)

.. method:: Device.prototype.strokePath(path, stroke, ctm, colorspace, color, alpha)

text
Stroke a path.

:param Path path: Path object.
:param StrokeState stroke: Stroke state.
:param Matrix ctm: The transform to apply.
:param ColorSpace colorspace: Colorspace.
:param Color color: The color to stroke the path with.
:param number alpha: The :term:`opacity`.

.. code-block::

	device.strokePath(path,
		{dashes: [5, 10], lineWidth: 3, lineCap: 'Round' },
		mupdf.Matrix.identity,
		mupdf.ColorSpace.DeviceRGB,
		[0, 1, 0],
		0.5
	)

.. method:: Device.prototype.clipPath(path, evenOdd, ctm)

text
Clip a path.

:param Path path: Path object.
:param boolean evenOdd: Use :term:`even-odd rule` or :term:`non-zero winding number rule` to fill the path.
:param Matrix ctm: The transform to apply.

.. code-block::

	device.clipPath(path, true, mupdf.Matrix.identity)

.. method:: Device.prototype.clipStrokePath(path, stroke, ctm)

text
Clip & stroke a path.

:param Path path: Path object.
:param StrokeState stroke: Stroke state.
:param Matrix ctm: The transform to apply.

.. code-block::

	device.clipStrokePath(path, true, mupdf.Matrix.identity)

Text
^^^^

.. method:: Device.prototype.fillText(text, ctm, colorspace, color, alpha)

text
Fill a text object.

:param Text text: Text object.
:param Matrix ctm: The transform to apply.
:param ColorSpace color