From a SWMM5 or SWMM6 model set to a finished PDF report:

SWMM PDF WIZARD

How It Works

From a SWMM5 or SWMM6 model set to a finished PDF report: the file readers, the report layout, the pdfcraft engine in the browser, and the bridge your other apps use.

App version SWMM PDF Wizard 1.0.0

PDF engine pdfcraft-cli 0.8.0 (commit 9e5b70d, 16 Oct 2026), compiled to WebAssembly

SWMM engines tested EPA SWMM 5.2.4 and OpenSWMM 6.0.0-alpha.4

Prepared for Robert (Bob) Dickinson

Date 17 October 2026

Contents

1. What the wizard does 3

1.1 What is in the package 3

2. How the pieces fit together 5

2.1 The modules 5

3. Using the wizard, step by step 7

3.1 Step 1 — Model set 7

3.2 Step 2 — Contents 9

3.3 Step 3 — Finish (pdfcraft) 10

3.4 Step 4 — Build and download 11

3.5 Export menu and the rest of the header 11

4. Reading the SWMM files 13

4.1 Text encoding 13

4.2 The input file (.inp) 13

4.3 The report file (.rpt) 13

4.4 The binary results file (.out) 14

5. Laying out the report 16

5.1 Pages, type and the PDF writer 16

5.2 The sections, in order 16

5.3 Tables 17

5.4 Charts 18

5.5 Page kinds and labels 18

6. Finishing with pdfcraft 19

6.1 What pdfcraft is 19

6.2 How it was built for the browser 19

6.3 How it runs in the page 19

6.4 The script 19

7. Using the wizard from your other apps 21

7.1 The call 21

7.2 What happens between the two tabs 21

7.3 Safety rules 22

7.4 The demo app 22

8. Putting it online 23

8.1 The claude.ai copy 23

8.2 Updating pdfcraft 23

9. How it was checked 24

9.1 Not yet checked by a person 24

10. What the wizard does not do 25

11. Use of AI 26

SWMM PDF Wizard turns a SWMM model set into a report you can send. You give it the input file (.inp) and, when you have them, the engine’s report (.rpt) and binary results (.out). It works with files written by EPA SWMM 5 and by OpenSWMM 6, and it tells you which engine wrote them. Everything runs in the browser tab. No file is uploaded anywhere.

The work is split between two pieces of software:

  • The wizard’s own code reads the three SWMM files and lays out the report pages: a cover, a linked contents page, a model summary, a network map, a run summary, hydrographs, every table from the .rpt, figures sent in by other apps, the .rpt as an appendix, and a Use of AI page. Every number on those pages is copied from the engine’s files.
  • pdfcraft, the open-source PDF workbench written in Rust by the ArtCraft team, finishes the document. Its command-line tool, pdfcraft-cli, is compiled unchanged to WebAssembly and runs inside the page. It combines attachments, adds bookmarks, page labels, running headers and numbered footers, document properties, an optional watermark and password protection, saves the file, then opens the saved file again to check it and draw the page previews.

A small script, swmm-pdf-bridge.js, lets any of your other apps open the wizard with a model set, charts and PDFs already loaded, and, if asked, receive the finished PDF back.

In one line: the engine’s numbers go in, a bookmarked, labelled, optionally secured PDF comes out, and nothing leaves the browser.

1.1 What is in the package

The wizard ships as one folder. Put the folder on any static web host (Netlify, GitHub Pages) and open index.html.

File Size (bytes) What it is
index.html 202,715 The whole wizard in one file: the SWMM readers, the PDF writer, the report layout, the pdfcraft script planner, the user interface, and the Web Worker that runs pdfcraft.
pdfcraft-cli.wasm.gz 7,015,516 pdfcraft-cli 0.8.0 for wasm32-wasip1, gzipped. Fetched the first time you build, once per visit.
pdfcraft-cli.wasm 22,735,265 The same engine uncompressed. Used only if the .gz copy cannot be fetched or inflated. Optional.
swmm-pdf-bridge.js 8,449 The script your other apps include to hand the wizard a model set, figures and PDFs.
bridge-demo.html 6,329 A small stand-in app that uses the bridge, including getting the finished PDF back.
swmm-io.js 27,332 The .inp / .rpt / .out reader as a standalone library, for other apps to use.
samples/ — The site drainage model run with EPA SWMM 5.2.4 and with OpenSWMM 6.0.0-alpha.4, as separate files and as one JSON file per engine.
README.md, BUILD.md — Deployment notes, bridge usage, and how the WebAssembly engine was built.
LICENSES/ — pdfcraft (MIT OR Apache-2.0, with its NOTICE) and browser_wasi_shim (MIT OR Apache-2.0).

2. How the pieces fit together

Figure 1. Data flow from the model set to the finished PDF.

