This document describes how PostForge works internally. It is aimed at developers
who want to understand the system well enough to contribute code, fix bugs, or
add new features. It assumes familiarity with PostScript at a user level (you know
what moveto, lineto, fill do) but not with interpreter internals.
Every PostScript program flows through the same pipeline:
┌──────────────┐
PostScript Source ──► Tokenizer ──► Execution ──► │ Display List │──► Output Device
(.ps file, extracts Engine │ (per page) │ (PNG, PDF, SVG,
string, or tokens from (exec_exec) └──────────────┘ TIFF, Qt)
interactive) byte streams
-
Source — PostScript code arrives as a file, string, or interactive input. The CLI (
postforge/cli.py) parses arguments, creates aContext(viapostforge/core/context_init.py), and pushes the input onto the execution stack. -
Tokenizer (
postforge/core/tokenizer.py) — Reads bytes from a stream one at a time, recognizing numbers, names, strings, procedures (delimited by{}), and special syntax like hex strings and ASCII85. The tokenizer is invoked on demand by the execution engine whenever a tokenizable object (File, Run, String) sits on top of the execution stack. -
Execution Engine (
postforge/operators/control.py, functionexec_exec) — The heart of the interpreter. Continuously processes objects from the execution stack until it is empty. Described in detail in the next section. -
Display List — A flat list of rendering commands (Fill, Stroke, ImageElement, TextObj, ClipElement, etc.) accumulated during a page. All coordinates are in device space — path operators transform user space coordinates through the CTM at the time of the call. Painting operators like
fill,stroke, andshowappend elements here. -
Output Device — When
showpagefires, the accumulated display list is handed to a device for rendering. Devices live inpostforge/devices/— raster devices (PNG, TIFF, Qt) and SVG use a shared Cairo rendering backend, while the PDF device generates content streams directly from the display list. An output device can use whatever rendering method it wants. After rendering,showpageerases the display list and reinitializes the graphics state for the next page.copypagefollows the same rendering path but preserves both the display list and graphics state, allowing further drawing on top of the existing page contents.
The execution engine is implemented in exec_exec() in
postforge/operators/control.py. It is a single while loop that drains
the execution stack (e_stack). On each iteration it inspects the top-of-stack
object and takes one of five paths:
Objects that are literal (their attrib is ATTRIB_LIT, or their type is in
LITERAL_TYPES — Int, Real, Bool, Null, Mark) are popped from the execution
stack and pushed onto the operand stack with no further processing.
An Operator wraps a Python function. The engine pops it from the execution
stack and calls top.val(ctxt, o_stack) directly. The operator function
receives the full context and the operand stack, pops its own arguments,
performs work, and pushes the results. All PostScript built-in operators are
registered this way during create_system_dict() in postforge/operators/dict.py.
Executable names trigger a dictionary stack lookup via ps_dict.lookup(). The
lookup walks the dictionary stack from top to bottom, returning the first match.
The name on the execution stack is then replaced with the looked-up object
(copied via __copy__() for non-operators) and execution continues — the
replacement object will be processed on the next iteration. If the name is not
found, a PostScript undefined error is raised. Note that while a successful
name lookup never touches the operand stack, the error path does — per the
PLRM's error initiation mechanism (Section 3.11.1), the offending name is
pushed onto the operand stack before the error handler is invoked.
The copy-on-lookup behavior is important: it prevents one execution from
mutating a shared dictionary entry (e.g., changing a procedure's attrib from
executable to literal via cvlit). Operators are immutable and skip the copy.
When a File, Run, or executable String sits on top, the engine calls the tokenizer to extract the next PostScript token from the byte stream. If a token is produced, it is pushed for execution. If the stream is exhausted, the tokenizable object is popped and execution continues with whatever is below it.
An executable array { ... } is a procedure. The engine peels off one element
at a time from the front (tracked by a start index and length counter),
pushing each element for execution. When the array is exhausted it is discarded.
As an optimization, the last element replaces the procedure on the execution
stack directly instead of requiring a separate push/pop. This also acts as
tail-call optimization: when a procedure's last element is itself a procedure
call (e.g., a recursive invocation), the replacement prevents the execution
stack from growing on each call.
Beyond the five main paths, the execution engine also handles control flow objects that appear on the execution stack:
-
Stopped (
T_STOPPED) — Pushed by thestoppedoperator to mark an error boundary. If execution reaches this marker without being interrupted,falseis pushed to the operand stack (no error occurred). Thestopoperator unwinds the execution stack to the nearest Stopped marker. -
Loop (
T_LOOP) — Loop contexts forloop,repeat,for,forall, andpathforall. Each iteration pushes a copy of the loop body onto the execution stack. Theexitoperator unwinds to the nearest Loop marker. -
HardReturn — Used by constructs like
execjob()to mark an unwindable boundary on the execution stack.
PostScript defines four stacks, all maintained in the Context object:
| Stack | Field | Purpose |
|---|---|---|
| Operand | o_stack |
Data values and arguments for operators |
| Execution | e_stack |
Objects waiting to be executed |
| Dictionary | d_stack |
Variable scoping — name lookup walks this top-to-bottom |
| Graphics State | g_stack |
Saved graphics states from gsave / save |
Stacks are Stack objects (a list subclass with an optional capacity limit).
Default capacities are defined in postforge/core/types/constants.py
(O_STACK_MAX=500, E_STACK_MAX=250, D_STACK_MAX=250, G_STACK_MAX=10), though
these can be overridden via setuserparams.
A Cython-compiled copy of exec_exec exists at
postforge/operators/_control_cy.pyx. It provides 15–40% speedup by using
C-typed local variables and inlining the dictionary lookup for the Name path.
If the compiled .so is present it is loaded automatically; otherwise the pure
Python version runs. The two implementations must be kept functionally
equivalent — see build_cython.sh for compilation.
All PostScript values are represented as Python objects inheriting from
PSObject (defined in postforge/core/types/base.py). Every PSObject carries:
val— The Python value (int, float, bytes, list, dict, callable, etc.)attrib— Literal (ATTRIB_LIT) or executable (ATTRIB_EXEC)access— Access control level (unlimited, read-only, execute-only, none)TYPE— Integer type constant for fast dispatch (e.g.,T_INT,T_NAME)is_composite/is_global— VM allocation metadata
Primitive types (postforge/core/types/primitive.py):
Int, Real, Bool, Name, String, Operator, Mark, Null
Composite types (postforge/core/types/composite/):
Array, Dict, PackedArray — these hold references and participate in VM
save/restore semantics.
Graphics types (postforge/core/types/graphics.py):
GraphicsState, GState (gstate object), Path, DisplayList, plus all
display list element classes (Fill, Stroke, ImageElement, TextObj, etc.)
File types (postforge/core/types/file_types.py):
File, Run, StandardFile, StandardFileProxy, FilterFileWrapper
Control flow types (postforge/core/types/control.py):
Stopped, Loop (with variants for loop/repeat/for/forall/pathforall),
HardReturn
The attrib field controls how the execution engine treats an object. A literal
Name pushed on the execution stack is transferred to the operand stack; an
executable Name triggers a dictionary lookup. Operators cvx and cvlit flip
this attribute. The copy-on-lookup mechanism (Path 3 above) prevents these
mutations from corrupting shared dictionary entries.
Type constants (T_INT, T_NAME, T_ARRAY, etc.) and pre-computed type
groupings (LITERAL_TYPES, TOKENIZABLE_TYPES, NUMERIC_TYPES, ARRAY_TYPES)
live in postforge/core/types/constants.py. The execution engine uses these
groupings for fast in checks rather than isinstance calls.
PostScript defines a dual virtual memory (VM) system that PostForge implements faithfully.
-
Local VM (
ctxt.lvm) — Per-context storage. Most objects live here. Subject to save/restore — arestorerolls back all changes made since the correspondingsave. -
Global VM (
GlobalResources.gvm) — Shared across contexts. Objects allocated here (viatrue setglobal) surviverestore. Font dictionaries are typically stored in global VM.
The vm_alloc_mode flag in the context controls which VM new composite objects
are allocated in. The setglobal / currentglobal operators manipulate this
flag.
The save operator (postforge/operators/vm.py) snapshots the current local
VM state and pushes a save object. The snapshot uses a Copy-on-Write (COW)
strategy: composite objects are marked as protected, and mutations trigger a
copy of the original backing store before modification. On restore, the
protected originals are reinstated.
For objects that don't participate in COW (or as a fallback), pickle-based
snapshots store VM state in _vm_snapshots keyed by (context_id, save_id).
save also pushes the current graphics state onto g_stack (like gsave),
and restore pops it back.
PostScript defines a job server model (PLRM Section 3.7.7) that controls how successive PostScript programs share an interpreter. PostForge implements this in two layers:
execjob() wraps every top-level PostScript job in a save/restore
boundary. This ensures that one job cannot affect the next — all local VM
changes are rolled back, the dictionary and graphics stacks are reset, and the
interpreter returns to a clean state. Jobs processed this way are called
encapsulated because their side effects are contained.
startjob (postforge/operators/job_control.py) allows a PostScript
program to break out of this encapsulation from within. It takes a boolean
and a password:
true password startjob % → true (success) or false (failure)When startjob succeeds it performs a job server sequence:
- End the current job — clear the operand stack, reset the dictionary stack to its initial three dictionaries (systemdict, globaldict, userdict), and if the current job was encapsulated, restore VM to the job-level save point.
- Begin a new job — if the boolean was
true(unencapsulated), skip the save so that VM changes persist for subsequent jobs. Iffalse(encapsulated), perform a new save to create a fresh job boundary.
Three conditions must all be met for startjob to succeed (otherwise it
pushes false and does nothing):
- The interpreter supports job encapsulation (
ctxt.supports_job_encapsulation) - The password matches
StartJobPasswordin the system parameters - The save nesting level equals the level when the current job started (no
outstanding
saveoperations deeper than the job boundary)
The save-level check is why the standalone tests call false 0 startjob
between test groups — it starts a fresh encapsulated job, resetting VM state
so the next group begins cleanly.
exitserver is the Level 1 equivalent. It is defined in serverdict and
behaves as true password startjob — always requesting an unencapsulated job.
On success it prints a standard message to stdout:
%%[exitserver: permanent state may be changed]%%
and removes serverdict from the dictionary stack. On failure it raises
invalidaccess.
Job save tracking: The context maintains a job_save_level_stack that
records the save object for each nested job. When startjob ends the current
job, it uses this stack to find the correct save point to restore.
Testing note: Successful startjob / exitserver calls destroy VM state
(clearing stacks and restoring saves), which wipes out the unit test
framework. For this reason, tests that exercise successful calls live in
unit_tests/job_control_tests_standalone.ps and write pass/fail results
directly to a stats file rather than using the assert framework.
PostScript programs build up a page through a sequence of graphics operations. The pipeline looks like:
Path Construction ──► Painting Operators ──► Display List ──► Device Rendering
(moveto, lineto, (fill, stroke, (flat list of (Cairo/custom
curveto, arc, show, image) Fill, Stroke, backend)
closepath) TextObj, etc.)
The GraphicsState object (postforge/core/types/graphics.py) holds all
per-page rendering state:
| Category | Fields |
|---|---|
| Transform | CTM (current transformation matrix), iCTM (inverse) |
| Path | currentpoint, path (list of SubPaths) |
| Clipping | clip_path, clip_path_stack, clip_path_version |
| Color | color_space, color, transfer_function, overprint |
| Line | line_width, line_cap, line_join, miter_limit, dash_pattern |
| Font | font (current font dictionary) |
| Other | flatness, smoothness, stroke_adjust, halftone, page_device |
gsave copies the graphics state (via an optimized shallow-copy with selective
deep-copy for mutable containers) and pushes it onto g_stack. grestore pops
it back.
Path operators (postforge/operators/path.py) build a Path — a list of
SubPath objects, where each SubPath is a list of elements: MoveTo, LineTo,
CurveTo, ClosePath. Coordinates are transformed from user space to device
space via the CTM at the time of the call. The current path lives in the
graphics state until a painting operator consumes it.
When a painting operator executes:
- It captures the current path and relevant graphics state (color, line properties, CTM).
- It creates a display list element —
Fill,Stroke,PatternFill,ImageElement,TextObj, etc. - It appends the element via
DisplayListBuilder.add_graphics_operation(). - It clears the current path (for
fill/stroke) or advances the current point (forshow).
The DisplayListBuilder (postforge/core/display_list_builder.py) manages
clipping path synchronization. ClipElement objects in the display list tell
the rendering device when and how to update its clip region.
The display list is a flat Python list containing instances of these classes
(all defined in postforge/core/types/graphics.py):
| Element | Created by | Purpose |
|---|---|---|
Fill |
fill, eofill |
Filled path with color and winding rule |
Stroke |
stroke |
Stroked path with line properties and CTM |
PatternFill |
fill with pattern color space |
Pattern-tiled fill |
ImageElement |
image, imagemask, colorimage |
Raster image data |
TextObj |
show (in TextObjs mode) |
Structured text for PDF output |
ClipElement |
clip, eoclip, initclip |
Clipping path update |
GlyphRef |
show (cache hit) | Reference to cached glyph bitmap |
GlyphStart/GlyphEnd |
show (cache miss) | Glyph bitmap capture markers |
ErasePage |
erasepage |
Page erase marker |
PostScript has a rich color model with multiple color space families.
PostForge implements this in postforge/core/color_space.py (the
ColorSpaceEngine class) with operators in postforge/operators/color_ops.py
and postforge/operators/device_color_state.py.
| Family | Spaces | Components |
|---|---|---|
| Device | DeviceGray, DeviceRGB, DeviceCMYK | 1, 3, 4 |
| CIE-Based | CIEBasedA, CIEBasedABC, CIEBasedDEF, CIEBasedDEFG | 1, 3, 3, 4 |
| ICC | ICCBased | N (profile-dependent) |
| Special | Indexed, Separation, DeviceN, Pattern | varies |
Device spaces map directly to output components. CIE-based spaces go through a calibrated pipeline (decode → matrix → XYZ → sRGB). ICCBased spaces use lcms2 via Pillow's ImageCms module when available, falling back to the alternate device space otherwise. Indexed, Separation, and DeviceN are all defined in terms of an underlying base space — their color values are resolved through a palette lookup or tint transform before reaching the display list.
The graphics state stores two fields for color:
color_space— Always a Python list, even for simple spaces (e.g.,["DeviceGray"],["Separation", name, alt_space, tint_transform]).color— A list of Python floats representing the current color. For most spaces this is the resolved device-space value. For ICCBased spaces it holds the raw profile components until rendering time.
Pattern color spaces store the pattern dictionary separately in
gstate._current_pattern.
PostScript provides two ways to set color:
Level 2 general model — setcolorspace sets the active space and
initializes the color to its default. setcolor then sets color components
within that space. For Separation and DeviceN, setcolor executes the tint
transform procedure to resolve the color into the alternative space. For
Indexed, it performs a palette lookup.
Level 1 convenience operators — setgray, setrgbcolor, setcmykcolor,
and sethsbcolor each set both the color space and the color in a single
call. These are equivalent to a setcolorspace / setcolor pair.
Query operators (currentgray, currentrgbcolor, currentcmykcolor) convert
the current color to the requested device space on the fly using standard PLRM
conversion formulas (NTSC weighting for gray, etc.).
When the UseCIEColor page device parameter is true, device color space
operators (setgray, setrgbcolor, setcmykcolor, setcolorspace) intercept
the color space selection and substitute CIE-based default color spaces. The
substitution looks up DefaultGray, DefaultRGB, or DefaultCMYK from the
ColorSpace resource category. If the corresponding Default* resource is
defined (via defineresource), it replaces the device color space with the
CIE-based space. If no Default* resource is found, the device color space is
used as a fallback. Default color space resource files are in
postforge/resources/ColorSpace/ — DefaultGray.ps (CIEBasedA with D65),
DefaultRGB.ps (CIEBasedABC with sRGB primaries), and DefaultCMYK.ps
(pass-through to DeviceCMYK).
Color conversion is lazy — setcolor stores the color in the graphics
state, but the conversion to device color happens only when a painting operator
builds a display list element:
- A painting operator (
fill,stroke, etc.) callsColorSpaceEngine.convert_to_device_color(). - The engine dispatches based on color space family — device spaces pass through (with cross-conversion if needed), CIE-based spaces run through their decode/matrix/XYZ pipeline, and ICCBased spaces apply an lcms2 transform.
- The resulting color values are stored in the display list element (
Fill,Stroke, etc.). When a/ColorModelor/ProcessColorModelis set in the page device (e.g.,/DeviceRGBfor Cairo-based raster devices), colors are converted to that model.ProcessColorModelis the PLRM-standard name (Level 3);ColorModelis checked first for backward compatibility. When neither is set (the PDF device), original device color spaces (CMYK, Gray, RGB) are preserved. - The rendering device consumes these colors — Cairo-based devices receive RGB, while the PDF device emits the appropriate PDF color operators for whatever color space was used.
PostForge uses a tiered approach to ICC color management:
- Tier 1 — PLRM formulas only. Device space cross-conversions (e.g., CMYK → RGB) use the standard PostScript formulas. Always available.
- Tier 2 — ICCBased profiles. When a PostScript program specifies an ICCBased color space with an embedded profile, PostForge extracts the profile and builds an lcms2 transform. Falls back to Tier 1 if the profile cannot be loaded.
- Tier 3 — Default CMYK profile. PostForge searches the system for a CMYK
ICC profile (GhostScript, colord, macOS, Windows locations) and uses it for
DeviceCMYK → RGB conversion, producing more accurate on-screen rendering.
Disabled via
--no-iccor when no profile is found.
Patterns are a special color space where the "color" is a tiled or shaded
graphic rather than a flat value. makepattern instantiates a pattern by
executing its PaintProc and capturing the result as a display list.
setpattern then installs the pattern as the current color.
There are two paint types for tiling patterns. Colored patterns (PaintType 1)
carry their own colors — the color space is simply ["Pattern"]. Uncolored
patterns (PaintType 2) act as a stencil — the color space is
["Pattern", underlying_space] and setcolor provides the color components
that fill the stencil. At rendering time, the pattern cell's display list
is tiled across the fill area based on the pattern's step size and bounding
box.
Output devices render the display list into a final format. Each device
consists of two parts that work together: a PostScript configuration file
in postforge/resources/OutputDevice/ (e.g., png.ps) that defines the page device
dictionary, and a Python module in postforge/devices/ that implements a
showpage(ctxt, pd) function to perform the actual rendering. The raster
devices (PNG, TIFF, Qt) use a shared Cairo rendering backend, while the PDF
device generates PDF content streams directly from the display list. A custom
device can use any rendering approach it wants.
┌────────────────────────────────────────────────────────────────┐
│ Shared Cairo Rendering │
│ (devices/common/) │
│ │
│ cairo_renderer.py - dispatch cairo_patterns.py - patterns │
│ cairo_images.py - images cairo_shading.py - shading │
└─────────┬──────────────┬──────────────┬──────────────┬─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ PNG │ │ SVG │ │ TIFF │ │ Qt │
│ device │ │ device │ │ device │ │ device │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
┌─────────────────────────────────────────┐
│ PDF device │
│ (devices/pdf/) │
│ │
│ pdf.py ──► content_stream.py │
│ ├─ stroke_ops.py │
│ ├─ text_ops.py │
│ ├─ type3_ops.py │
│ ├─ image_ops.py │
│ └─ shading_ops.py │
│ ──► pdf_builder.py │
│ ├─ font_embedder.py │
│ ├─ cid_font_embedder.py │
│ ├─ cff_font_embedder.py │
│ └─ font_tracker.py │
└─────────────────────────────────────────┘
PNG (postforge/devices/png/png.py) — Creates a Cairo ImageSurface, calls
render_display_list(), writes a .png file. The simplest device and a good
starting point for understanding the rendering pipeline.
PDF (postforge/devices/pdf/) — Generates PDF content streams directly
from the display list (does not use Cairo). Preserves original color spaces
(CMYK, Gray, RGB) instead of converting everything to RGB. Text uses TextObj
elements written as PDF text operators with TJ arrays and kern values, producing
searchable/selectable text. Font embedding handles Type 1 reconstruction,
CID/TrueType extraction, CFF, Type 42, Type 3, and subsetting. The final PDF
is assembled at document end via a native PDF writer (pdf_objects.py).
SVG (postforge/devices/svg/svg.py) — Renders to a Cairo SVGSurface,
then post-processes the SVG to convert text from outlines to selectable <text>
elements with CSS font-family fallback chains. This can be overridden with
--text-as-paths to render text as path outlines instead. Each page produces
a separate .svg file.
TIFF (postforge/devices/tiff/tiff.py) — Renders to a Cairo ImageSurface
(like PNG), then converts to a PIL Image for TIFF encoding via Pillow. Supports
single-page (one .tif per page) and multi-page (all pages in one .tif via
--multipage-tiff). Optional CMYK output via ICC profile conversion
(--cmyk), with the ICC profile embedded in the TIFF for downstream color
management.
Qt (postforge/devices/qt/qt.py) — Interactive display window. Renders to
a Cairo ImageSurface and displays it in a Qt widget. In interactive mode, the
display updates live as display list elements are added; in batch mode, it
updates on showpage.
The PostScript configuration files (e.g., png.ps, pdf.ps, svg.ps, tiff.ps) define the page
device dictionary — page size, resolution, margins, color space, and
Install/BeginPage/EndPage procedures. When a device is selected, this
dictionary is loaded and merged into the graphics state's page_device.
render_display_list() in postforge/devices/common/cairo_renderer.py is the
main dispatch loop used by the raster and vector-surface devices (PNG, SVG,
TIFF, Qt). The PDF device does not use this renderer — it generates PDF content
streams directly. The Cairo renderer iterates over display list elements and
delegates to type-specific rendering functions:
- Path construction → Cairo
move_to,line_to,curve_to,close_path - Fill/Stroke → Cairo
fill/strokewith color and line properties - Images → Pixel format conversion and Cairo surface blitting (
cairo_images.py) - Patterns → Pattern surface tiling (
cairo_patterns.py) - Shading → Gradient and mesh rendering (
cairo_shading.py) - Clipping → Cairo
clip/reset_clip - Glyph cache → Bitmap surface blitting for cached Type 3 glyphs
Stroke method: For bitmap devices (PNG, Qt), strokes are converted to filled
paths by the interpreter before they reach the display list. This works around
bugs in Cairo's stroke rasterization, particularly with dashed lines. Vector
devices (PDF, SVG) use native stroke rendering instead — PDF emits stroke
operators directly, while SVG uses Cairo's vector surface. This behavior is
controlled per-device by the /StrokeMethod entry in the page device dictionary
(set in each device's .ps configuration file).
PostScript's resource system provides a structured way to look up fonts,
encodings, color spaces, and other named objects. PostForge implements this
in postforge/operators/resource.py.
Resources are organized by category, stored in nested dictionaries within both global and local VM:
gvm["resource"]["Category"] → Dict of category definitions
gvm["resource"]["Font"] → Dict of global font resources
gvm["resource"]["Encoding"] → Dict of encoding resources
lvm["resource"]["Font"] → Dict of local font resources
...
The postforge/resources/ directory on disk contains the resource files organized by
category:
| Directory | Contents |
|---|---|
Font/ |
Font programs — Type 1 (.t1, .pfa, .pfb), Type 3 (.t3, .ps), TrueType/OpenType (.ttf, .otf) |
CIDFont/ |
CID-keyed font definitions |
CMap/ |
Character code to CID mapping tables |
Encoding/ |
Character encoding vectors |
ColorSpace/, ColorRendering/ |
Color space definitions (including Default* CIE spaces) |
Form/, Pattern/, Halftone/ |
Graphics resources |
ProcSet/ |
Procedure sets |
IdiomSet/, ControlLanguage/, etc. |
Level 3 resource categories (registered, empty) |
Init/ |
Initialization scripts (sysdict.ps, resource categories) |
OutputDevice/ |
Device configuration dictionaries |
When findfont is called, the resource system searches:
FontDirectory(in-memory cache of already-loaded fonts)- Font resource files on disk (
postforge/resources/Font/) - System fonts via font mapping (
postforge/resources/Init/fontmapping.ps) - If all else fails, Helvetica is used as the fallback font
Font programs are PostScript files that, when executed, call definefont to
register the font dictionary. PostForge supports Type 1 (CharStrings with
charstring interpreter), Type 3 (BuildGlyph procedure), Type 0 (composite
CID-keyed fonts), and Type 42 (TrueType wrapped in PostScript).
At startup, postforge/resources/Init/sysdict.ps is executed. This PostScript program
defines operators and data structures that are more naturally expressed in
PostScript than Python — error handlers, encoding vectors, resource category
setup, device initialization procedures, and the interactive executive. The
resource category infrastructure is bootstrapped by
postforge/resources/Init/resourcecategories.ps.
A quick reference to the directory structure:
| Path | Purpose |
|---|---|
postforge/cli.py |
Entry point, argument parsing, orchestration |
postforge/cli_args.py |
CLI argument parser definition, page range parsing, output naming |
postforge/cli_runner.py |
Execution logic — device setup, batch jobs, interactive mode |
postforge/core/context_init.py |
PostScript context creation (init_system_params, create_context) |
postforge/core/types/ |
Type system — PSObject, all PS types, Context, GraphicsState |
postforge/core/tokenizer.py |
Byte-stream tokenizer |
postforge/core/error.py |
PostScript error handling |
postforge/core/color_space.py |
Color space infrastructure |
postforge/core/charstring_interpreter.py |
Type 1 font charstring interpreter |
postforge/core/type2_charstring.py |
Type 2 (CFF/OpenType) charstring interpreter |
postforge/core/display_list_builder.py |
Display list construction + clip tracking |
postforge/core/glyph_cache.py |
Type 3 glyph path and bitmap caching |
postforge/core/binary_token.py |
Binary object/token encoding/decoding |
postforge/core/ps_function.py |
PostScript function evaluation (Type 0/2/3/4) |
postforge/core/unicode_mapping.py |
Glyph name → Unicode mapping |
postforge/operators/control.py |
Execution engine (exec_exec), exec, stopped |
postforge/operators/_control_cy.pyx |
Cython-compiled execution engine |
postforge/operators/dict.py |
Operator registration, dictionary operators |
postforge/operators/path.py |
Path construction operators |
postforge/operators/painting.py |
fill, stroke, show |
postforge/operators/graphics_state.py |
gsave, grestore, state operators |
postforge/operators/matrix.py |
CTM manipulation |
postforge/operators/vm.py |
save, restore, VM allocation |
postforge/operators/resource.py |
Resource system operators |
postforge/operators/image.py |
image, imagemask, colorimage |
postforge/operators/font_ops.py |
definefont, findfont, scalefont |
postforge/operators/text_show.py |
show, ashow, widthshow, kshow |
postforge/operators/filter.py |
Filter framework + core filters |
postforge/devices/common/ |
Shared Cairo rendering backend |
postforge/devices/png/ |
PNG output device |
postforge/devices/pdf/ |
PDF output device + font embedding |
postforge/devices/tiff/ |
TIFF output device (multi-page, CMYK) |
postforge/devices/qt/ |
Interactive Qt display |
postforge/utils/ |
Memory analysis, profiling |
postforge/resources/Init/ |
PostScript initialization scripts |
postforge/resources/Font/ |
Type 1 font programs |
postforge/resources/OutputDevice/ |
Device configuration dictionaries |
unit_tests/ |
PostScript-based test suite |