std/netpbm: add new package
diff --git a/doc/changelog.md b/doc/changelog.md
index fac9ab1..5d42496 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -4,6 +4,7 @@
 ## Work In Progress
 
 - Added `std/jpeg`.
+- Added `std/netpbm`.
 - Changed `lzw.set_literal_width` to `lzw.set_quirk`.
 - Changed `set_quirk_enabled!(quirk: u32, enabled: bool)` to `set_quirk!(key:
   u32, value: u64) status`.
diff --git a/doc/std/README.md b/doc/std/README.md
index 8294c62..58466bf 100644
--- a/doc/std/README.md
+++ b/doc/std/README.md
@@ -47,13 +47,16 @@
 
 - `ADLER32: BASE`
 - `BMP:     BASE`
+- `BZIP2:   BASE`
 - `CBOR:    BASE`
 - `CRC32:   BASE`
 - `DEFLATE: BASE`
 - `GIF:     BASE, LZW`
 - `GZIP:    BASE, CRC32, DEFLATE`
+- `JPEG:    BASE`
 - `JSON:    BASE`
 - `LZW:     BASE`
+- `NETPBM:  BASE`
 - `NIE:     BASE`
 - `PNG:     BASE, ADLER32, CRC32, DEFLATE, ZLIB`
 - `TGA:     BASE`
diff --git a/doc/std/image-decoders.md b/doc/std/image-decoders.md
index ec82743..a51ac1b 100644
--- a/doc/std/image-decoders.md
+++ b/doc/std/image-decoders.md
@@ -125,6 +125,7 @@
 - [std/bmp](/std/bmp)
 - [std/gif](/std/gif)
 - [std/jpeg](/std/jpeg)
+- [std/netpbm](/std/netpbm)
 - [std/nie](/std/nie)
 - [std/png](/std/png)
 - [std/tga](/std/tga)
diff --git a/example/convert-to-nia/convert-to-nia.c b/example/convert-to-nia/convert-to-nia.c
index 6993b6c..41ed667 100644
--- a/example/convert-to-nia/convert-to-nia.c
+++ b/example/convert-to-nia/convert-to-nia.c
@@ -65,6 +65,7 @@
 #define WUFFS_CONFIG__MODULE__GIF
 #define WUFFS_CONFIG__MODULE__JPEG
 #define WUFFS_CONFIG__MODULE__LZW
+#define WUFFS_CONFIG__MODULE__NETPBM
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
@@ -139,6 +140,7 @@
   wuffs_bmp__decoder bmp;
   wuffs_gif__decoder gif;
   wuffs_jpeg__decoder jpeg;
+  wuffs_netpbm__decoder netpbm;
   wuffs_nie__decoder nie;
   wuffs_png__decoder png;
   wuffs_tga__decoder tga;
@@ -304,6 +306,16 @@
               &g_potential_decoders.nie);
       return NULL;
 