Figure 1. Data flow from the model set to the finished PDF.

A build goes through five stages. The wizard shows each one as it runs.

  • Read and check the files. swmm-io.js parses the .inp, the .rpt and the .out, and the wizard compares them: the engine named in the .rpt against the version number inside the .out, and the flow units in each file.
  • Lay out the report pages. report-builder.js decides what goes on each page and pdf-writer.js writes those pages as a plain vector PDF in memory, with real text (searchable, selectable) and drawn charts.
  • Load pdfcraft. The first build of a visit fetches the 7 MB engine, inflates it and compiles it inside a Web Worker. Later builds reuse the compiled module.
  • pdfcraft finishes the document. pdfcraft-plan.js writes a script of pdfcraft tool calls from the page layout and your choices. The worker runs the whole script in one call of pdfcraft-cli on an in-memory folder.
  • pdfcraft checks its own output. The same script reopens the saved file, reads its page count, page labels, bookmarks and security back, extracts text from the first page and renders previews of up to 24 pages.

2.1 The modules

Module Lines Job
swmm-io.js 513 Reads .inp, .rpt and .out from SWMM5 and SWMM6; pairs files by name; detects the engine; decodes text.
pdf-writer.js 242 A small PDF writer: pages, text in the standard-14 fonts, lines, rectangles, paths, circles, JPEG images, internal and web links, Flate compression.
std14-widths.js 3 Character widths for Helvetica and Helvetica-Bold, taken from pdfcraft’s own font metrics so text measures the same way pdfcraft does.
report-builder.js 662 Page layout: sections, tables with repeating headers, charts, the network map, the contents page, page kinds for labelling.
pdfcraft-plan.js 116 Turns the layout and the Finish settings into the list of pdfcraft tool calls (the script).
pdfcraft-runner.js 110 Runs pdfcraft-cli.wasm on an in-memory folder through a WASI shim; one fresh instance per call.
app.html 1,021 The four-step interface, file loading, the build pipeline, previews, exports, the bridge receiver, onboarding, theme and GIF recording.
swmm-pdf-bridge.js 153 Used by other apps: packs model files, figures and PDFs, opens the wizard, delivers the payload, receives the PDF.

The build step (build.js) inlines every module except the bridge into index.html, so the wizard is one file plus the engine. The WASI shim and the runner are inlined as the source text of the Web Worker, which the page starts from a blob URL.

3. Using the wizard, step by step

The wizard has four numbered tabs and an About tab. You can move between tabs in any order; Build is the only step that does work.

3.1 Step 1 — Model set

Figure 2. Step 1 after loading the SWMM5 sample.

Figure 2. Step 1 after loading the SWMM5 sample.

There are six ways to load files:

  • Choose files: one picker for any mix of .inp, .rpt, .out (and .txt reports).
  • Choose folder, or drag a folder onto the page. The folder is read recursively; drag-and-drop reads directories in batches until none are left, so large folders are not cut short.
  • Drag files anywhere onto the page. PDFs and images dropped here become attachments.
  • Load from URLs: paste links to the three files (raw GitHub links work).
  • Try sample: SWMM5 / SWMM6: the same site drainage model run with each engine.
  • From another app, through the bridge (section 7).

Files are paired by name, following the rules used across your other apps:

You load The wizard does
a.inp, a.rpt, a.out Loads the set with all three paired.
a.inp Loads the model alone (summary, options and map; no results).
model.inp, results.out Pairs the single .out even though the names differ.
model.inp, other.rpt, model.rpt Pairs model.rpt: a matching name beats a lone file.
a.inp, b.inp, a.rpt Shows a list of models; you pick one and its .rpt and .out come with it.
x.rpt or x.out alone Attaches it to the model already loaded.
EXAM1.INP, exam1.OUT Pairs them: extensions and names are compared without case.

The Model set card then shows what was read: for each file its size and the first characters of its SHA-256 fingerprint; for the .inp the node, link and subcatchment counts and flow units; for the .rpt the engine and version, the number of sections, warnings and errors, and the flow routing continuity error; for the .out the format version, the number of reporting periods and the reporting interval. It also warns you when something does not add up:

  • the .rpt and the .out name different engines (they may come from different runs);
  • the flow units in the .inp and the .out differ;
  • the engine reported errors;
  • the .inp uses SWMM6-only input but the .rpt came from SWMM5, which rejects or ignores it;
  • the model runs in SI units (the report keeps the engine’s units and says so on the cover);
  • the .rpt or .out is missing, and what you would gain by adding it.

The provenance line at the foot of the page lists each file with its fingerprint, the engine and version, the pdfcraft build and the wizard version.

3.2 Step 2 — Contents

Figure 3. Step 2: cover details, sections, hydrographs and the Use of AI text.

Figure 3. Step 2: cover details, sections, hydrographs and the Use of AI text.

Cover. Title (filled from the first [TITLE] line of the .inp, which you can overwrite), subtitle, project, client, prepared by, date and an optional cover note.

Sections. Eleven switches: cover, contents, model summary, network map, run summary, hydrographs, results tables, figures from apps, the .rpt appendix, the .inp appendix (off by default) and the Use of AI page.

Hydrographs. By default the report plots the five links with the highest peak flow and the five nodes with the greatest maximum depth, chosen from the .out series. You can change the number (one to eight) or list the link and node names you want; the card shows example names from the loaded .out.

Use of AI page. The default text says who made the report and how: that the person named on the cover chose the model, ran the engine and is responsible; that the wizard copied the numbers with deterministic code and pdfcraft finished the file; and how the wizard itself was built and checked. Edit it to match what you did. Reset puts the default back.

3.3 Step 3 — Finish (pdfcraft)

Figure 4. Step 3: the pdfcraft engine, document options, attachments, security and splitting.

Figure 4. Step 3: the pdfcraft engine, document options, attachments, security and splitting.

pdfcraft engine. Shows whether the engine is loaded. It loads by itself on the first build. Load now loads it ahead of time; Load from a file… takes the .wasm or .wasm.gz from disk, for a copy of the wizard opened straight from a folder (a page opened from disk cannot fetch files).

Document. Bookmarks for every section and table; page labels (i, ii for the front pages, 1, 2 for the body, A-1 for the appendix); a running header and page-numbered footer. The header shows the report title on the left and the engine and version on the right; the footer shows the .inp file name on the left and the page number on the right. Each can be overwritten. Author and keywords go into the document properties. The watermark can be DRAFT, PRELIMINARY, FOR REVIEW or your own text.

Attachments. PDFs or images to bind in with the report, such as calculations, a cover letter or another app’s PDF export. Each is opened by pdfcraft as soon as you add it, so you see its page count, or that it needs a password (a password box then appears). Give each a bookmark title, choose before or after the report, and reorder them. pdfcraft keeps each attachment’s own bookmarks, links and form fields.

Security and splitting. A password to open the file, a permissions password, and whether copying and comments are allowed when a permissions password is set. Encryption is AES-256. You can also split the finished file at each top-level bookmark or every 10 or 25 pages.

3.4 Step 4 — Build and download

Figure 5. Step 4 after a build of the SWMM5 sample with a DRAFT watermark.

Figure 5. Step 4 after a build of the SWMM5 sample with a DRAFT watermark.

Build the PDF runs the five stages from section 2 and ticks them off. The run log on the right lists every pdfcraft step with its result. When it finishes, the Done box reports what pdfcraft read back from the saved file: the page count, the first and last page labels, the number of bookmarks and the security settings. The preview shows the first 24 pages as pdfcraft drew them from the saved file; click one to see it larger. Download PDF saves the file. If you split it, each part has its own button. If the model set came from another app that asked for the PDF back, a Send back button appears.

On the sample model, a 16-page report builds in about 2.3 seconds once the engine is loaded; a 64-page build with two attachments, a password and splitting took 3.7 seconds (headless Chromium on a two-core machine).

3.5 Export menu and the rest of the header

Export item What you get
Finished PDF The same file as Download PDF.
Report pages only The pages before pdfcraft ran: no bookmarks, labels, headers or security. Also offered when pdfcraft cannot load.
All .rpt tables as CSV Every table and continuity block from the .rpt, one after another, with group labels joined to column names.
pdfcraft script steps.json exactly as run, with passwords replaced by ••••. You can run it yourself with a native pdfcraft-cli.
pdfcraft run report pdfcraft’s own JSON report of the run: each step, its status and its result.

The header also holds the Dark / Light / Auto theme switch (the app opens dark), the ? button that reopens the welcome box and the seven-step tour, and Make a GIF, which records the app as you use it. Recording uses html2canvas and gif.js from cdnjs and falls back to a WebM video if the GIF encoder cannot load.

4. Reading the SWMM files

All three readers live in swmm-io.js. The wizard uses them, and other apps can load the same file, so every app reads results the same way.

4.1 Text encoding

SWMM5 and SWMM6 write text differently. SWMM 5.2.4 writes the cubic-feet symbol in “1000 ft³” as a single Latin-1 byte; OpenSWMM 6 writes UTF-8. The reader first tries strict UTF-8 and, if the file is not valid UTF-8, reads it as Windows-1252. Both engines’ reports then show the same characters.

4.2 The input file (.inp)