+    case WUFFS_BASE__FOURCC__NPBM:
+      status = wuffs_netpbm__decoder__initialize(
+          &g_potential_decoders.netpbm, sizeof g_potential_decoders.netpbm,
+          WUFFS_VERSION, WUFFS_INITIALIZE__DEFAULT_OPTIONS);
+      TRY(wuffs_base__status__message(&status));
+      g_image_decoder =
+          wuffs_netpbm__decoder__upcast_as__wuffs_base__image_decoder(
+              &g_potential_decoders.netpbm);
+      return NULL;
+
     case WUFFS_BASE__FOURCC__PNG:
       status = wuffs_png__decoder__initialize(
           &g_potential_decoders.png, sizeof g_potential_decoders.png,
diff --git a/example/imageviewer/imageviewer.cc b/example/imageviewer/imageviewer.cc
index 2da70b0..66d34d8 100644
--- a/example/imageviewer/imageviewer.cc
+++ b/example/imageviewer/imageviewer.cc
@@ -81,6 +81,7 @@
 #define WUFFS_CONFIG__MODULE__GIF
 #define WUFFS_CONFIG__MODULE__JPEG
 #define WUFFS_CONFIG__MODULE__LZW
+#define WUFFS_CONFIG__MODULE__NETPBM
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
diff --git a/example/sdl-imageviewer/sdl-imageviewer.cc b/example/sdl-imageviewer/sdl-imageviewer.cc
index 17d3acf..02e045c 100644
--- a/example/sdl-imageviewer/sdl-imageviewer.cc
+++ b/example/sdl-imageviewer/sdl-imageviewer.cc
@@ -79,6 +79,7 @@
 #define WUFFS_CONFIG__MODULE__GIF
 #define WUFFS_CONFIG__MODULE__JPEG
 #define WUFFS_CONFIG__MODULE__LZW
+#define WUFFS_CONFIG__MODULE__NETPBM
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
diff --git a/internal/cgen/auxiliary/image.cc b/internal/cgen/auxiliary/image.cc
index c3ece90..fcb3406 100644
--- a/internal/cgen/auxiliary/image.cc
+++ b/internal/cgen/auxiliary/image.cc
@@ -81,6 +81,11 @@
       return wuffs_nie__decoder::alloc_as__wuffs_base__image_decoder();
 #endif
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NETPBM)
+    case WUFFS_BASE__FOURCC__NPBM:
+      return wuffs_netpbm__decoder::alloc_as__wuffs_base__image_decoder();
+#endif
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG)
     case WUFFS_BASE__FOURCC__PNG: {
       auto dec = wuffs_png__decoder::alloc_as__wuffs_base__image_decoder();
diff --git a/internal/cgen/auxiliary/image.hh b/internal/cgen/auxiliary/image.hh
index 01ff8be..3a32ba4 100644
--- a/internal/cgen/auxiliary/image.hh
+++ b/internal/cgen/auxiliary/image.hh
@@ -91,6 +91,7 @@
   //  - WUFFS_BASE__FOURCC__GIF
   //  - WUFFS_BASE__FOURCC__JPEG
   //  - WUFFS_BASE__FOURCC__NIE
+  //  - WUFFS_BASE__FOURCC__NPBM
   //  - WUFFS_BASE__FOURCC__PNG
   //  - WUFFS_BASE__FOURCC__TGA
   //  - WUFFS_BASE__FOURCC__WBMP
diff --git a/internal/cgen/base/magic-submodule.c b/internal/cgen/base/magic-submodule.c
index 47fabee..a2ce9e9 100644
--- a/internal/cgen/base/magic-submodule.c
+++ b/internal/cgen/base/magic-submodule.c
@@ -157,6 +157,8 @@
       {+0x47494620, "\x03\x47\x49\x46\x38"},  // GIF
       {+0x54494646, "\x03\x49\x49\x2A\x00"},  // TIFF (little-endian)
       {+0x54494646, "\x03\x4D\x4D\x00\x2A"},  // TIFF (big-endian)
+      {+0x4E50424D, "\x02\x50\x35\x0A"},      // NPBM (P5; *.pgm)
+      {+0x4E50424D, "\x02\x50\x36\x0A"},      // NPBM (P6; *.ppm)
       {-0x52494646, "\x03\x52\x49\x46\x46"},  // RIFF
       {+0x4E494520, "\x02\x6E\xC3\xAF"},      // NIE
       {+0x514F4920, "\x03\x71\x6F\x69\x66"},  // QOI
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index 8f61f2e..d0c3215 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -59,12 +59,12 @@
 	{"MTIM", "Modification Time"},
 	{"MP3 ", "MPEG-1 Audio Layer III"},
 	{"NIE ", "Naive Image"},
+	{"NPBM", "Netpbm (Portable Anymap)"},
 	{"OFS2", "Offset (2-Dimensional)"},
 	{"OTF ", "Open Type Format"},
 	{"PDF ", "Portable Document Format"},
 	{"PHYD", "Physical Dimensions"},
 	{"PNG ", "Portable Network Graphics"},
-	{"PNM ", "Portable Anymap"},
 	{"PS  ", "PostScript"},
 	{"QOI ", "Quite OK Image"},
 	{"RAC ", "Random Access Compression"},
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 2d10520..0b4f507 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -666,6 +666,9 @@
 // Naive Image.
 #define WUFFS_BASE__FOURCC__NIE 0x4E494520
 
+// Netpbm (Portable Anymap).
+#define WUFFS_BASE__FOURCC__NPBM 0x4E50424D
+
 // Offset (2-Dimensional).
 #define WUFFS_BASE__FOURCC__OFS2 0x4F465332
 
@@ -681,9 +684,6 @@
 // Portable Network Graphics.
 #define WUFFS_BASE__FOURCC__PNG 0x504E4720
 
-// Portable Anymap.
-#define WUFFS_BASE__FOURCC__PNM 0x504E4D20
-
 // PostScript.
 #define WUFFS_BASE__FOURCC__PS 0x50532020
 
@@ -9322,6 +9322,328 @@
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__JSON) || defined(WUFFS_NONMONOLITHIC)
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NETPBM) || defined(WUFFS_NONMONOLITHIC)
+
+// ---------------- Status Codes
+
+extern const char wuffs_netpbm__error__bad_header[];
+extern const char wuffs_netpbm__error__truncated_input[];
+extern const char wuffs_netpbm__error__unsupported_netpbm_file[];
+
+// ---------------- Public Consts
+
+#define WUFFS_NETPBM__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0
+
+// ---------------- Struct Declarations
+
+typedef struct wuffs_netpbm__decoder__struct wuffs_netpbm__decoder;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------- Public Initializer Prototypes
+
+// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self,
+// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)".
+//
+// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version.
+// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options.
+
+wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+wuffs_netpbm__decoder__initialize(
+    wuffs_netpbm__decoder* self,
+    size_t sizeof_star_self,
+    uint64_t wuffs_version,
+    uint32_t options);
+
+size_t
+sizeof__wuffs_netpbm__decoder();
+
+// ---------------- Allocs
+
+// These functions allocate and initialize Wuffs structs. They return NULL if
+// memory allocation fails. If they return non-NULL, there is no need to call
+// wuffs_foo__bar__initialize, but the caller is responsible for eventually
+// calling free on the returned pointer. That pointer is effectively a C++
+// std::unique_ptr<T, decltype(&free)>.
+
+wuffs_netpbm__decoder*
+wuffs_netpbm__decoder__alloc();
+
+static inline wuffs_base__image_decoder*
+wuffs_netpbm__decoder__alloc_as__wuffs_base__image_decoder() {
+  return (wuffs_base__image_decoder*)(wuffs_netpbm__decoder__alloc());
+}
+
+// ---------------- Upcasts
+
+static inline wuffs_base__image_decoder*
+wuffs_netpbm__decoder__upcast_as__wuffs_base__image_decoder(
+    wuffs_netpbm__decoder* p) {
+  return (wuffs_base__image_decoder*)p;
+}
+
+// ---------------- Public Function Prototypes
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__set_quirk(
+    wuffs_netpbm__decoder* self,
+    uint32_t a_key,
+    uint64_t a_value);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__decode_image_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__decode_frame_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__decode_frame(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32
+wuffs_netpbm__decoder__frame_dirty_rect(
+    const wuffs_netpbm__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_netpbm__decoder__num_animation_loops(
+    const wuffs_netpbm__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_netpbm__decoder__num_decoded_frame_configs(
+    const wuffs_netpbm__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_netpbm__decoder__num_decoded_frames(
+    const wuffs_netpbm__decoder* self);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__restart_frame(
+    wuffs_netpbm__decoder* self,
+    uint64_t a_index,
+    uint64_t a_io_position);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_netpbm__decoder__set_report_metadata(
+    wuffs_netpbm__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__tell_me_more(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__more_information* a_minfo,
+    wuffs_base__io_buffer* a_src);
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64
+wuffs_netpbm__decoder__workbuf_len(
+    const wuffs_netpbm__decoder* self);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+// ---------------- Struct Definitions
+
+// These structs' fields, and the sizeof them, are private implementation
+// details that aren't guaranteed to be stable across Wuffs versions.
+//
+// See https://en.wikipedia.org/wiki/Opaque_pointer#C
+
+#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
+
+struct wuffs_netpbm__decoder__struct {
+  // Do not access the private_impl's or private_data's fields directly. There
+  // is no API/ABI compatibility or safety guarantee if you do so. Instead, use
+  // the wuffs_foo__bar__baz functions.
+  //
+  // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct
+  // can be stack allocated when WUFFS_IMPLEMENTATION is defined.
+
+  struct {
+    uint32_t magic;
+    uint32_t active_coroutine;
+    wuffs_base__vtable vtable_for__wuffs_base__image_decoder;
+    wuffs_base__vtable null_vtable;
+
+    uint32_t f_pixfmt;
+    uint32_t f_width;
+    uint32_t f_height;
+    uint32_t f_max_value;
+    uint8_t f_call_sequence;
+    uint64_t f_frame_config_io_position;
+    uint32_t f_dst_x;
+    uint32_t f_dst_y;
+    wuffs_base__pixel_swizzler f_swizzler;
+
+    uint32_t p_decode_image_config[1];
+    uint32_t p_do_decode_image_config[1];
+    uint32_t p_decode_frame_config[1];
+    uint32_t p_do_decode_frame_config[1];
+    uint32_t p_decode_frame[1];
+    uint32_t p_do_decode_frame[1];
+  } private_impl;
+
+#ifdef __cplusplus
+#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
+  using unique_ptr = std::unique_ptr<wuffs_netpbm__decoder, decltype(&free)>;
+
+  // On failure, the alloc_etc functions return nullptr. They don't throw.
+
+  static inline unique_ptr
+  alloc() {
+    return unique_ptr(wuffs_netpbm__decoder__alloc(), &free);
+  }
+
+  static inline wuffs_base__image_decoder::unique_ptr
+  alloc_as__wuffs_base__image_decoder() {
+    return wuffs_base__image_decoder::unique_ptr(
+        wuffs_netpbm__decoder__alloc_as__wuffs_base__image_decoder(), &free);
+  }
+#endif  // defined(WUFFS_BASE__HAVE_UNIQUE_PTR)
+
+#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION)
+  // Disallow constructing or copying an object via standard C++ mechanisms,
+  // e.g. the "new" operator, as this struct is intentionally opaque. Its total
+  // size and field layout is not part of the public, stable, memory-safe API.
+  // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and
+  // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as
+  // their first argument) rather than tweaking bar.private_impl.qux fields.
+  //
+  // In C, we can just leave wuffs_foo__bar as an incomplete type (unless
+  // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in
+  // order to provide convenience methods. These forward on "this", so that you
+  // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)".
+  wuffs_netpbm__decoder__struct() = delete;
+  wuffs_netpbm__decoder__struct(const wuffs_netpbm__decoder__struct&) = delete;
+  wuffs_netpbm__decoder__struct& operator=(
+      const wuffs_netpbm__decoder__struct&) = delete;
+#endif  // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION)
+
+#if !defined(WUFFS_IMPLEMENTATION)
+  // As above, the size of the struct is not part of the public API, and unless
+  // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap
+  // allocated, not stack allocated. Its size is not intended to be known at
+  // compile time, but it is unfortunately divulged as a side effect of
+  // defining C++ convenience methods. Use "sizeof__T()", calling the function,
+  // instead of "sizeof T", invoking the operator. To make the two values
+  // different, so that passing the latter will be rejected by the initialize
+  // function, we add an arbitrary amount of dead weight.
+  uint8_t dead_weight[123000000];  // 123 MB.
+#endif  // !defined(WUFFS_IMPLEMENTATION)
+
+  inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+  initialize(
+      size_t sizeof_star_self,
+      uint64_t wuffs_version,
+      uint32_t options) {
+    return wuffs_netpbm__decoder__initialize(
+        this, sizeof_star_self, wuffs_version, options);
+  }
+
+  inline wuffs_base__image_decoder*
+  upcast_as__wuffs_base__image_decoder() {
+    return (wuffs_base__image_decoder*)this;
+  }
+
+  inline wuffs_base__status
+  set_quirk(
+      uint32_t a_key,
+      uint64_t a_value) {
+    return wuffs_netpbm__decoder__set_quirk(this, a_key, a_value);
+  }
+
+  inline wuffs_base__status
+  decode_image_config(
+      wuffs_base__image_config* a_dst,
+      wuffs_base__io_buffer* a_src) {
+    return wuffs_netpbm__decoder__decode_image_config(this, a_dst, a_src);
+  }
+
+  inline wuffs_base__status
+  decode_frame_config(
+      wuffs_base__frame_config* a_dst,
+      wuffs_base__io_buffer* a_src) {
+    return wuffs_netpbm__decoder__decode_frame_config(this, a_dst, a_src);
+  }
+
+  inline wuffs_base__status
+  decode_frame(
+      wuffs_base__pixel_buffer* a_dst,
+      wuffs_base__io_buffer* a_src,
+      wuffs_base__pixel_blend a_blend,
+      wuffs_base__slice_u8 a_workbuf,
+      wuffs_base__decode_frame_options* a_opts) {
+    return wuffs_netpbm__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts);
+  }
+
+  inline wuffs_base__rect_ie_u32
+  frame_dirty_rect() const {
+    return wuffs_netpbm__decoder__frame_dirty_rect(this);
+  }
+
+  inline uint32_t
+  num_animation_loops() const {
+    return wuffs_netpbm__decoder__num_animation_loops(this);
+  }
+
+  inline uint64_t
+  num_decoded_frame_configs() const {
+    return wuffs_netpbm__decoder__num_decoded_frame_configs(this);
+  }
+
+  inline uint64_t
+  num_decoded_frames() const {
+    return wuffs_netpbm__decoder__num_decoded_frames(this);
+  }
+
+  inline wuffs_base__status
+  restart_frame(
+      uint64_t a_index,
+      uint64_t a_io_position) {
+    return wuffs_netpbm__decoder__restart_frame(this, a_index, a_io_position);
+  }
+
+  inline wuffs_base__empty_struct
+  set_report_metadata(
+      uint32_t a_fourcc,
+      bool a_report) {
+    return wuffs_netpbm__decoder__set_report_metadata(this, a_fourcc, a_report);
+  }
+
+  inline wuffs_base__status
+  tell_me_more(
+      wuffs_base__io_buffer* a_dst,
+      wuffs_base__more_information* a_minfo,
+      wuffs_base__io_buffer* a_src) {
+    return wuffs_netpbm__decoder__tell_me_more(this, a_dst, a_minfo, a_src);
+  }
+
+  inline wuffs_base__range_ii_u64
+  workbuf_len() const {
+    return wuffs_netpbm__decoder__workbuf_len(this);
+  }
+
+#endif  // __cplusplus
+};  // struct wuffs_netpbm__decoder__struct
+
+#endif  // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION)
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NETPBM) || defined(WUFFS_NONMONOLITHIC)
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NIE) || defined(WUFFS_NONMONOLITHIC)
 
 // ---------------- Status Codes
@@ -11363,6 +11685,7 @@
   //  - WUFFS_BASE__FOURCC__GIF
   //  - WUFFS_BASE__FOURCC__JPEG
   //  - WUFFS_BASE__FOURCC__NIE
+  //  - WUFFS_BASE__FOURCC__NPBM
   //  - WUFFS_BASE__FOURCC__PNG
   //  - WUFFS_BASE__FOURCC__TGA
   //  - WUFFS_BASE__FOURCC__WBMP
@@ -16947,6 +17270,8 @@
       {+0x47494620, "\x03\x47\x49\x46\x38"},  // GIF
       {+0x54494646, "\x03\x49\x49\x2A\x00"},  // TIFF (little-endian)
       {+0x54494646, "\x03\x4D\x4D\x00\x2A"},  // TIFF (big-endian)
+      {+0x4E50424D, "\x02\x50\x35\x0A"},      // NPBM (P5; *.pgm)
+      {+0x4E50424D, "\x02\x50\x36\x0A"},      // NPBM (P6; *.ppm)
       {-0x52494646, "\x03\x52\x49\x46\x46"},  // RIFF
       {+0x4E494520, "\x02\x6E\xC3\xAF"},      // NIE
       {+0x514F4920, "\x03\x71\x6F\x69\x66"},  // QOI
@@ -41707,6 +42032,1122 @@
 
 #endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__JSON)
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NETPBM)
+
+// ---------------- Status Codes Implementations
+
+const char wuffs_netpbm__error__bad_header[] = "#netpbm: bad header";
+const char wuffs_netpbm__error__truncated_input[] = "#netpbm: truncated input";
+const char wuffs_netpbm__error__unsupported_netpbm_file[] = "#netpbm: unsupported Netpbm file";
+const char wuffs_netpbm__note__internal_note_short_read[] = "@netpbm: internal note: short read";
+
+// ---------------- Private Consts
+
+// ---------------- Private Initializer Prototypes
+
+// ---------------- Private Function Prototypes
+
+static wuffs_base__status
+wuffs_netpbm__decoder__do_decode_image_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+static wuffs_base__status
+wuffs_netpbm__decoder__do_decode_frame_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+static wuffs_base__status
+wuffs_netpbm__decoder__do_decode_frame(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts);
+
+static wuffs_base__status
+wuffs_netpbm__decoder__swizzle(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src);
+
+// ---------------- VTables
+
+const wuffs_base__image_decoder__func_ptrs
+wuffs_netpbm__decoder__func_ptrs_for__wuffs_base__image_decoder = {
+  (wuffs_base__status(*)(void*,
+      wuffs_base__pixel_buffer*,
+      wuffs_base__io_buffer*,
+      wuffs_base__pixel_blend,
+      wuffs_base__slice_u8,
+      wuffs_base__decode_frame_options*))(&wuffs_netpbm__decoder__decode_frame),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__frame_config*,
+      wuffs_base__io_buffer*))(&wuffs_netpbm__decoder__decode_frame_config),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__image_config*,
+      wuffs_base__io_buffer*))(&wuffs_netpbm__decoder__decode_image_config),
+  (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_netpbm__decoder__frame_dirty_rect),
+  (uint32_t(*)(const void*))(&wuffs_netpbm__decoder__num_animation_loops),
+  (uint64_t(*)(const void*))(&wuffs_netpbm__decoder__num_decoded_frame_configs),
+  (uint64_t(*)(const void*))(&wuffs_netpbm__decoder__num_decoded_frames),
+  (wuffs_base__status(*)(void*,
+      uint64_t,
+      uint64_t))(&wuffs_netpbm__decoder__restart_frame),
+  (wuffs_base__status(*)(void*,
+      uint32_t,
+      uint64_t))(&wuffs_netpbm__decoder__set_quirk),
+  (wuffs_base__empty_struct(*)(void*,
+      uint32_t,
+      bool))(&wuffs_netpbm__decoder__set_report_metadata),
+  (wuffs_base__status(*)(void*,
+      wuffs_base__io_buffer*,
+      wuffs_base__more_information*,
+      wuffs_base__io_buffer*))(&wuffs_netpbm__decoder__tell_me_more),
+  (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_netpbm__decoder__workbuf_len),
+};
+
+// ---------------- Initializer Implementations
+
+wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT
+wuffs_netpbm__decoder__initialize(
+    wuffs_netpbm__decoder* self,
+    size_t sizeof_star_self,
+    uint64_t wuffs_version,
+    uint32_t options){
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (sizeof(*self) != sizeof_star_self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver);
+  }
+  if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) ||
+      (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) {
+    return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version);
+  }
+
+  if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) {
+    // The whole point of this if-check is to detect an uninitialized *self.
+    // We disable the warning on GCC. Clang-5.0 does not have this warning.
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+#endif
+    if (self->private_impl.magic != 0) {
+      return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed);
+    }
+#if !defined(__clang__) && defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+  } else {
+    if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) {
+      memset(self, 0, sizeof(*self));
+      options |= WUFFS_INITIALIZE__ALREADY_ZEROED;
+    } else {
+      memset(&(self->private_impl), 0, sizeof(self->private_impl));
+    }
+  }
+
+  self->private_impl.magic = WUFFS_BASE__MAGIC;
+  self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name =
+      wuffs_base__image_decoder__vtable_name;
+  self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers =
+      (const void*)(&wuffs_netpbm__decoder__func_ptrs_for__wuffs_base__image_decoder);
+  return wuffs_base__make_status(NULL);
+}
+
+wuffs_netpbm__decoder*
+wuffs_netpbm__decoder__alloc() {
+  wuffs_netpbm__decoder* x =
+      (wuffs_netpbm__decoder*)(calloc(sizeof(wuffs_netpbm__decoder), 1));
+  if (!x) {
+    return NULL;
+  }
+  if (wuffs_netpbm__decoder__initialize(
+      x, sizeof(wuffs_netpbm__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) {
+    free(x);
+    return NULL;
+  }
+  return x;
+}
+
+size_t
+sizeof__wuffs_netpbm__decoder() {
+  return sizeof(wuffs_netpbm__decoder);
+}
+
+// ---------------- Function Implementations
+
+// -------- func netpbm.decoder.set_quirk
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__set_quirk(
+    wuffs_netpbm__decoder* self,
+    uint32_t a_key,
+    uint64_t a_value) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+
+  return wuffs_base__make_status(wuffs_base__error__unsupported_option);
+}
+
+// -------- func netpbm.decoder.decode_image_config
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__decode_image_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 1)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        wuffs_base__status t_0 = wuffs_netpbm__decoder__do_decode_image_config(self, a_dst, a_src);
+        v_status = t_0;
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_decode_image_config[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func netpbm.decoder.do_decode_image_config
+
+static wuffs_base__status
+wuffs_netpbm__decoder__do_decode_image_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__image_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  uint8_t v_c = 0;
+  uint32_t v_n = 0;
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence != 0) {
+      status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_0 = *iop_a_src++;
+      v_c = t_0;
+    }
+    if (v_c != 80) {
+      status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_1 = *iop_a_src++;
+      v_c = t_1;
+    }
+    if ((v_c < 49) || (55 < v_c)) {
+      status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+      goto exit;
+    } else if (v_c == 53) {
+      self->private_impl.f_pixfmt = 536870920;
+    } else if (v_c == 54) {
+      self->private_impl.f_pixfmt = 2684356744;
+    } else {
+      status = wuffs_base__make_status(wuffs_netpbm__error__unsupported_netpbm_file);
+      goto exit;
+    }
+    {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3);
+      if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        goto suspend;
+      }
+      uint8_t t_2 = *iop_a_src++;
+      v_c = t_2;
+    }
+    if (v_c != 10) {
+      status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+      goto exit;
+    }
+    label__0__continue:;
+    while (true) {
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          goto suspend;
+        }
+        uint8_t t_3 = *iop_a_src++;
+        v_c = t_3;
+      }
+      if ((v_c == 32) || (v_c == 9)) {
+        goto label__0__continue;
+      } else if (v_c == 35) {
+        while (true) {
+          {
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+              status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+              goto suspend;
+            }
+            uint8_t t_4 = *iop_a_src++;
+            v_c = t_4;
+          }
+          if (v_c == 10) {
+            goto label__1__break;
+          }
+        }
+        label__1__break:;
+        goto label__0__continue;
+      } else if ((v_c < 48) || (57 < v_c)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+        goto exit;
+      }
+      self->private_impl.f_width = ((uint32_t)((v_c - 48)));
+      goto label__0__break;
+    }
+    label__0__break:;
+    while (true) {
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          goto suspend;
+        }
+        uint8_t t_5 = *iop_a_src++;
+        v_c = t_5;
+      }
+      if ((v_c == 32) || (v_c == 9)) {
+        goto label__2__break;
+      } else if ((v_c < 48) || (57 < v_c)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+        goto exit;
+      }
+      v_n = ((10 * self->private_impl.f_width) + ((uint32_t)((v_c - 48))));
+      if (v_n > 16777215) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__unsupported_netpbm_file);
+        goto exit;
+      }
+      self->private_impl.f_width = v_n;
+    }
+    label__2__break:;
+    label__3__continue:;
+    while (true) {
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          goto suspend;
+        }
+        uint8_t t_6 = *iop_a_src++;
+        v_c = t_6;
+      }
+      if ((v_c == 32) || (v_c == 9)) {
+        goto label__3__continue;
+      } else if (v_c == 35) {
+        while (true) {
+          {
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+              status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+              goto suspend;
+            }
+            uint8_t t_7 = *iop_a_src++;
+            v_c = t_7;
+          }
+          if (v_c == 10) {
+            goto label__4__break;
+          }
+        }
+        label__4__break:;
+        goto label__3__continue;
+      } else if ((v_c < 48) || (57 < v_c)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+        goto exit;
+      }
+      self->private_impl.f_height = ((uint32_t)((v_c - 48)));
+      goto label__3__break;
+    }
+    label__3__break:;
+    label__5__continue:;
+    while (true) {
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9);
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          goto suspend;
+        }
+        uint8_t t_8 = *iop_a_src++;
+        v_c = t_8;
+      }
+      if ((v_c == 32) || (v_c == 9) || (v_c == 13)) {
+        goto label__5__continue;
+      } else if (v_c == 10) {
+        goto label__5__break;
+      } else if ((v_c < 48) || (57 < v_c)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+        goto exit;
+      }
+      v_n = ((10 * self->private_impl.f_height) + ((uint32_t)((v_c - 48))));
+      if (v_n > 16777215) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__unsupported_netpbm_file);
+        goto exit;
+      }
+      self->private_impl.f_height = v_n;
+    }
+    label__5__break:;
+    label__6__continue:;
+    while (true) {
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10);
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          goto suspend;
+        }
+        uint8_t t_9 = *iop_a_src++;
+        v_c = t_9;
+      }
+      if ((v_c == 32) || (v_c == 9)) {
+        goto label__6__continue;
+      } else if (v_c == 35) {
+        while (true) {
+          {
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11);
+            if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+              status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+              goto suspend;
+            }
+            uint8_t t_10 = *iop_a_src++;
+            v_c = t_10;
+          }
+          if (v_c == 10) {
+            goto label__7__break;
+          }
+        }
+        label__7__break:;
+        goto label__6__continue;
+      } else if ((v_c < 48) || (57 < v_c)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+        goto exit;
+      }
+      self->private_impl.f_max_value = ((uint32_t)((v_c - 48)));
+      goto label__6__break;
+    }
+    label__6__break:;
+    label__8__continue:;
+    while (true) {
+      {
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12);
+        if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          goto suspend;
+        }
+        uint8_t t_11 = *iop_a_src++;
+        v_c = t_11;
+      }
+      if ((v_c == 32) || (v_c == 9) || (v_c == 13)) {
+        goto label__8__continue;
+      } else if (v_c == 10) {
+        goto label__8__break;
+      } else if ((v_c < 48) || (57 < v_c)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__bad_header);
+        goto exit;
+      }
+      v_n = ((10 * self->private_impl.f_max_value) + ((uint32_t)((v_c - 48))));
+      if (v_n > 16777215) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__unsupported_netpbm_file);
+        goto exit;
+      }
+      self->private_impl.f_max_value = v_n;
+    }
+    label__8__break:;
+    if (self->private_impl.f_max_value != 255) {
+      status = wuffs_base__make_status(wuffs_netpbm__error__unsupported_netpbm_file);
+      goto exit;
+    }
+    self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)));
+    if (a_dst != NULL) {
+      wuffs_base__image_config__set(
+          a_dst,
+          self->private_impl.f_pixfmt,
+          0,
+          self->private_impl.f_width,
+          self->private_impl.f_height,
+          self->private_impl.f_frame_config_io_position,
+          false);
+    }
+    self->private_impl.f_call_sequence = 32;
+
+    goto ok;
+    ok:
+    self->private_impl.p_do_decode_image_config[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
+// -------- func netpbm.decoder.decode_frame_config
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__decode_frame_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 2)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_decode_frame_config[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        wuffs_base__status t_0 = wuffs_netpbm__decoder__do_decode_frame_config(self, a_dst, a_src);
+        v_status = t_0;
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_decode_frame_config[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0;
+
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func netpbm.decoder.do_decode_frame_config
+
+static wuffs_base__status
+wuffs_netpbm__decoder__do_decode_frame_config(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__frame_config* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence == 32) {
+    } else if (self->private_impl.f_call_sequence < 32) {
+      if (a_src) {
+        a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+      }
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_netpbm__decoder__do_decode_image_config(self, NULL, a_src);
+      if (a_src) {
+        iop_a_src = a_src->data.ptr + a_src->meta.ri;
+      }
+      if (status.repr) {
+        goto suspend;
+      }
+    } else if (self->private_impl.f_call_sequence == 40) {
+      if (self->private_impl.f_frame_config_io_position != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) {
+        status = wuffs_base__make_status(wuffs_base__error__bad_restart);
+        goto exit;
+      }
+    } else if (self->private_impl.f_call_sequence == 64) {
+      self->private_impl.f_call_sequence = 96;
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    } else {
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    }
+    if (a_dst != NULL) {
+      wuffs_base__frame_config__set(
+          a_dst,
+          wuffs_base__utility__make_rect_ie_u32(
+          0,
+          0,
+          self->private_impl.f_width,
+          self->private_impl.f_height),
+          ((wuffs_base__flicks)(0)),
+          0,
+          self->private_impl.f_frame_config_io_position,
+          0,
+          false,
+          false,
+          0);
+    }
+    self->private_impl.f_call_sequence = 64;
+
+    ok:
+    self->private_impl.p_do_decode_frame_config[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
+// -------- func netpbm.decoder.decode_frame
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__decode_frame(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_dst || !a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 3)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_decode_frame[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        wuffs_base__status t_0 = wuffs_netpbm__decoder__do_decode_frame(self,
+            a_dst,
+            a_src,
+            a_blend,
+            a_workbuf,
+            a_opts);
+        v_status = t_0;
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_netpbm__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_decode_frame[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0;
+
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func netpbm.decoder.do_decode_frame
+
+static wuffs_base__status
+wuffs_netpbm__decoder__do_decode_frame(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__pixel_blend a_blend,
+    wuffs_base__slice_u8 a_workbuf,
+    wuffs_base__decode_frame_options* a_opts) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  uint32_t coro_susp_point = self->private_impl.p_do_decode_frame[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    if (self->private_impl.f_call_sequence == 64) {
+    } else if (self->private_impl.f_call_sequence < 64) {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+      status = wuffs_netpbm__decoder__do_decode_frame_config(self, NULL, a_src);
+      if (status.repr) {
+        goto suspend;
+      }
+    } else {
+      status = wuffs_base__make_status(wuffs_base__note__end_of_data);
+      goto ok;
+    }
+    self->private_impl.f_dst_x = 0;
+    self->private_impl.f_dst_y = 0;
+    v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler,
+        wuffs_base__pixel_buffer__pixel_format(a_dst),
+        wuffs_base__pixel_buffer__palette(a_dst),
+        wuffs_base__utility__make_pixel_format(self->private_impl.f_pixfmt),
+        wuffs_base__utility__empty_slice_u8(),
+        a_blend);
+    if ( ! wuffs_base__status__is_ok(&v_status)) {
+      status = v_status;
+      if (wuffs_base__status__is_error(&status)) {
+        goto exit;
+      } else if (wuffs_base__status__is_suspension(&status)) {
+        status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+        goto exit;
+      }
+      goto ok;
+    }
+    while (true) {
+      v_status = wuffs_netpbm__decoder__swizzle(self, a_dst, a_src);
+      if (wuffs_base__status__is_ok(&v_status)) {
+        goto label__0__break;
+      } else if (v_status.repr != wuffs_netpbm__note__internal_note_short_read) {
+        status = v_status;
+        if (wuffs_base__status__is_error(&status)) {
+          goto exit;
+        } else if (wuffs_base__status__is_suspension(&status)) {
+          status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension);
+          goto exit;
+        }
+        goto ok;
+      }
+      status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2);
+    }
+    label__0__break:;
+    self->private_impl.f_call_sequence = 96;
+
+    ok:
+    self->private_impl.p_do_decode_frame[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_do_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+  exit:
+  return status;
+}
+
+// -------- func netpbm.decoder.swizzle
+
+static wuffs_base__status
+wuffs_netpbm__decoder__swizzle(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__pixel_buffer* a_dst,
+    wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  wuffs_base__pixel_format v_dst_pixfmt = {0};
+  uint32_t v_dst_bits_per_pixel = 0;
+  uint32_t v_dst_bytes_per_pixel = 0;
+  uint64_t v_dst_bytes_per_row = 0;
+  uint32_t v_src_bytes_per_pixel = 0;
+  wuffs_base__table_u8 v_tab = {0};
+  wuffs_base__slice_u8 v_dst = {0};
+  uint64_t v_i = 0;
+  uint64_t v_j = 0;
+  uint64_t v_n = 0;
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst);
+  v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt);
+  if ((v_dst_bits_per_pixel & 7) != 0) {
+    status = wuffs_base__make_status(wuffs_base__error__unsupported_option);
+    goto exit;
+  }
+  v_dst_bytes_per_pixel = (v_dst_bits_per_pixel / 8);
+  v_dst_bytes_per_row = ((uint64_t)((self->private_impl.f_width * v_dst_bytes_per_pixel)));
+  v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0);
+  while (true) {
+    if (self->private_impl.f_dst_x == self->private_impl.f_width) {
+      self->private_impl.f_dst_x = 0;
+      self->private_impl.f_dst_y += 1;
+      if (self->private_impl.f_dst_y >= self->private_impl.f_height) {
+        goto label__0__break;
+      }
+    }
+    v_dst = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y);
+    if (v_dst_bytes_per_row < ((uint64_t)(v_dst.len))) {
+      v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row);
+    }
+    v_i = (((uint64_t)(self->private_impl.f_dst_x)) * ((uint64_t)(v_dst_bytes_per_pixel)));
+    if (v_i >= ((uint64_t)(v_dst.len))) {
+      v_src_bytes_per_pixel = 1;
+      if (self->private_impl.f_pixfmt == 2684356744) {
+        v_src_bytes_per_pixel = 3;
+      }
+      v_n = (((uint64_t)(io2_a_src - iop_a_src)) / ((uint64_t)(v_src_bytes_per_pixel)));
+      v_n = wuffs_base__u64__min(v_n, ((uint64_t)(((uint32_t)(self->private_impl.f_width - self->private_impl.f_dst_x)))));
+      v_j = v_n;
+      while (v_j >= 8) {
+        if (((uint64_t)(io2_a_src - iop_a_src)) >= ((uint64_t)((v_src_bytes_per_pixel * 8)))) {
+          iop_a_src += (v_src_bytes_per_pixel * 8);
+        }
+        v_j -= 8;
+      }
+      while (v_j > 0) {
+        if (((uint64_t)(io2_a_src - iop_a_src)) >= ((uint64_t)((v_src_bytes_per_pixel * 1)))) {
+          iop_a_src += (v_src_bytes_per_pixel * 1);
+        }
+        v_j -= 1;
+      }
+    } else {
+      v_n = wuffs_base__pixel_swizzler__swizzle_interleaved_from_reader(
+          &self->private_impl.f_swizzler,
+          wuffs_base__slice_u8__subslice_i(v_dst, v_i),
+          wuffs_base__pixel_buffer__palette(a_dst),
+          &iop_a_src,
+          io2_a_src);
+    }
+    if (v_n == 0) {
+      status = wuffs_base__make_status(wuffs_netpbm__note__internal_note_short_read);
+      goto ok;
+    }
+    wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295))));
+  }
+  label__0__break:;
+  status = wuffs_base__make_status(NULL);
+  goto ok;
+
+  ok:
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
+// -------- func netpbm.decoder.frame_dirty_rect
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32
+wuffs_netpbm__decoder__frame_dirty_rect(
+    const wuffs_netpbm__decoder* self) {
+  if (!self) {
+    return wuffs_base__utility__empty_rect_ie_u32();
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return wuffs_base__utility__empty_rect_ie_u32();
+  }
+
+  return wuffs_base__utility__make_rect_ie_u32(
+      0,
+      0,
+      self->private_impl.f_width,
+      self->private_impl.f_height);
+}
+
+// -------- func netpbm.decoder.num_animation_loops
+
+WUFFS_BASE__MAYBE_STATIC uint32_t
+wuffs_netpbm__decoder__num_animation_loops(
+    const wuffs_netpbm__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  return 0;
+}
+
+// -------- func netpbm.decoder.num_decoded_frame_configs
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_netpbm__decoder__num_decoded_frame_configs(
+    const wuffs_netpbm__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  if (self->private_impl.f_call_sequence > 32) {
+    return 1;
+  }
+  return 0;
+}
+
+// -------- func netpbm.decoder.num_decoded_frames
+
+WUFFS_BASE__MAYBE_STATIC uint64_t
+wuffs_netpbm__decoder__num_decoded_frames(
+    const wuffs_netpbm__decoder* self) {
+  if (!self) {
+    return 0;
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return 0;
+  }
+
+  if (self->private_impl.f_call_sequence > 64) {
+    return 1;
+  }
+  return 0;
+}
+
+// -------- func netpbm.decoder.restart_frame
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__restart_frame(
+    wuffs_netpbm__decoder* self,
+    uint64_t a_index,
+    uint64_t a_io_position) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+
+  if (self->private_impl.f_call_sequence < 32) {
+    return wuffs_base__make_status(wuffs_base__error__bad_call_sequence);
+  }
+  if ((a_index != 0) || (a_io_position != self->private_impl.f_frame_config_io_position)) {
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  self->private_impl.f_call_sequence = 40;
+  return wuffs_base__make_status(NULL);
+}
+
+// -------- func netpbm.decoder.set_report_metadata
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct
+wuffs_netpbm__decoder__set_report_metadata(
+    wuffs_netpbm__decoder* self,
+    uint32_t a_fourcc,
+    bool a_report) {
+  return wuffs_base__make_empty_struct();
+}
+
+// -------- func netpbm.decoder.tell_me_more
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__status
+wuffs_netpbm__decoder__tell_me_more(
+    wuffs_netpbm__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__more_information* a_minfo,
+    wuffs_base__io_buffer* a_src) {
+  if (!self) {
+    return wuffs_base__make_status(wuffs_base__error__bad_receiver);
+  }
+  if (self->private_impl.magic != WUFFS_BASE__MAGIC) {
+    return wuffs_base__make_status(
+        (self->private_impl.magic == WUFFS_BASE__DISABLED)
+        ? wuffs_base__error__disabled_by_previous_error
+        : wuffs_base__error__initialize_not_called);
+  }
+  if (!a_dst || !a_src) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__bad_argument);
+  }
+  if ((self->private_impl.active_coroutine != 0) &&
+      (self->private_impl.active_coroutine != 4)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+    return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls);
+  }
+  self->private_impl.active_coroutine = 0;
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  status = wuffs_base__make_status(wuffs_base__error__no_more_information);
+  goto exit;
+
+  goto ok;
+  ok:
+  goto exit;
+  exit:
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func netpbm.decoder.workbuf_len
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64
+wuffs_netpbm__decoder__workbuf_len(
+    const wuffs_netpbm__decoder* self) {
+  if (!self) {
+    return wuffs_base__utility__empty_range_ii_u64();
+  }
+  if ((self->private_impl.magic != WUFFS_BASE__MAGIC) &&
+      (self->private_impl.magic != WUFFS_BASE__DISABLED)) {
+    return wuffs_base__utility__empty_range_ii_u64();
+  }
+
+  return wuffs_base__utility__make_range_ii_u64(0, 0);
+}
+
+#endif  // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NETPBM)
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NIE)
 
 // ---------------- Status Codes Implementations