The reader splits the file at each [SECTION] header, drops anything after a semicolon, and splits each line on white space. From that it builds:

  • the [TITLE] lines and the [OPTIONS] table, including the flow units (CFS, GPM and MGD are US customary);
  • nodes from [JUNCTIONS], [OUTFALLS], [STORAGE] and [DIVIDERS], with invert and maximum depth;
  • links from [CONDUITS], [PUMPS], [ORIFICES], [WEIRS] and [OUTLETS], with end nodes, conduit length and roughness, and shape and first dimension from [XSECTIONS];
  • subcatchments with gage, outlet, area, percent impervious, width and slope;
  • the map geometry: [COORDINATES], [VERTICES], [POLYGONS] and [SYMBOLS];
  • element counts, total subcatchment area and total conduit length;
  • a list of SWMM6-only input: the sections [VIRTUAL_JUNCTIONS], [2D_OPTIONS], [2D_MESH], [2D_CELLS], [2D_FACES], [2D_COUPLING] and [INLET_USAGE_2D], and the options SURCHARGE_METHOD DYNAMIC_SLOT, FLOW_ROUTING FV, FV_… and NODE_SPLIT.

4.3 The report file (.rpt)

Engine and version. Read from the banner at the top of the file. SWMM 5.2.4 writes EPA STORM WATER MANAGEMENT MODEL – VERSION 5.2 (Build 5.2.4); the reader takes the build number, 5.2.4. OpenSWMM 6 writes OPENSWMM ENGINE – VERSION 6.0.0-alpha.4. A version starting with 6 is SWMM6.

Sections. Both engines box each section title between two lines of asterisks. The reader splits the file at every box and recognises three kinds of body:

  • Results tables: a dashed rule, one to three header lines, another rule, the rows, and sometimes a final rule followed by a System total row (Outfall Loading, Washoff). A sentence before the first rule, such as “Surcharging occurs when water rises above the top of the highest conduit.”, is kept as the table’s note.
  • Input summaries (Raingage, Subcatchment, Node, Link, Cross Section Summary): header lines, one rule, then rows.
  • Key and value blocks: lines with dot leaders (“Total Precipitation …… 6.829 2.834”) or a colon (“Minimum Time Step : 3.86 sec”). The column headers of the continuity blocks, such as “Volume acre-feet” and “Depth inches”, come from the box lines around the title.

Warnings and errors are collected wherever they appear. The reader also keeps the analysis start and end times and the elapsed time, the Analysis Options block, and the continuity error of each continuity block (runoff quantity, flow routing, groundwater, runoff quality, quality routing).

How a table is split into columns

SWMM writes tables as fixed-position text with right-aligned headers spread over several lines, and some rows have blank cells. The reader handles that with these rules, in this order:

  • Columns are anchored on the rows that have the most values. Rows with fewer values (a pump in the Link Flow Summary has no velocity or Max/Full columns) are placed by position, so blank cells stay blank.
  • A word that falls in no column and sits one space after the previous word joins that word’s cell (“IDEAL PUMP” is one type).
  • A unit word one space after a number joins it (“15 min.” is one recording interval).
  • A days column followed by an hr:min column becomes one “Time of Max Occurrence (d hh:mm)” column with values such as “0 12:05”.
  • Header text is split into phrases at two or more spaces, and each phrase goes to the column it overlaps most.
  • A phrase over three or more columns is a group label (“Fraction of Time in Flow Class”). So is a phrase over two columns that each have their own bottom label (“Hours Full” over Both Ends, Upstream, Dnstream; “% Time Off Pump Curve” over Low and High).
  • A single-spaced phrase over two columns is split between them (“%Slope Rain Gage” becomes “%Slope” and “Rain Gage”).
  • A column left with no header at all is the tail of the column to its left and is merged into it.

A column whose values are all numbers is right-aligned in the report. A table the rules cannot square up is not forced: the section is printed as the engine wrote it, in a monospaced font.

4.4 The binary results file (.out)

SWMM5 and SWMM6 write the same binary layout. Only the version number differs: 52004 for SWMM 5.2.4 and 60000 for OpenSWMM 6.0.0-alpha.4. All integers are 4-byte little-endian; values are 4-byte floats; dates are 8-byte floats counting days from 30 December 1899.

Part Where Contents
Header start of file Seven integers: magic number 516114522, version, flow units code (0 CFS, 1 GPM, 2 MGD, 3 CMS, 4 LPS, 5 MLD), and the counts of reported subcatchments, nodes, links and pollutants.
Names at the ID position Each name as a length followed by its characters: subcatchments, nodes, links, pollutants; then one units code per pollutant.
Properties at the property position Subcatchment area; node type, invert and maximum depth; link type, upstream and downstream offsets, maximum depth and length.
Variable lists after the properties For subcatchments, nodes, links and the system: how many variables are saved and their codes. Pollutant variables follow the fixed ones.
Time base after the lists Start date (8-byte float) and reporting step in seconds.
Periods at the output position Per reporting period: the date, then every subcatchment’s variables, every node’s, every link’s, and the system variables.
Epilog last 24 bytes ID position, property position, output position, number of periods, error code, and the magic number again.

The reader starts from the epilog, since it holds the positions of everything else. It refuses a file whose leading or trailing magic number is wrong (a run that stopped early leaves no epilog), whose counts are impossible, or which is shorter than its own period count says. Each period is 8 + 4 × (subcatchments × their variables + nodes × theirs + links × theirs + system variables) bytes long, so one value is read for every period by stepping through the file at that stride. The variable counts are read from the file, never assumed, so pollutants are handled.

Only elements that the .inp [REPORT] section asked to save are in the .out. The OpenSWMM 6 Cedar Glen deck saves none, for example, so its .out holds system variables only; the report then draws the system hydrograph and says why there are no node or link plots.

5. Laying out the report

5.1 Pages, type and the PDF writer

Pages are US Letter, 8.5 by 11 inches (612 by 792 points), with 0.75-inch side margins. Text is set in Helvetica, Helvetica-Bold and Courier, three of the fourteen fonts every PDF reader has, so no font is embedded and all text can be searched and selected. Characters are written in the WinAnsi encoding; a few symbols outside it are spelled out (a minus sign becomes a hyphen, ≤ becomes <=). Character widths come from pdfcraft’s own font tables, so text that the wizard measures to fit a column is the width pdfcraft and PDF readers will draw. Page contents are Flate-compressed with the browser’s built-in compressor. Figures from apps are JPEG images embedded as they are.

Figure 6. Cover and Model Summary of the SWMM5 sample report. Figure 6. Cover and Model Summary of the SWMM5 sample report.

Figure 6. Cover and Model Summary of the SWMM5 sample report.

5.2 The sections, in order

Section What is on it Where the numbers come from
Cover Title, SWMM5/SWMM6 label, project, client, prepared by, date, engine, flow units; each source file with its size and SHA-256; a small network map. .inp [TITLE], .rpt banner, file bytes
Contents Every section and table with its page label and dot leaders. Each line is a link. Drawn last, once page numbers are known. the layout
Model Summary Key results box; element counts with total area and conduit length; simulation options side by side; a note when SWMM6-only input is present. .rpt for key results; .inp for counts and options (the counts and totals are the only figures the wizard adds up itself)
Network Map Subcatchment polygons, links coloured by Max/Full Flow (below 0.50 blue, 0.50 to 0.80 green, 0.80 to 1.00 amber, above 1.00 red, not reported grey), outfalls as triangles, storage as squares, node labels up to 60 nodes. .inp map sections; .rpt Link Flow Summary
Run Summary Engine, banner, run times, .out format; warnings and errors; each continuity block; routing time step summary; critical elements, instability indexes, nonconverging nodes, control actions. .rpt, .out header
Hydrographs System flows with a rainfall strip on top; flow in the top links; depth in the top nodes. The peak of each series is in the legend. .out series
Results Tables Every other .rpt section, each with a bookmark. Group labels drawn across their columns; System rows in bold under a rule; notes kept. .rpt tables
Figures from Apps One figure per page with title, caption and the sending app’s name, version and web address. the bridge
Appendix A (B) The full .rpt (and optionally the .inp) in 6.3-point Courier; long lines wrapped. the files
Use of AI The text from Step 2. you

Key results are copied from the .rpt: the runoff and flow routing continuity errors, total precipitation, the peak total outfall flow from the System row of the Outfall Loading Summary, the number of flooded nodes, the number of surcharged conduits, and the link with the highest Max/Full Flow.

5.3 Tables

Tables start at 8-point type. When a table is wider than the page, the type shrinks in small steps down to 6.2 points; if it is still too wide, the table is split into column groups with the first column (the element name) repeated in each. Header rows repeat on every page a table continues onto, under a “(continued)” line. A table shorter than about 4.5 inches is moved whole to the next page rather than split,, and a table’s title is kept on the same page as the start of the table.

5.4 Charts

Figure 7. Hydrographs page and a Results Tables page from the same report. Figure 7. Hydrographs page and a Results Tables page from the same report.

Figure 7. Hydrographs page and a Results Tables page from the same report.

Charts are drawn as vector lines on the page, not pictures. The time axis is elapsed hours with round steps (from a quarter hour up to a year, at most ten intervals). The flow and depth axes use steps of 1, 2, 2.5 or 5 times a power of ten. Long series are thinned to 1,400 points by keeping the lowest and highest value in each slice of time, so no peak is lost. The rainfall strip is drawn as bars when the system rainfall is not zero.

Each chart plots values at the .out reporting interval. Peaks between reporting times can be higher than what is plotted, and the .rpt tables, which use every routing step, are the numbers to quote. The report prints this under the charts.