@@ -52708,6 +54149,11 @@
       return wuffs_nie__decoder::alloc_as__wuffs_base__image_decoder();
 #endif
 
+#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NETPBM)
+    case WUFFS_BASE__FOURCC__NPBM:
+      return wuffs_netpbm__decoder::alloc_as__wuffs_base__image_decoder();
+#endif
+
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG)
     case WUFFS_BASE__FOURCC__PNG: {
       auto dec = wuffs_png__decoder::alloc_as__wuffs_base__image_decoder();
diff --git a/script/manual-test-truncated-input.cc b/script/manual-test-truncated-input.cc
index 741e4c5..962f331 100644
--- a/script/manual-test-truncated-input.cc
+++ b/script/manual-test-truncated-input.cc
@@ -65,7 +65,9 @@
 #define WUFFS_CONFIG__MODULE__DEFLATE
 #define WUFFS_CONFIG__MODULE__GIF
 #define WUFFS_CONFIG__MODULE__GZIP
+#define WUFFS_CONFIG__MODULE__JPEG
 #define WUFFS_CONFIG__MODULE__LZW
+#define WUFFS_CONFIG__MODULE__NETPBM
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
@@ -114,9 +116,15 @@
     case WUFFS_BASE__FOURCC__GIF:
       dec = wuffs_gif__decoder::alloc_as__wuffs_base__image_decoder();
       break;
+    case WUFFS_BASE__FOURCC__JPEG:
+      dec = wuffs_jpeg__decoder::alloc_as__wuffs_base__image_decoder();
+      break;
     case WUFFS_BASE__FOURCC__NIE:
       dec = wuffs_nie__decoder::alloc_as__wuffs_base__image_decoder();
       break;
+    case WUFFS_BASE__FOURCC__NPBM:
+      dec = wuffs_netpbm__decoder::alloc_as__wuffs_base__image_decoder();
+      break;
     case WUFFS_BASE__FOURCC__PNG:
       dec = wuffs_png__decoder::alloc_as__wuffs_base__image_decoder();
       break;
diff --git a/script/print-average-pixel.cc b/script/print-average-pixel.cc
index 89b0dce..3f29fec 100644
--- a/script/print-average-pixel.cc
+++ b/script/print-average-pixel.cc
@@ -57,7 +57,9 @@
 #define WUFFS_CONFIG__MODULE__CRC32
 #define WUFFS_CONFIG__MODULE__DEFLATE
 #define WUFFS_CONFIG__MODULE__GIF
+#define WUFFS_CONFIG__MODULE__JPEG
 #define WUFFS_CONFIG__MODULE__LZW
+#define WUFFS_CONFIG__MODULE__NETPBM
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
diff --git a/script/print-image-metadata.cc b/script/print-image-metadata.cc
index 01617ee..56bebac 100644
--- a/script/print-image-metadata.cc
+++ b/script/print-image-metadata.cc
@@ -53,7 +53,9 @@
 #define WUFFS_CONFIG__MODULE__CRC32
 #define WUFFS_CONFIG__MODULE__DEFLATE
 #define WUFFS_CONFIG__MODULE__GIF
+#define WUFFS_CONFIG__MODULE__JPEG
 #define WUFFS_CONFIG__MODULE__LZW
+#define WUFFS_CONFIG__MODULE__NETPBM
 #define WUFFS_CONFIG__MODULE__NIE
 #define WUFFS_CONFIG__MODULE__PNG
 #define WUFFS_CONFIG__MODULE__TGA
@@ -376,9 +378,15 @@
       case WUFFS_BASE__FOURCC__GIF:
         dec = wuffs_gif__decoder::alloc_as__wuffs_base__image_decoder();
         break;
+      case WUFFS_BASE__FOURCC__JPEG:
+        dec = wuffs_jpeg__decoder::alloc_as__wuffs_base__image_decoder();
+        break;
       case WUFFS_BASE__FOURCC__NIE:
         dec = wuffs_nie__decoder::alloc_as__wuffs_base__image_decoder();
         break;
+      case WUFFS_BASE__FOURCC__NPBM:
+        dec = wuffs_netpbm__decoder::alloc_as__wuffs_base__image_decoder();
+        break;
       case WUFFS_BASE__FOURCC__PNG:
         dec = wuffs_png__decoder::alloc_as__wuffs_base__image_decoder();
         break;
diff --git a/std/netpbm/decode_netpbm.wuffs b/std/netpbm/decode_netpbm.wuffs
new file mode 100644
index 0000000..6af56a2
--- /dev/null
+++ b/std/netpbm/decode_netpbm.wuffs
@@ -0,0 +1,433 @@
+// Copyright 2023 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub status "#bad header"
+pub status "#truncated input"
+pub status "#unsupported Netpbm file"
+
+pri status "@internal note: short read"
+
+pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 0
+
+pub struct decoder? implements base.image_decoder(
+        pixfmt : base.u32,
+        width  : base.u32[..= 0xFF_FFFF],
+        height : base.u32[..= 0xFF_FFFF],
+
+        max_value : base.u32[..= 0xFF_FFFF],
+
+        // The call sequence state machine is discussed in
+        // (/doc/std/image-decoders-call-sequence.md).
+        call_sequence : base.u8,
+
+        frame_config_io_position : base.u64,
+
+        dst_x : base.u32,
+        dst_y : base.u32,
+
+        swizzler : base.pixel_swizzler,
+        util     : base.utility,
+)
+
+pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
+    return base."#unsupported option"
+}
+
+pub func decoder.decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
+    var status : base.status
+
+    while true {
+        status =? this.do_decode_image_config?(dst: args.dst, src: args.src)
+        if (status == base."$short read") and args.src.is_closed() {
+            return "#truncated input"
+        }
+        yield? status
+    } endwhile
+}
+
+pri func decoder.do_decode_image_config?(dst: nptr base.image_config, src: base.io_reader) {
+    var c : base.u8
+    var n : base.u32
+
+    if this.call_sequence <> 0x00 {
+        return base."#bad call sequence"
+    }
+
+    c = args.src.read_u8?()
+    if c <> 'P' {
+        return "#bad header"
+    }
+
+    c = args.src.read_u8?()
+    if (c < '1') or ('7' < c) {
+        return "#bad header"
+    } else if c == '5' {
+        this.pixfmt = base.PIXEL_FORMAT__Y
+    } else if c == '6' {
+        this.pixfmt = base.PIXEL_FORMAT__RGB
+    } else {
+        return "#unsupported Netpbm file"
+    }
+
+    c = args.src.read_u8?()
+    if c <> 0x0A {
+        return "#bad header"
+    }
+
+    // Decode width.
+    while true {
+        c = args.src.read_u8?()
+        if (c == ' ') or (c == 0x09) {
+            continue
+        } else if c == '#' {
+            // Consume a "#...\n" comment.
+            while true {
+                c = args.src.read_u8?()
+                if c == 0x0A {
+                    break
+                }
+            } endwhile
+            continue
+        } else if (c < '0') or ('9' < c) {
+            return "#bad header"
+        }
+        this.width = ((c - '0') as base.u32)
+        break
+    } endwhile
+    while true {
+        c = args.src.read_u8?()
+        if (c == ' ') or (c == 0x09) {
+            break
+        } else if (c < '0') or ('9' < c) {
+            return "#bad header"
+        }
+        n = (10 * this.width) + ((c - '0') as base.u32)
+        if n > 0xFF_FFFF {
+            return "#unsupported Netpbm file"
+        }
+        this.width = n
+    } endwhile
+
+    // Decode height.
+    while true {
+        c = args.src.read_u8?()
+        if (c == ' ') or (c == 0x09) {
+            continue
+        } else if c == '#' {
+            // Consume a "#...\n" comment.
+            while true {
+                c = args.src.read_u8?()
+                if c == 0x0A {
+                    break
+                }
+            } endwhile
+            continue
+        } else if (c < '0') or ('9' < c) {
+            return "#bad header"
+        }
+        this.height = ((c - '0') as base.u32)
+        break
+    } endwhile
+    while true {
+        c = args.src.read_u8?()
+        if (c == ' ') or (c == 0x09) or (c == 0x0D) {
+            continue
+        } else if c == 0x0A {
+            break
+        } else if (c < '0') or ('9' < c) {
+            return "#bad header"
+        }
+        n = (10 * this.height) + ((c - '0') as base.u32)
+        if n > 0xFF_FFFF {
+            return "#unsupported Netpbm file"
+        }
+        this.height = n
+    } endwhile
+
+    // Decode max_value.
+    while true {
+        c = args.src.read_u8?()
+        if (c == ' ') or (c == 0x09) {
+            continue
+        } else if c == '#' {
+            // Consume a "#...\n" comment.
+            while true {
+                c = args.src.read_u8?()
+                if c == 0x0A {
+                    break
+                }
+            } endwhile
+            continue
+        } else if (c < '0') or ('9' < c) {
+            return "#bad header"
+        }
+        this.max_value = ((c - '0') as base.u32)
+        break
+    } endwhile
+    while true {
+        c = args.src.read_u8?()
+        if (c == ' ') or (c == 0x09) or (c == 0x0D) {
+            continue
+        } else if c == 0x0A {
+            break
+        } else if (c < '0') or ('9' < c) {
+            return "#bad header"
+        }
+        n = (10 * this.max_value) + ((c - '0') as base.u32)
+        if n > 0xFF_FFFF {
+            return "#unsupported Netpbm file"
+        }
+        this.max_value = n
+    } endwhile
+
+    if this.max_value <> 255 {
+        return "#unsupported Netpbm file"
+    }
+
+    this.frame_config_io_position = args.src.position()
+
+    if args.dst <> nullptr {
+        args.dst.set!(
+                pixfmt: this.pixfmt,
+                pixsub: 0,
+                width: this.width,
+                height: this.height,
+                first_frame_io_position: this.frame_config_io_position,
+                first_frame_is_opaque: false)
+    }
+
+    this.call_sequence = 0x20
+}
+
+pub func decoder.decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
+    var status : base.status
+
+    while true {
+        status =? this.do_decode_frame_config?(dst: args.dst, src: args.src)
+        if (status == base."$short read") and args.src.is_closed() {
+            return "#truncated input"
+        }
+        yield? status
+    } endwhile
+}
+
+pri func decoder.do_decode_frame_config?(dst: nptr base.frame_config, src: base.io_reader) {
+    if this.call_sequence == 0x20 {
+        // No-op.
+    } else if this.call_sequence < 0x20 {
+        this.do_decode_image_config?(dst: nullptr, src: args.src)
+    } else if this.call_sequence == 0x28 {
+        if this.frame_config_io_position <> args.src.position() {
+            return base."#bad restart"
+        }
+    } else if this.call_sequence == 0x40 {
+        this.call_sequence = 0x60
+        return base."@end of data"
+    } else {
+        return base."@end of data"
+    }
+
+    if args.dst <> nullptr {
+        args.dst.set!(bounds: this.util.make_rect_ie_u32(
+                min_incl_x: 0,
+                min_incl_y: 0,
+                max_excl_x: this.width,
+                max_excl_y: this.height),
+                duration: 0,
+                index: 0,
+                io_position: this.frame_config_io_position,
+                disposal: 0,
+                opaque_within_bounds: false,
+                overwrite_instead_of_blend: false,
+                background_color: 0x0000_0000)
+    }
+
+    this.call_sequence = 0x40
+}
+
+pub func decoder.decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
+    var status : base.status
+
+    while true {
+        status =? this.do_decode_frame?(dst: args.dst, src: args.src, blend: args.blend, workbuf: args.workbuf, opts: args.opts)
+        if (status == base."$short read") and args.src.is_closed() {
+            return "#truncated input"
+        }
+        yield? status
+    } endwhile
+}
+
+pri func decoder.do_decode_frame?(dst: ptr base.pixel_buffer, src: base.io_reader, blend: base.pixel_blend, workbuf: slice base.u8, opts: nptr base.decode_frame_options) {
+    var status : base.status
+
+    if this.call_sequence == 0x40 {
+        // No-op.
+    } else if this.call_sequence < 0x40 {
+        this.do_decode_frame_config?(dst: nullptr, src: args.src)
+    } else {
+        return base."@end of data"
+    }
+
+    this.dst_x = 0
+    this.dst_y = 0
+
+    status = this.swizzler.prepare!(
+            dst_pixfmt: args.dst.pixel_format(),
+            dst_palette: args.dst.palette(),
+            src_pixfmt: this.util.make_pixel_format(repr: this.pixfmt),
+            src_palette: this.util.empty_slice_u8(),
+            blend: args.blend)
+    if not status.is_ok() {
+        return status
+    }
+
+    while true {
+        status = this.swizzle!(dst: args.dst, src: args.src)
+        if status.is_ok() {
+            break
+        } else if status <> "@internal note: short read" {
+            return status
+        }
+        yield? base."$short read"
+    } endwhile
+
+    this.call_sequence = 0x60
+}
+
+pri func decoder.swizzle!(dst: ptr base.pixel_buffer, src: base.io_reader) base.status {
+    var dst_pixfmt          : base.pixel_format
+    var dst_bits_per_pixel  : base.u32[..= 256]
+    var dst_bytes_per_pixel : base.u32[..= 32]
+    var dst_bytes_per_row   : base.u64
+    var src_bytes_per_pixel : base.u32[..= 8]
+    var tab                 : table base.u8
+    var dst                 : slice base.u8
+    var i                   : base.u64
+    var j                   : base.u64
+    var n                   : base.u64
+
+    // TODO: the dst_pixfmt variable shouldn't be necessary. We should be able
+    // to chain the two calls: "args.dst.pixel_format().bits_per_pixel()".
+    dst_pixfmt = args.dst.pixel_format()
+    dst_bits_per_pixel = dst_pixfmt.bits_per_pixel()
+    if (dst_bits_per_pixel & 7) <> 0 {
+        return base."#unsupported option"
+    }
+    dst_bytes_per_pixel = dst_bits_per_pixel / 8
+    dst_bytes_per_row = (this.width * dst_bytes_per_pixel) as base.u64
+    tab = args.dst.plane(p: 0)
+
+    while true {
+        if this.dst_x == this.width {
+            this.dst_x = 0
+            this.dst_y ~mod+= 1
+            if this.dst_y >= this.height {
+                break
+            }
+        }
+
+        dst = tab.row_u32(y: this.dst_y)
+        if dst_bytes_per_row < dst.length() {
+            dst = dst[.. dst_bytes_per_row]
+        }
+        i = (this.dst_x as base.u64) * (dst_bytes_per_pixel as base.u64)
+        if i >= dst.length() {
+            src_bytes_per_pixel = 1
+            assert src_bytes_per_pixel > 0
+            if this.pixfmt == base.PIXEL_FORMAT__RGB {
+                src_bytes_per_pixel = 3
+                assert src_bytes_per_pixel > 0
+            }
+            n = args.src.length() / (src_bytes_per_pixel as base.u64)
+            n = n.min(no_more_than: (this.width ~mod- this.dst_x) as base.u64)
+            j = n
+            while j >= 8 {
+                if args.src.length() >= ((src_bytes_per_pixel * 8) as base.u64) {
+                    args.src.skip_u32_fast!(
+                            actual: src_bytes_per_pixel * 8,
+                            worst_case: src_bytes_per_pixel * 8)
+                }
+                j -= 8
+            } endwhile
+            while j > 0 {
+                if args.src.length() >= ((src_bytes_per_pixel * 1) as base.u64) {
+                    args.src.skip_u32_fast!(
+                            actual: src_bytes_per_pixel * 1,
+                            worst_case: src_bytes_per_pixel * 1)
+                }
+                j -= 1
+            } endwhile
+        } else {
+            n = this.swizzler.swizzle_interleaved_from_reader!(
+                    dst: dst[i ..],
+                    dst_palette: args.dst.palette(),
+                    src: args.src)
+        }
+        if n == 0 {
+            return "@internal note: short read"
+        }
+        this.dst_x ~sat+= (n & 0xFFFF_FFFF) as base.u32
+    } endwhile
+
+    return ok
+}
+
+pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
+    return this.util.make_rect_ie_u32(
+            min_incl_x: 0,
+            min_incl_y: 0,
+            max_excl_x: this.width,
+            max_excl_y: this.height)
+}
+
+pub func decoder.num_animation_loops() base.u32 {
+    return 0
+}
+
+pub func decoder.num_decoded_frame_configs() base.u64 {
+    if this.call_sequence > 0x20 {
+        return 1
+    }
+    return 0
+}
+
+pub func decoder.num_decoded_frames() base.u64 {
+    if this.call_sequence > 0x40 {
+        return 1
+    }
+    return 0
+}
+
+pub func decoder.restart_frame!(index: base.u64, io_position: base.u64) base.status {
+    if this.call_sequence < 0x20 {
+        return base."#bad call sequence"
+    }
+    if (args.index <> 0) or (args.io_position <> this.frame_config_io_position) {
+        return base."#bad argument"
+    }
+    this.call_sequence = 0x28
+    return ok
+}
+
+pub func decoder.set_report_metadata!(fourcc: base.u32, report: base.bool) {
+    // No-op. Netpbm doesn't support metadata.
+}
+
+pub func decoder.tell_me_more?(dst: base.io_writer, minfo: nptr base.more_information, src: base.io_reader) {
+    return base."#no more information"
+}
+
+pub func decoder.workbuf_len() base.range_ii_u64 {
+    return this.util.make_range_ii_u64(min_incl: 0, max_incl: 0)
+}
diff --git a/test/c/std/netpbm.c b/test/c/std/netpbm.c
new file mode 100644
index 0000000..371c0e9
--- /dev/null
+++ b/test/c/std/netpbm.c
@@ -0,0 +1,226 @@
+// Copyright 2023 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ----------------
+
+/*
+This test program is typically run indirectly, by the "wuffs test" or "wuffs
+bench" commands. These commands take an optional "-mimic" flag to check that
+Wuffs' output mimics (i.e. exactly matches) other libraries' output, such as
+giflib for GIF, libpng for PNG, etc.
+
+To manually run this test:
+
+for CC in clang gcc; do
+  $CC -std=c99 -Wall -Werror netpbm.c && ./a.out
+  rm -f a.out
+done
+
+Each edition should print "PASS", amongst other information, and exit(0).
+
+Add the "wuffs mimic cflags" (everything after the colon below) to the C
+compiler flags (after the .c file) to run the mimic tests.
+
+To manually run the benchmarks, replace "-Wall -Werror" with "-O3" and replace
+the first "./a.out" with "./a.out -bench". Combine these changes with the
+"wuffs mimic cflags" to run the mimic benchmarks.
+*/
+
+// ¿ wuffs mimic cflags: -DWUFFS_MIMIC
+
+// Wuffs ships as a "single file C library" or "header file library" as per
+// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
+//
+// To use that single file as a "foo.c"-like implementation, instead of a
+// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
+// compiling it.
+#define WUFFS_IMPLEMENTATION
+
+// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
+// release/c/etc.c choose which parts of Wuffs to build. That file contains the
+// entire Wuffs standard library, implementing a variety of codecs and file
+// formats. Without this macro definition, an optimizing compiler or linker may
+// very well discard Wuffs code for unused codecs, but listing the Wuffs
+// modules we use makes that process explicit. Preprocessing means that such
+// code simply isn't compiled.
+#define WUFFS_CONFIG__MODULES
+#define WUFFS_CONFIG__MODULE__BASE
+#define WUFFS_CONFIG__MODULE__NETPBM
+
+// If building this program in an environment that doesn't easily accommodate
+// relative includes, you can use the script/inline-c-relative-includes.go
+// program to generate a stand-alone C file.
+#include "../../../release/c/wuffs-unsupported-snapshot.c"
+#include "../testlib/testlib.c"
+#ifdef WUFFS_MIMIC
+// No mimic library.
+#endif
+
+// ---------------- Netpbm Tests
+
+const char*  //
+test_wuffs_netpbm_decode_interface() {
+  CHECK_FOCUS(__func__);
+  wuffs_netpbm__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_netpbm__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+  return do_test__wuffs_base__image_decoder(
+      wuffs_netpbm__decoder__upcast_as__wuffs_base__image_decoder(&dec),
+      "test/data/hippopotamus.ppm", 0, SIZE_MAX, 36, 28, 0xFFF5F5F5);
+}
+
+const char*  //
+test_wuffs_netpbm_decode_truncated_input() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer src =
+      wuffs_base__ptr_u8__reader(g_src_array_u8, 0, false);
+  wuffs_netpbm__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_netpbm__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__status status =
+      wuffs_netpbm__decoder__decode_image_config(&dec, NULL, &src);
+  if (status.repr != wuffs_base__suspension__short_read) {
+    RETURN_FAIL("closed=false: have \"%s\", want \"%s\"", status.repr,
+                wuffs_base__suspension__short_read);
+  }
+
+  src.meta.closed = true;
+  status = wuffs_netpbm__decoder__decode_image_config(&dec, NULL, &src);
+  if (status.repr != wuffs_netpbm__error__truncated_input) {
+    RETURN_FAIL("closed=true: have \"%s\", want \"%s\"", status.repr,
+                wuffs_netpbm__error__truncated_input);
+  }
+  return NULL;
+}
+
+const char*  //
+test_wuffs_netpbm_decode_frame_config() {
+  CHECK_FOCUS(__func__);
+  wuffs_netpbm__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_netpbm__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = g_src_slice_u8,
+  });
+  CHECK_STRING(read_file(&src, "test/data/hippopotamus.pgm"));
+  CHECK_STATUS("decode_frame_config #0",
+               wuffs_netpbm__decoder__decode_frame_config(&dec, &fc, &src));
+
+  wuffs_base__status status =
+      wuffs_netpbm__decoder__decode_frame_config(&dec, &fc, &src);
+  if (status.repr != wuffs_base__note__end_of_data) {
+    RETURN_FAIL("decode_frame_config #1: have \"%s\", want \"%s\"", status.repr,
+                wuffs_base__note__end_of_data);
+  }
+  return NULL;
+}
+
+const char*  //
+test_wuffs_netpbm_decode_image_config() {
+  CHECK_FOCUS(__func__);
+  wuffs_netpbm__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_netpbm__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__image_config ic = ((wuffs_base__image_config){});
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = g_src_slice_u8,
+  });
+  CHECK_STRING(read_file(&src, "test/data/hippopotamus.pgm"));
+  CHECK_STATUS("decode_image_config",
+               wuffs_netpbm__decoder__decode_image_config(&dec, &ic, &src));
+
+  uint32_t have_width = wuffs_base__pixel_config__width(&ic.pixcfg);
+  uint32_t want_width = 36;
+  if (have_width != want_width) {
+    RETURN_FAIL("width: have %" PRIu32 ", want %" PRIu32, have_width,
+                want_width);
+  }
+  uint32_t have_height = wuffs_base__pixel_config__height(&ic.pixcfg);
+  uint32_t want_height = 28;
+  if (have_height != want_height) {
+    RETURN_FAIL("height: have %" PRIu32 ", want %" PRIu32, have_height,
+                want_height);
+  }
+  return NULL;
+}
+
+// ---------------- Mimic Tests
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- Netpbm Benches
+
+// No Netpbm benches.
+
+// ---------------- Mimic Benches
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+// ---------------- Manifest
+
+proc g_tests[] = {
+
+    test_wuffs_netpbm_decode_frame_config,
+    test_wuffs_netpbm_decode_image_config,
+    test_wuffs_netpbm_decode_interface,
+    test_wuffs_netpbm_decode_truncated_input,
+
+#ifdef WUFFS_MIMIC
+
+// No mimic tests.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+proc g_benches[] = {
+
+// No Netpbm benches.
+
+#ifdef WUFFS_MIMIC
+
+// No mimic benches.
+
+#endif  // WUFFS_MIMIC
+
+    NULL,
+};
+
+int  //
+main(int argc, char** argv) {
+  g_proc_package_name = "std/netpbm";
+  return test_main(argc, argv, g_tests, g_benches);
+}
diff --git a/test/data/hippopotamus.pgm b/test/data/hippopotamus.pgm
new file mode 100644
index 0000000..2e09d8c
--- /dev/null
+++ b/test/data/hippopotamus.pgm
@@ -0,0 +1,4 @@
+P5
+36 28
+255
+rtuwxy{{|~~‚‚ƒ„…†‡ˆˆ‰ŠŠ‹ŒŒŒŽŽŽwxy{|~‚{€€…‰†‡Š‹ŒŒŽŽ‘‘‘‘‘’’’||~€‚ƒsruz‰“¡˜Ž‘’“““””•••––––€€‚ƒ…‚vwƒp‚†˜®«›””“••–—–—˜™ššššššƒ„†ˆ~mew{ru„‘¢¬¯·¾¯¡—“œ¤šš ž›žžžž‡ˆŠ`ijˆw}z|ˆ’™ ¢™ššœ”›§›¦¢±™¡¡¡¡¡‹d_a_s`ahqlx”“‚‹‡}~€—Ÿ¡º¼¦Ä®¤¥¥¦¦“rdTOMbVT\\\i‡€vwbimzwzy»Ä»Ë³©¨©©ª”•X\WWTaYXeXJ_lhbnSah^\ICKXŽÀ¾m¦¬­®˜‰VR_TZ_Q^\VROZedPY[WWQONqK.L©£ˆ°°±²SS[[]bX^^UPJ`g\RTFCHPUIbpIY´¿³´´µ¶¢ŽdO[UT^Y[ZLMV`dUILHAM`SAikš­Å¸À·¸¹¹¦¦e^XZUXUYULP[[RWIOMNHHL;U\y®Çÿ¾»¼¼«±y[RVSPVeMLNNQNFITR\P?GL`ox‘ÐƼĿ¿¿²´¦aUSSRNUIJJIFNLHQY]XDPdnn{„ÊÑÀÁÃÄĺº¾lXW[UFOMLLTPMMOPMNSCKa‚†›±¶ÈÀ¸ºÈËÂÃňSERPEPPEKLLFDCDGVhv ’¥˜Á×ûœ¥¾ÅÓÇÇɗ\;+uvFOJKDDCB=:Hey¢Û|—¾À‰šš±Úܼ¼¾™^</|½&8ESKB@KV'4Ak«à“RT`|€¥ÂÝã䯭­—uK2E­1@AiYMGK³(?y»âু¦½Õãççé鯫¨ˆ‡bC(E7NCicTPeÃ%.GˆÊçåéíîííìììíî¬‚YB@8/?FEdcVVx¾38S“àéêëìíîïððððññÇ¿¹°¦ž™„ugabc^]r‰:CwŽÊêëìîïððòòòòóóÙÒÌŽ¶®¦ž•ˆws†„rV/2Xk­ãçêíîððòòóóóôæâßÙÓÍƾ·°§š{|ƒqXjnltŠµÓ×ÝâæëíïñòóôôîìéçäáÝÙÕÑÍÇÁ¤“”ª«¨¥¥§ª°¶¾ÈÒÛâèìðòóõððïîíìêèçäâàÝÛÙÕÏËÆÁ»¸¸»ÀÈÐØàåêîñóôôññðïîîíìëêéçæäãáàÞÜÙÕÒÑÓÖÛáæêíïñòóôõ
\ No newline at end of file
diff --git a/test/data/hippopotamus.ppm b/test/data/hippopotamus.ppm
new file mode 100644
index 0000000..3567069
--- /dev/null
+++ b/test/data/hippopotamus.ppm
Binary files differ
diff --git a/test/nia-checksums-of-data.txt b/test/nia-checksums-of-data.txt
index f067494..cd4fe1c 100644
--- a/test/nia-checksums-of-data.txt
+++ b/test/nia-checksums-of-data.txt
@@ -73,6 +73,8 @@
 d3bbed27 test/data/hippopotamus.masked-with-muybridge.gif
 7e6acf01 test/data/hippopotamus.masked-with-muybridge.png
 dcbb225a test/data/hippopotamus.nie
+57d9a4dd test/data/hippopotamus.pgm
+dcbb225a test/data/hippopotamus.ppm
 ed4b78fc test/data/hippopotamus.regular.gif
 dcbb225a test/data/hippopotamus.regular.png
 888b1c04 test/data/hippopotamus.regular.truncated.png