5.5 Page kinds and labels

Every page is tagged as front matter (cover, contents), body, appendix A or B, or back matter (Use of AI). pdfcraft turns those tags into page labels: i, ii for front matter; 1, 2, 3 for the body; A-1, A-2 for the appendix; the Use of AI page continues the body numbers. Attached files are labelled “Att 1-1”, “Att 1-2” and so on, so their pages never share a number with the report. A PDF reader shows these labels in its page box, and the contents page and footers use the same numbers.

6. Finishing with pdfcraft

6.1 What pdfcraft is

pdfcraft (github.com/storytold/pdfcraft) is an open-source PDF reader and editor written from scratch in Rust by the ArtCraft team, licensed MIT OR Apache-2.0. In October 2026 it describes itself as alpha software. Besides the desktop and web apps it has a command-line tool, pdfcraft-cli, that reaches every engine feature through a table of JSON-described tools: the WebAssembly build used here lists 170 of them. The wizard uses pdfcraft-cli as it is; nothing in pdfcraft was changed.

6.2 How it was built for the browser

pdfcraft-cli was compiled for wasm32-wasip1, the WebAssembly target that talks to its host through the WASI system interface, with –no-default-features to leave out its MCP server. pdfcraft already runs single-threaded on WebAssembly, so no source changes were needed. The resulting module is 22.7 MB (7.0 MB gzipped) and needs only 25 WASI functions from its host.

The build session could not download Rust’s prebuilt WebAssembly standard library, so the standard library was compiled from the Rust 1.97.0 source and the C library (wasi-libc) from its source. BUILD.md in the package gives both the normal one-line build and the exact steps used.

6.3 How it runs in the page

  • A Web Worker holds the engine, so the page stays responsive and a crash inside pdfcraft cannot take the page down.
  • The worker fetches the engine once per visit. It tries pdfcraft-cli.wasm.gz, then pdfcraft-cli-gz.wasm (the same gzipped bytes under a name every host serves), then the plain pdfcraft-cli.wasm. It recognises gzip by its first two bytes and inflates it with the browser’s built-in decompressor, checks that the result starts with the WebAssembly signature, and compiles it.
  • Every call creates a fresh instance of the compiled module with an in-memory folder mounted as /work. The wizard puts the report, the attachments and the script into that folder, pdfcraft writes its outputs there, and the worker hands every file in the folder back to the page. Nothing persists between calls.
  • The WASI host is browser_wasi_shim 0.4.2 with one correction. pdfcraft confines every path to /work (its –root option) and, to resolve paths, asks whether each part is a symbolic link. On a path that is not a link the answer must be the error EINVAL; the shim answered ENOTSUP (“not supported”), and pdfcraft stopped with “–root /work: Not supported”. The runner now answers EINVAL when the path exists, and the usual not-found error when it does not.

The same script run through this browser runner and through Node’s own WASI produced byte-identical PDFs.

6.4 The script

Each build is one call:

pdfcraft-cli run –script /work/steps.json –root /work –report

–report makes pdfcraft print one JSON document with every step’s number, tool, status and result, which the wizard shows as the run log. The steps, in order:

Tool When What the wizard asks for
doc_combine only with attachments Joins the attachments and the report in the order you set. pdfcraft adds one bookmark per file and opens encrypted attachments with their passwords.
doc_open always Opens the report (or the combined file) as document 1.
doc_set_info always Title, Author, Subject (with engine and version) and Keywords; pdfcraft also updates XMP metadata when present.
bookmark_rename with attachments Gives each file bookmark the title you typed.
bookmark_add always One bookmark per section and one per results table under it; nested under the report’s own bookmark when attachments are bound in.
page_number always The page labels from section 5.5.
doc_header_footer always For each run of body and appendix pages: header left and right, footer left, footer right “Page <<1>>” with the right starting number, 7.5-point grey type.
doc_watermark if chosen Your text at 12% opacity in red, on the report pages only.
doc_initial_view always Opens with the bookmarks panel showing, fit to width, with the title in the window bar.
doc_protect if a password is set Open password and/or permissions password (printing allowed; copying and comments as you chose); AES-256.
doc_save always Writes final.pdf as a full rewrite.
doc_open, doc_info, text_extract always Reopens final.pdf (with the open password) and reads back pages, labels, bookmarks and security, and the first page’s text.
page_render always Previews of up to the first 24 pages at 36 dpi.
doc_split if chosen Parts at each top-level bookmark, or every 10 or 25 pages.

A failure at or before the save stops the build and names the step; the report pages without pdfcraft’s finishing can still be downloaded. A failure in the read-back steps is flagged but the saved file is kept. The script you can export has every password replaced by ••••. A short excerpt from a build with one attachment and an open password:

{“tool”:”doc_combine”,”args”:{“paths”:[“report.pdf”,”att1.pdf”],”out”:”combined.pdf”}}

{“tool”:”doc_open”,”args”:{“path”:”combined.pdf”}}

{“tool”:”bookmark_add”,”args”:{“doc”:1,”title”:”Cover”,”page”:1,”parent”:[1]}}

{“tool”:”page_number”,”args”:{“doc”:1,”from”:1,”to”:2,”style”:”lower-roman”}}

{“tool”:”doc_watermark”,”args”:{“doc”:1,”text”:”DRAFT”,”opacity”:0.12,…}}

{“tool”:”doc_protect”,”args”:{“doc”:1,”open_password”:”••••”}}

{“tool”:”doc_save”,”args”:{“doc”:1,”path”:”final.pdf”,”full”:true}}

{“tool”:”page_render”,”args”:{“doc”:2,”page”:1,”dpi”:36},”out”:”preview-1.png”}

7. Using the wizard from your other apps

Any app can send its model set, charts and PDFs to the wizard with swmm-pdf-bridge.js. The wizard opens in a new tab with everything loaded; the person checks the contents and builds. If the app asks for it, the finished PDF comes back to the app.

7.1 The call

https://YOUR-WIZARD-SITE/swmm-pdf-bridge.js

<script>

SwmmPdfBridge.open({

app: { name: “My App”, version: “2.1”, url: location.href },

title: “Cedar Glen – 10-year storm”,

files: { inp: { name: “model.inp”, text: inpText },

rpt: { name: “model.rpt”, text: rptText },

out: { name: “model.out”, buffer: outArrayBuffer } },

figures: [{ title: “Peak HGL”, caption: “10-year storm”, image: myCanvas }],

pdfs: [{ name: “calcs.pdf”, bytes: pdfArrayBuffer, where: “after” }],

options: { project: “Cedar Glen”, preparedBy: “R. Dickinson”, watermark: “DRAFT” },

returnPdf: true

}, { wizardUrl: “https://YOUR-WIZARD-SITE/&#8221;,

onDone: ({ name, bytes }) => { /* save or show the PDF */ } });

</script>

Field Meaning
app Name, version and page address of the sending app. Printed under each figure and shown in the wizard.
title Report title; overrides the .inp [TITLE].
files.inp, files.rpt Text, or a File/Blob. Either may be left out.
files.out An ArrayBuffer, typed array or File/Blob.
figures Each with title, caption and image: a canvas, an SVG element, an <img>, an image URL or data URL, or a Blob. The bridge draws each on a white ground and turns it into a JPEG.
pdfs PDFs to attach, each with name, bytes, where (“before” or “after”) and an optional bookmark title.
options project, client, preparedBy, subtitle, watermark: filled into Steps 2 and 3.
returnPdf When true, the wizard shows a “Send back” button and the PDF arrives in onDone.

SwmmPdfBridge.button(container, getPayload, options) adds a ready-made “PDF report” button to an app’s export cluster. It opens the wizard tab inside the click, so pop-up blockers allow it, and gathers the payload afterwards.

7.2 What happens between the two tabs

  • The app opens the wizard’s address with #bridge on the end in a new tab.
  • The wizard sees #bridge and posts swmmpdf:ready to the tab that opened it every half second, for up to a minute.
  • The bridge answers the first ready with one swmmpdf:load message carrying the files, figures and PDFs. Large buffers are transferred, not copied.
  • The wizard loads the set and shows a “Received from an app” card with the app’s name, version, web address and what it sent.
  • After the build, Send back posts swmmpdf:done with the file name and bytes to the app, addressed to the app’s own web address only.

7.3 Safety rules

  • The wizard accepts a swmmpdf:load message only from the window that opened it (or the page that frames it).
  • What another site sends is treated as data. Text files are read as text. Figures must be JPEG images, which the wizard checks by reading their frame header; nothing sent is ever run as code. At most 60 figures and 20 PDFs are accepted, and file names are cleaned.
  • Every name that comes from a file or a message is escaped before it is shown on the page. SWMM element names cannot contain spaces, so the test case is a space-free tag such as <img/src=x/onerror=…>.

7.4 The demo app

bridge-demo.html loads the SWMM5 sample, draws the outfall hydrograph on a canvas and a trunk profile as SVG, and sends both with the model set. Its second button asks for the PDF back and offers it for download when it arrives. In testing the round trip delivered a 122 KB, 18-page PDF back to the demo app.

8. Putting it online

The wizard needs a web server only to hand out its files; it does no work on the server. Any static host works:

  • Netlify: drag the swmm-pdf-wizard folder onto Netlify Drop, or run netlify deploy –dir=. –prod in the folder.
  • GitHub Pages: commit the folder to a repository and turn Pages on.

No special server headers are needed. The engine is single-threaded, so the cross-origin isolation headers that threaded WebAssembly requires do not apply. The page must be served over http or https: a copy opened straight from disk cannot fetch its engine, which is what Load from a file… is for.

8.1 The claude.ai copy

A copy is also published as a private Claude artifact. It is the same page with three differences: the engine file is named pdfcraft-cli-gz.wasm, because artifacts do not serve .gz files; the samples are loaded from one JSON file per engine; and saving a file goes through the viewer’s own download prompt. Use the Netlify or GitHub Pages copy for the bridge, because a page inside claude.ai cannot reliably be opened by other sites.

8.2 Updating pdfcraft

pdfcraft moves fast. To take a newer version, rebuild pdfcraft-cli for wasm32-wasip1 as BUILD.md describes and replace the two engine files. Nothing else changes as long as the tool names and arguments the wizard uses still exist; the run log names any step a new version rejects.

9. How it was checked

The checks are mechanical. Each compares the wizard’s output with something it did not produce itself.

Check How Result
Readers against the engines SWMM 5.2.4 and OpenSWMM 6.0.0-alpha.4 were built from source and run on six models (eight runs). For each node, the “Reported Max Depth” in the .rpt was compared with the maximum of that node’s depth series read from the .out. 7 runs with node results, 56 nodes; worst difference 0.0045 ft, within the .rpt’s two-decimal rounding.
The check can fail The same comparison with the wrong .out variable (head instead of depth). Fails, with a difference of 4,973 ft.
Node inverts .out property block against the .inp. Worst difference 0.0002 ft.
Engine detection Banner and .out version on both engines. SWMM 5.2.4 / 52004 and OpenSWMM 6.0.0-alpha.4 / 60000.
PDF structure qpdf –check on every finished file; bookmarks and page labels read back with qpdf; metadata with poppler pdfinfo; text with pdftotext. No errors; outline, labels i…10 and A-1…A-4, and metadata as planned.
Encryption A protected file opened with qpdf and poppler using the password. AES-256 (revision 6); opens only with the password.
Browser runner The same pdfcraft script run in the browser WASI shim and in Node’s WASI. Byte-identical PDFs.
End to end Headless Chromium: both samples; two attachments, one password-protected; open password; split; CSV and script exports; the bridge round trip. All passed; no errors in the browser console; exported script had passwords masked; 3 split parts.
Colour contrast The app’s colour tokens measured in both themes. 30 pairs, none below target; lowest 4.83:1.

9.1 Not yet checked by a person

  • Every .rpt table layout the reader can meet. LID, groundwater and RDII reports and SWMM 5.1 files were not tested; LID Performance, pollutant and pump tables were.
  • Large models. The largest tested was an OpenSWMM 6 deck with 390 nodes (370 of them virtual junctions) and 389 links, which made a 91-page report.
  • Browsers other than Chromium.

10. What the wizard does not do

  • It does not run SWMM. It reports on files an engine has already written. Run SWMM5 or OpenSWMM 6 first.
  • It does not convert units. A model run in SI units is reported in SI, with a note on the cover.
  • Hydrographs are reporting-interval values; peaks between reports can be higher. Quote the .rpt tables.
  • It does not interpret results. The key results box copies numbers; it does not judge them.
  • pdfcraft is alpha software. The wizard checks the saved file by reopening it with pdfcraft, but open the PDF in your usual reader before you send it.
  • The bridge works between tabs in the same browser. It is not a web service another server can call.

11. Use of AI

SWMM PDF Wizard and this document were made with Claude, Anthropic’s AI model, in a Claude Code session on 16 and 17 October 2026, under the direction of Robert (Bob) Dickinson. He asked for a SWMM5 and SWMM6 PDF wizard built on pdfcraft for use with his apps, and for this description of how it works. Responsibility for both is his.

What Claude did. It built the Rust standard library and wasi-libc from source, compiled pdfcraft-cli 0.8.0 unchanged to WebAssembly, and wrote the browser runner with its one WASI correction, the .inp, .rpt and .out readers, the PDF writer and report layout, the pdfcraft script planner, the wizard’s interface, the bridge and its demo, the tests, the README and BUILD notes, and this document, including its screenshots and figures.

How the work was checked. By the mechanical comparisons in section 9: engine output against the readers, independent PDF tools against the finished files, and a headless browser running the whole wizard. No person has yet reviewed the items listed in section 9.1, and no person has checked this document against the code line by line.

Leave a Comment

Translate »
Scroll to Top

Discover more from Reference Hub for EPA SWMM5, SWMM6, and ICM InfoWorks Modeling

Subscribe now to keep reading and get access to the full archive.

Continue reading