Add base_64 codec to wuffs_base
diff --git a/internal/cgen/base/fundamental-public.h b/internal/cgen/base/fundamental-public.h
index 55c0d40..a450b43 100644
--- a/internal/cgen/base/fundamental-public.h
+++ b/internal/cgen/base/fundamental-public.h
@@ -256,6 +256,16 @@
 
 // --------
 
+// wuffs_base__transform__output is the result of transforming from a src slice
+// to a dst slice.
+typedef struct {
+  wuffs_base__status status;
+  size_t num_dst;
+  size_t num_src;
+} wuffs_base__transform__output;
+
+// --------
+
 // FourCC constants.
 
 // !! INSERT FourCCs.
diff --git a/internal/cgen/base/i64conv-submodule.c b/internal/cgen/base/i64conv-submodule.c
index eee18ee..f6fcb2e 100644
--- a/internal/cgen/base/i64conv-submodule.c
+++ b/internal/cgen/base/i64conv-submodule.c
@@ -435,3 +435,322 @@
 
   return len;
 }
+
+// ---------------- Base-64
+
+// The two base-64 alphabets, std and url, differ only in the last two codes.
+//  - std: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+//  - url: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+
+static const uint8_t wuffs_base__base_64__decode_std[256] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x00 ..= 0x07.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x08 ..= 0x0F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x10 ..= 0x17.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x18 ..= 0x1F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x20 ..= 0x27.
+    0x80, 0x80, 0x80, 0x3E, 0x80, 0x80, 0x80, 0x3F,  // 0x28 ..= 0x2F.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,  // 0x30 ..= 0x37.
+    0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x38 ..= 0x3F.
+
+    0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,  // 0x40 ..= 0x47.
+    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  // 0x48 ..= 0x4F.
+    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,  // 0x50 ..= 0x57.
+    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x58 ..= 0x5F.
+    0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,  // 0x60 ..= 0x67.
+    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,  // 0x68 ..= 0x6F.
+    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,  // 0x70 ..= 0x77.
+    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x78 ..= 0x7F.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x80 ..= 0x87.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x88 ..= 0x8F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x90 ..= 0x97.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x98 ..= 0x9F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA0 ..= 0xA7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA8 ..= 0xAF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB0 ..= 0xB7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB8 ..= 0xBF.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC0 ..= 0xC7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC8 ..= 0xCF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD0 ..= 0xD7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD8 ..= 0xDF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE0 ..= 0xE7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE8 ..= 0xEF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF0 ..= 0xF7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF8 ..= 0xFF.
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+};
+
+static const uint8_t wuffs_base__base_64__decode_url[256] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x00 ..= 0x07.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x08 ..= 0x0F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x10 ..= 0x17.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x18 ..= 0x1F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x20 ..= 0x27.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x3E, 0x80, 0x80,  // 0x28 ..= 0x2F.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,  // 0x30 ..= 0x37.
+    0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x38 ..= 0x3F.
+
+    0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,  // 0x40 ..= 0x47.
+    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  // 0x48 ..= 0x4F.
+    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,  // 0x50 ..= 0x57.
+    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x3F,  // 0x58 ..= 0x5F.
+    0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,  // 0x60 ..= 0x67.
+    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,  // 0x68 ..= 0x6F.
+    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,  // 0x70 ..= 0x77.
+    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x78 ..= 0x7F.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x80 ..= 0x87.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x88 ..= 0x8F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x90 ..= 0x97.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x98 ..= 0x9F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA0 ..= 0xA7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA8 ..= 0xAF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB0 ..= 0xB7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB8 ..= 0xBF.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC0 ..= 0xC7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC8 ..= 0xCF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD0 ..= 0xD7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD8 ..= 0xDF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE0 ..= 0xE7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE8 ..= 0xEF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF0 ..= 0xF7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF8 ..= 0xFF.
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+};
+
+static const uint8_t wuffs_base__base_64__encode_std[64] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,  // 0x00 ..= 0x07.
+    0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,  // 0x08 ..= 0x0F.
+    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,  // 0x10 ..= 0x17.
+    0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,  // 0x18 ..= 0x1F.
+    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,  // 0x20 ..= 0x27.
+    0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,  // 0x28 ..= 0x2F.
+    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,  // 0x30 ..= 0x37.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F,  // 0x38 ..= 0x3F.
+};
+
+static const uint8_t wuffs_base__base_64__encode_url[64] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,  // 0x00 ..= 0x07.
+    0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,  // 0x08 ..= 0x0F.
+    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,  // 0x10 ..= 0x17.
+    0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,  // 0x18 ..= 0x1F.
+    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,  // 0x20 ..= 0x27.
+    0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,  // 0x28 ..= 0x2F.
+    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,  // 0x30 ..= 0x37.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2D, 0x5F,  // 0x38 ..= 0x3F.
+};
+
+// --------
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__decode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options) {
+  const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET)
+                                ? wuffs_base__base_64__decode_url
+                                : wuffs_base__base_64__decode_std;
+  wuffs_base__transform__output o;
+  uint8_t* d_ptr = dst.ptr;
+  size_t d_len = dst.len;
+  const uint8_t* s_ptr = src.ptr;
+  size_t s_len = src.len;
+  bool pad = false;
+
+  while (s_len >= 4) {
+    uint32_t s = wuffs_base__load_u32le__no_bounds_check(s_ptr);
+    uint32_t s0 = alphabet[0xFF & (s >> 0)];
+    uint32_t s1 = alphabet[0xFF & (s >> 8)];
+    uint32_t s2 = alphabet[0xFF & (s >> 16)];
+    uint32_t s3 = alphabet[0xFF & (s >> 24)];
+
+    if (((s0 | s1 | s2 | s3) & 0xC0) != 0) {
+      if (s_len > 4) {
+        o.status.repr = wuffs_base__error__bad_data;
+        goto done;
+      } else if (!src_closed) {
+        o.status.repr = wuffs_base__suspension__short_read;
+        goto done;
+      } else if ((options & WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING) &&
+                 (s_ptr[3] == '=')) {
+        pad = true;
+        if (s_ptr[2] == '=') {
+          goto src2;
+        }
+        goto src3;
+      }
+      o.status.repr = wuffs_base__error__bad_data;
+      goto done;
+    }
+
+    if (d_len < 3) {
+      o.status.repr = wuffs_base__suspension__short_write;
+      goto done;
+    }
+
+    s_ptr += 4;
+    s_len -= 4;
+    s = (s0 << 18) | (s1 << 12) | (s2 << 6) | (s3 << 0);
+    *d_ptr++ = (uint8_t)(s >> 16);
+    *d_ptr++ = (uint8_t)(s >> 8);
+    *d_ptr++ = (uint8_t)(s >> 0);
+    d_len -= 3;
+  }
+
+  if (!src_closed) {
+    o.status.repr = wuffs_base__suspension__short_read;
+    goto done;
+  }
+
+  if (s_len == 0) {
+    o.status.repr = NULL;
+    goto done;
+  } else if (s_len == 1) {
+    o.status.repr = wuffs_base__error__bad_data;
+    goto done;
+  } else if (s_len == 2) {
+    goto src2;
+  }
+
+src3:
+  do {
+    uint32_t s = wuffs_base__load_u24le__no_bounds_check(s_ptr);
+    uint32_t s0 = alphabet[0xFF & (s >> 0)];
+    uint32_t s1 = alphabet[0xFF & (s >> 8)];
+    uint32_t s2 = alphabet[0xFF & (s >> 16)];
+    if ((s0 & 0xC0) || (s1 & 0xC0) || (s2 & 0xC3)) {
+      o.status.repr = wuffs_base__error__bad_data;
+      goto done;
+    }
+    if (d_len < 2) {
+      o.status.repr = wuffs_base__suspension__short_write;
+      goto done;
+    }
+    s_ptr += pad ? 4 : 3;
+    s = (s0 << 18) | (s1 << 12) | (s2 << 6);
+    *d_ptr++ = (uint8_t)(s >> 16);
+    *d_ptr++ = (uint8_t)(s >> 8);
+    o.status.repr = NULL;
+    goto done;
+  } while (0);
+
+src2:
+  do {
+    uint32_t s = wuffs_base__load_u16le__no_bounds_check(s_ptr);
+    uint32_t s0 = alphabet[0xFF & (s >> 0)];
+    uint32_t s1 = alphabet[0xFF & (s >> 8)];
+    if ((s0 & 0xC0) || (s1 & 0xCF)) {
+      o.status.repr = wuffs_base__error__bad_data;
+      goto done;
+    }
+    if (d_len < 1) {
+      o.status.repr = wuffs_base__suspension__short_write;
+      goto done;
+    }
+    s_ptr += pad ? 4 : 2;
+    s = (s0 << 18) | (s1 << 12);
+    *d_ptr++ = (uint8_t)(s >> 16);
+    o.status.repr = NULL;
+    goto done;
+  } while (0);
+
+done:
+  o.num_dst = d_ptr - dst.ptr;
+  o.num_src = s_ptr - src.ptr;
+  return o;
+}
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__encode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options) {
+  const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET)
+                                ? wuffs_base__base_64__encode_url
+                                : wuffs_base__base_64__encode_std;
+  wuffs_base__transform__output o;
+  uint8_t* d_ptr = dst.ptr;
+  size_t d_len = dst.len;
+  const uint8_t* s_ptr = src.ptr;
+  size_t s_len = src.len;
+
+  do {
+    while (s_len >= 3) {
+      if (d_len < 4) {
+        o.status.repr = wuffs_base__suspension__short_write;
+        goto done;
+      }
+      uint32_t s = wuffs_base__load_u24be__no_bounds_check(s_ptr);
+      s_ptr += 3;
+      s_len -= 3;
+      *d_ptr++ = alphabet[0x3F & (s >> 18)];
+      *d_ptr++ = alphabet[0x3F & (s >> 12)];
+      *d_ptr++ = alphabet[0x3F & (s >> 6)];
+      *d_ptr++ = alphabet[0x3F & (s >> 0)];
+      d_len -= 4;
+    }
+
+    if (!src_closed) {
+      o.status.repr = wuffs_base__suspension__short_read;
+      goto done;
+    }
+
+    if (s_len == 2) {
+      if (d_len <
+          ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 3)) {
+        o.status.repr = wuffs_base__suspension__short_write;
+        goto done;
+      }
+      uint32_t s = ((uint32_t)(wuffs_base__load_u16be__no_bounds_check(s_ptr)))
+                   << 8;
+      s_ptr += 2;
+      *d_ptr++ = alphabet[0x3F & (s >> 18)];
+      *d_ptr++ = alphabet[0x3F & (s >> 12)];
+      *d_ptr++ = alphabet[0x3F & (s >> 6)];
+      if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) {
+        *d_ptr++ = '=';
+      }
+      o.status.repr = NULL;
+      goto done;
+
+    } else if (s_len == 1) {
+      if (d_len <
+          ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 2)) {
+        o.status.repr = wuffs_base__suspension__short_write;
+        goto done;
+      }
+      uint32_t s = ((uint32_t)(wuffs_base__load_u8__no_bounds_check(s_ptr)))
+                   << 16;
+      s_ptr += 1;
+      *d_ptr++ = alphabet[0x3F & (s >> 18)];
+      *d_ptr++ = alphabet[0x3F & (s >> 12)];
+      if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) {
+        *d_ptr++ = '=';
+        *d_ptr++ = '=';
+      }
+      o.status.repr = NULL;
+      goto done;
+
+    } else {
+      o.status.repr = NULL;
+      goto done;
+    }
+  } while (0);
+
+done:
+  o.num_dst = d_ptr - dst.ptr;
+  o.num_src = s_ptr - src.ptr;
+  return o;
+}
diff --git a/internal/cgen/base/strconv-public.h b/internal/cgen/base/strconv-public.h
index 561b954..071476d 100644
--- a/internal/cgen/base/strconv-public.h
+++ b/internal/cgen/base/strconv-public.h
@@ -295,6 +295,57 @@
 wuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,
                                  wuffs_base__slice_u8 src);
 
+// ---------------- Base-64
+
+// Options (bitwise or'ed together) for wuffs_base__base_64__xxx functions.
+
+#define WUFFS_BASE__BASE_64__DEFAULT_OPTIONS ((uint32_t)0x00000000)
+
+// WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING means that, when decoding base-64,
+// the input may (but does not need to) be padded with '=' bytes so that the
+// overall encoded length in bytes is a multiple of 4. A successful decoding
+// will return a num_src that includes those padding bytes.
+//
+// Excess padding (e.g. three final '='s) will be rejected as bad data.
+#define WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING ((uint32_t)0x00000001)
+
+// WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING means that, when encoding base-64,
+// the output will be padded with '=' bytes so that the overall encoded length
+// in bytes is a multiple of 4.
+#define WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING ((uint32_t)0x00000002)
+
+// WUFFS_BASE__BASE_64__URL_ALPHABET means that, for base-64, the URL-friendly
+// and file-name-friendly alphabet be used, as per RFC 4648 section 5. When
+// this option bit is off, the standard alphabet from section 4 is used.
+#define WUFFS_BASE__BASE_64__URL_ALPHABET ((uint32_t)0x00000100)
+
+// wuffs_base__base_64__decode transforms base-64 encoded bytes from src to
+// arbitrary bytes in dst.
+//
+// It will not permit line breaks or other whitespace in src. Filtering those
+// out is the responsibility of the caller.
+//
+// For modular builds that divide the base module into sub-modules, using this
+// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
+// just WUFFS_CONFIG__MODULE__BASE__CORE.
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__decode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options);
+
+// wuffs_base__base_64__encode transforms arbitrary bytes from src to base-64
+// encoded bytes in dst.
+//
+// For modular builds that divide the base module into sub-modules, using this
+// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
+// just WUFFS_CONFIG__MODULE__BASE__CORE.
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__encode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options);
+
 // ---------------- Unicode and UTF-8
 
 #define WUFFS_BASE__UNICODE_CODE_POINT__MIN_INCL 0x00000000
diff --git a/internal/cgen/data/data.go b/internal/cgen/data/data.go
index 0571418..44b14b4 100644
--- a/internal/cgen/data/data.go
+++ b/internal/cgen/data/data.go
@@ -176,7 +176,22 @@
 	"er_u64(dst, u, options,\n                                                               neg);\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_u64(wuffs_base__slice_u8 dst,\n                              uint64_t x,\n                              uint32_t options) {\n  return wuffs_base__private_implementation__render_number_u64(dst, x, options,\n                                                               false);\n}\n\n" +
 	"" +
 	"// ---------------- Hexadecimal\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len2 = src.len / 2;\n  size_t len = dst.len < src_len2 ? dst.len : src_len2;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[0]] << 4) |\n                   (wuffs_base__parse_number__hexadecimal_digits[s[1]] & 0x0F));\n    d += 1;\n    s += 2;\n  }\n\n  return len;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len4 = src.len / 4;\n  size_t len = dst.len < src_len4 ? dst.len : src_len4;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[2]] << 4) |\n                   (wuffs_base__parse_number__hexa" +
-	"decimal_digits[s[3]] & 0x0F));\n    d += 1;\n    s += 4;\n  }\n\n  return len;\n}\n" +
+	"decimal_digits[s[3]] & 0x0F));\n    d += 1;\n    s += 4;\n  }\n\n  return len;\n}\n\n" +
+	"" +
+	"// ---------------- Base-64\n\n// The two base-64 alphabets, std and url, differ only in the last two codes.\n//  - std: \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n//  - url: \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\"\n\nstatic const uint8_t wuffs_base__base_64__decode_std[256] = {\n    // 0     1     2     3     4     5     6     7\n    // 8     9     A     B     C     D     E     F\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x00 ..= 0x07.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x08 ..= 0x0F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x10 ..= 0x17.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x18 ..= 0x1F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x20 ..= 0x27.\n    0x80, 0x80, 0x80, 0x3E, 0x80, 0x80, 0x80, 0x3F,  // 0x28 ..= 0x2F.\n    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,  // 0x30 ..= 0x37.\n    0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x38 ..= 0x3F.\n\n    0x80, 0x00, 0x01, 0x02," +
+	" 0x03, 0x04, 0x05, 0x06,  // 0x40 ..= 0x47.\n    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  // 0x48 ..= 0x4F.\n    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,  // 0x50 ..= 0x57.\n    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x58 ..= 0x5F.\n    0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,  // 0x60 ..= 0x67.\n    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,  // 0x68 ..= 0x6F.\n    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,  // 0x70 ..= 0x77.\n    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x78 ..= 0x7F.\n\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x80 ..= 0x87.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x88 ..= 0x8F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x90 ..= 0x97.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x98 ..= 0x9F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA0 ..= 0xA7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA8 ..= 0xAF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // " +
+	"0xB0 ..= 0xB7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB8 ..= 0xBF.\n\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC0 ..= 0xC7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC8 ..= 0xCF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD0 ..= 0xD7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD8 ..= 0xDF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE0 ..= 0xE7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE8 ..= 0xEF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF0 ..= 0xF7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF8 ..= 0xFF.\n    // 0     1     2     3     4     5     6     7\n    // 8     9     A     B     C     D     E     F\n};\n\nstatic const uint8_t wuffs_base__base_64__decode_url[256] = {\n    // 0     1     2     3     4     5     6     7\n    // 8     9     A     B     C     D     E     F\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x00 ..= 0x07.\n    0x80, 0x80, 0x80, 0x80, " +
+	"0x80, 0x80, 0x80, 0x80,  // 0x08 ..= 0x0F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x10 ..= 0x17.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x18 ..= 0x1F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x20 ..= 0x27.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x3E, 0x80, 0x80,  // 0x28 ..= 0x2F.\n    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,  // 0x30 ..= 0x37.\n    0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x38 ..= 0x3F.\n\n    0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,  // 0x40 ..= 0x47.\n    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  // 0x48 ..= 0x4F.\n    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,  // 0x50 ..= 0x57.\n    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x3F,  // 0x58 ..= 0x5F.\n    0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,  // 0x60 ..= 0x67.\n    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,  // 0x68 ..= 0x6F.\n    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,  // 0x70 ..= 0x77.\n    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0" +
+	"x78 ..= 0x7F.\n\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x80 ..= 0x87.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x88 ..= 0x8F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x90 ..= 0x97.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x98 ..= 0x9F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA0 ..= 0xA7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA8 ..= 0xAF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB0 ..= 0xB7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB8 ..= 0xBF.\n\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC0 ..= 0xC7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC8 ..= 0xCF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD0 ..= 0xD7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD8 ..= 0xDF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE0 ..= 0xE7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE8 ..= 0xEF.\n    0x80, 0x80" +
+	", 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF0 ..= 0xF7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF8 ..= 0xFF.\n    // 0     1     2     3     4     5     6     7\n    // 8     9     A     B     C     D     E     F\n};\n\nstatic const uint8_t wuffs_base__base_64__encode_std[64] = {\n    // 0     1     2     3     4     5     6     7\n    // 8     9     A     B     C     D     E     F\n    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,  // 0x00 ..= 0x07.\n    0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,  // 0x08 ..= 0x0F.\n    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,  // 0x10 ..= 0x17.\n    0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,  // 0x18 ..= 0x1F.\n    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,  // 0x20 ..= 0x27.\n    0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,  // 0x28 ..= 0x2F.\n    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,  // 0x30 ..= 0x37.\n    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F,  // 0x38 ..= 0x3F.\n};\n\nstatic const uint8_t wuffs_base__base_64__encode_url[64" +
+	"] = {\n    // 0     1     2     3     4     5     6     7\n    // 8     9     A     B     C     D     E     F\n    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,  // 0x00 ..= 0x07.\n    0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,  // 0x08 ..= 0x0F.\n    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,  // 0x10 ..= 0x17.\n    0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,  // 0x18 ..= 0x1F.\n    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,  // 0x20 ..= 0x27.\n    0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,  // 0x28 ..= 0x2F.\n    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,  // 0x30 ..= 0x37.\n    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2D, 0x5F,  // 0x38 ..= 0x3F.\n};\n\n" +
+	"" +
+	"// --------\n\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_64__decode(wuffs_base__slice_u8 dst,\n                            wuffs_base__slice_u8 src,\n                            bool src_closed,\n                            uint32_t options) {\n  const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET)\n                                ? wuffs_base__base_64__decode_url\n                                : wuffs_base__base_64__decode_std;\n  wuffs_base__transform__output o;\n  uint8_t* d_ptr = dst.ptr;\n  size_t d_len = dst.len;\n  const uint8_t* s_ptr = src.ptr;\n  size_t s_len = src.len;\n  bool pad = false;\n\n  while (s_len >= 4) {\n    uint32_t s = wuffs_base__load_u32le__no_bounds_check(s_ptr);\n    uint32_t s0 = alphabet[0xFF & (s >> 0)];\n    uint32_t s1 = alphabet[0xFF & (s >> 8)];\n    uint32_t s2 = alphabet[0xFF & (s >> 16)];\n    uint32_t s3 = alphabet[0xFF & (s >> 24)];\n\n    if (((s0 | s1 | s2 | s3) & 0xC0) != 0) {\n      if (s_len > 4) {\n        o.status.repr = wuffs_base_" +
+	"_error__bad_data;\n        goto done;\n      } else if (!src_closed) {\n        o.status.repr = wuffs_base__suspension__short_read;\n        goto done;\n      } else if ((options & WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING) &&\n                 (s_ptr[3] == '=')) {\n        pad = true;\n        if (s_ptr[2] == '=') {\n          goto src2;\n        }\n        goto src3;\n      }\n      o.status.repr = wuffs_base__error__bad_data;\n      goto done;\n    }\n\n    if (d_len < 3) {\n      o.status.repr = wuffs_base__suspension__short_write;\n      goto done;\n    }\n\n    s_ptr += 4;\n    s_len -= 4;\n    s = (s0 << 18) | (s1 << 12) | (s2 << 6) | (s3 << 0);\n    *d_ptr++ = (uint8_t)(s >> 16);\n    *d_ptr++ = (uint8_t)(s >> 8);\n    *d_ptr++ = (uint8_t)(s >> 0);\n    d_len -= 3;\n  }\n\n  if (!src_closed) {\n    o.status.repr = wuffs_base__suspension__short_read;\n    goto done;\n  }\n\n  if (s_len == 0) {\n    o.status.repr = NULL;\n    goto done;\n  } else if (s_len == 1) {\n    o.status.repr = wuffs_base__error__bad_data;\n    goto done;\n  } else if (s" +
+	"_len == 2) {\n    goto src2;\n  }\n\nsrc3:\n  do {\n    uint32_t s = wuffs_base__load_u24le__no_bounds_check(s_ptr);\n    uint32_t s0 = alphabet[0xFF & (s >> 0)];\n    uint32_t s1 = alphabet[0xFF & (s >> 8)];\n    uint32_t s2 = alphabet[0xFF & (s >> 16)];\n    if ((s0 & 0xC0) || (s1 & 0xC0) || (s2 & 0xC3)) {\n      o.status.repr = wuffs_base__error__bad_data;\n      goto done;\n    }\n    if (d_len < 2) {\n      o.status.repr = wuffs_base__suspension__short_write;\n      goto done;\n    }\n    s_ptr += pad ? 4 : 3;\n    s = (s0 << 18) | (s1 << 12) | (s2 << 6);\n    *d_ptr++ = (uint8_t)(s >> 16);\n    *d_ptr++ = (uint8_t)(s >> 8);\n    o.status.repr = NULL;\n    goto done;\n  } while (0);\n\nsrc2:\n  do {\n    uint32_t s = wuffs_base__load_u16le__no_bounds_check(s_ptr);\n    uint32_t s0 = alphabet[0xFF & (s >> 0)];\n    uint32_t s1 = alphabet[0xFF & (s >> 8)];\n    if ((s0 & 0xC0) || (s1 & 0xCF)) {\n      o.status.repr = wuffs_base__error__bad_data;\n      goto done;\n    }\n    if (d_len < 1) {\n      o.status.repr = wuffs_base__suspension__sho" +
+	"rt_write;\n      goto done;\n    }\n    s_ptr += pad ? 4 : 2;\n    s = (s0 << 18) | (s1 << 12);\n    *d_ptr++ = (uint8_t)(s >> 16);\n    o.status.repr = NULL;\n    goto done;\n  } while (0);\n\ndone:\n  o.num_dst = d_ptr - dst.ptr;\n  o.num_src = s_ptr - src.ptr;\n  return o;\n}\n\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_64__encode(wuffs_base__slice_u8 dst,\n                            wuffs_base__slice_u8 src,\n                            bool src_closed,\n                            uint32_t options) {\n  const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET)\n                                ? wuffs_base__base_64__encode_url\n                                : wuffs_base__base_64__encode_std;\n  wuffs_base__transform__output o;\n  uint8_t* d_ptr = dst.ptr;\n  size_t d_len = dst.len;\n  const uint8_t* s_ptr = src.ptr;\n  size_t s_len = src.len;\n\n  do {\n    while (s_len >= 3) {\n      if (d_len < 4) {\n        o.status.repr = wuffs_base__suspension__short_write;\n        goto done;\n     " +
+	" }\n      uint32_t s = wuffs_base__load_u24be__no_bounds_check(s_ptr);\n      s_ptr += 3;\n      s_len -= 3;\n      *d_ptr++ = alphabet[0x3F & (s >> 18)];\n      *d_ptr++ = alphabet[0x3F & (s >> 12)];\n      *d_ptr++ = alphabet[0x3F & (s >> 6)];\n      *d_ptr++ = alphabet[0x3F & (s >> 0)];\n      d_len -= 4;\n    }\n\n    if (!src_closed) {\n      o.status.repr = wuffs_base__suspension__short_read;\n      goto done;\n    }\n\n    if (s_len == 2) {\n      if (d_len <\n          ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 3)) {\n        o.status.repr = wuffs_base__suspension__short_write;\n        goto done;\n      }\n      uint32_t s = ((uint32_t)(wuffs_base__load_u16be__no_bounds_check(s_ptr)))\n                   << 8;\n      s_ptr += 2;\n      *d_ptr++ = alphabet[0x3F & (s >> 18)];\n      *d_ptr++ = alphabet[0x3F & (s >> 12)];\n      *d_ptr++ = alphabet[0x3F & (s >> 6)];\n      if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) {\n        *d_ptr++ = '=';\n      }\n      o.status.repr = NULL;\n      goto done;\n\n    } e" +
+	"lse if (s_len == 1) {\n      if (d_len <\n          ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 2)) {\n        o.status.repr = wuffs_base__suspension__short_write;\n        goto done;\n      }\n      uint32_t s = ((uint32_t)(wuffs_base__load_u8__no_bounds_check(s_ptr)))\n                   << 16;\n      s_ptr += 1;\n      *d_ptr++ = alphabet[0x3F & (s >> 18)];\n      *d_ptr++ = alphabet[0x3F & (s >> 12)];\n      if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) {\n        *d_ptr++ = '=';\n        *d_ptr++ = '=';\n      }\n      o.status.repr = NULL;\n      goto done;\n\n    } else {\n      o.status.repr = NULL;\n      goto done;\n    }\n  } while (0);\n\ndone:\n  o.num_dst = d_ptr - dst.ptr;\n  o.num_src = s_ptr - src.ptr;\n  return o;\n}\n" +
 	""
 
 const BasePixConvSubmoduleC = "" +
@@ -297,6 +312,8 @@
 	"" +
 	"// --------\n\n// WUFFS_BASE__RESULT is a result type: either a status (an error) or a value.\n//\n// A result with all fields NULL or zero is as valid as a zero-valued T.\n#define WUFFS_BASE__RESULT(T)  \\\n  struct {                     \\\n    wuffs_base__status status; \\\n    T value;                   \\\n  }\n\ntypedef WUFFS_BASE__RESULT(double) wuffs_base__result_f64;\ntypedef WUFFS_BASE__RESULT(int64_t) wuffs_base__result_i64;\ntypedef WUFFS_BASE__RESULT(uint64_t) wuffs_base__result_u64;\n\n" +
 	"" +
+	"// --------\n\n// wuffs_base__transform__output is the result of transforming from a src slice\n// to a dst slice.\ntypedef struct {\n  wuffs_base__status status;\n  size_t num_dst;\n  size_t num_src;\n} wuffs_base__transform__output;\n\n" +
+	"" +
 	"// --------\n\n// FourCC constants.\n\n// !! INSERT FourCCs.\n\n" +
 	"" +
 	"// --------\n\n// Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a\n// second. See https://github.com/OculusVR/Flicks\ntypedef int64_t wuffs_base__flicks;\n\n#define WUFFS_BASE__FLICKS_PER_SECOND ((uint64_t)705600000)\n#define WUFFS_BASE__FLICKS_PER_MILLISECOND ((uint64_t)705600)\n\n" +
@@ -512,6 +529,10 @@
 	"// ---------------- Hexadecimal\n\n// wuffs_base__hexadecimal__decode2 converts \"6A6b\" to \"jk\", where e.g. 'j' is\n// U+006A. There are 2 source bytes for every destination byte.\n//\n// It returns the number of dst bytes written: the minimum of dst.len and\n// (src.len / 2). Excess source bytes are ignored.\n//\n// It assumes that the src bytes are two hexadecimal digits (0-9, A-F, a-f),\n// repeated. It may write nonsense bytes if not, although it will not read or\n// write out of bounds.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src);\n\n// wuffs_base__hexadecimal__decode4 converts \"\\\\x6A\\\\x6b\" to \"jk\", where e.g.\n// 'j' is U+006A. There are 4 source bytes for every destination byte.\n//\n// It returns the number of d" +
 	"st bytes written: the minimum of dst.len and\n// (src.len / 4). Excess source bytes are ignored.\n//\n// It assumes that the src bytes are two ignored bytes and then two hexadecimal\n// digits (0-9, A-F, a-f), repeated. It may write nonsense bytes if not,\n// although it will not read or write out of bounds.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src);\n\n" +
 	"" +
+	"// ---------------- Base-64\n\n// Options (bitwise or'ed together) for wuffs_base__base_64__xxx functions.\n\n#define WUFFS_BASE__BASE_64__DEFAULT_OPTIONS ((uint32_t)0x00000000)\n\n// WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING means that, when decoding base-64,\n// the input may (but does not need to) be padded with '=' bytes so that the\n// overall encoded length in bytes is a multiple of 4. A successful decoding\n// will return a num_src that includes those padding bytes.\n//\n// Excess padding (e.g. three final '='s) will be rejected as bad data.\n#define WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING ((uint32_t)0x00000001)\n\n// WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING means that, when encoding base-64,\n// the output will be padded with '=' bytes so that the overall encoded length\n// in bytes is a multiple of 4.\n#define WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING ((uint32_t)0x00000002)\n\n// WUFFS_BASE__BASE_64__URL_ALPHABET means that, for base-64, the URL-friendly\n// and file-name-friendly alphabet be used, as per RFC 4648 sect" +
+	"ion 5. When\n// this option bit is off, the standard alphabet from section 4 is used.\n#define WUFFS_BASE__BASE_64__URL_ALPHABET ((uint32_t)0x00000100)\n\n// wuffs_base__base_64__decode transforms base-64 encoded bytes from src to\n// arbitrary bytes in dst.\n//\n// It will not permit line breaks or other whitespace in src. Filtering those\n// out is the responsibility of the caller.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_64__decode(wuffs_base__slice_u8 dst,\n                            wuffs_base__slice_u8 src,\n                            bool src_closed,\n                            uint32_t options);\n\n// wuffs_base__base_64__encode transforms arbitrary bytes from src to base-64\n// encoded bytes in dst.\n//\n// For modular builds that divide the base module into sub-modules, using this" +
+	"\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_64__encode(wuffs_base__slice_u8 dst,\n                            wuffs_base__slice_u8 src,\n                            bool src_closed,\n                            uint32_t options);\n\n" +
+	"" +
 	"// ---------------- Unicode and UTF-8\n\n#define WUFFS_BASE__UNICODE_CODE_POINT__MIN_INCL 0x00000000\n#define WUFFS_BASE__UNICODE_CODE_POINT__MAX_INCL 0x0010FFFF\n\n#define WUFFS_BASE__UNICODE_REPLACEMENT_CHARACTER 0x0000FFFD\n\n#define WUFFS_BASE__UNICODE_SURROGATE__MIN_INCL 0x0000D800\n#define WUFFS_BASE__UNICODE_SURROGATE__MAX_INCL 0x0000DFFF\n\n#define WUFFS_BASE__ASCII__MIN_INCL 0x00\n#define WUFFS_BASE__ASCII__MAX_INCL 0x7F\n\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH__MIN_INCL 1\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL 4\n\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_1__CODE_POINT__MIN_INCL 0x00000000\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_1__CODE_POINT__MAX_INCL 0x0000007F\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_2__CODE_POINT__MIN_INCL 0x00000080\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_2__CODE_POINT__MAX_INCL 0x000007FF\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_3__CODE_POINT__MIN_INCL 0x00000800\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_3__CODE_POINT__MAX_INCL 0x0000FFFF\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_4__CODE_POINT_" +
 	"_MIN_INCL 0x00010000\n#define WUFFS_BASE__UTF_8__BYTE_LENGTH_4__CODE_POINT__MAX_INCL 0x0010FFFF\n\n" +
 	"" +
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index 6944190..44775f9 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -160,6 +160,7 @@
 	`"#bad argument (length too short)"`,
 	`"#bad argument"`,
 	`"#bad call sequence"`,
+	`"#bad data"`,
 	`"#bad receiver"`,
 	`"#bad restart"`,
 	`"#bad sizeof receiver"`,
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index b24ecaf..9f188e1 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -206,6 +206,7 @@
 extern const char wuffs_base__error__bad_argument_length_too_short[];
 extern const char wuffs_base__error__bad_argument[];
 extern const char wuffs_base__error__bad_call_sequence[];
+extern const char wuffs_base__error__bad_data[];
 extern const char wuffs_base__error__bad_receiver[];
 extern const char wuffs_base__error__bad_restart[];
 extern const char wuffs_base__error__bad_sizeof_receiver[];
@@ -319,6 +320,16 @@
 
 // --------
 
+// wuffs_base__transform__output is the result of transforming from a src slice
+// to a dst slice.
+typedef struct {
+  wuffs_base__status status;
+  size_t num_dst;
+  size_t num_src;
+} wuffs_base__transform__output;
+
+// --------
+
 // FourCC constants.
 
 // International Color Consortium Profile.
@@ -4201,6 +4212,57 @@
 wuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,
                                  wuffs_base__slice_u8 src);
 
+// ---------------- Base-64
+
+// Options (bitwise or'ed together) for wuffs_base__base_64__xxx functions.
+
+#define WUFFS_BASE__BASE_64__DEFAULT_OPTIONS ((uint32_t)0x00000000)
+
+// WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING means that, when decoding base-64,
+// the input may (but does not need to) be padded with '=' bytes so that the
+// overall encoded length in bytes is a multiple of 4. A successful decoding
+// will return a num_src that includes those padding bytes.
+//
+// Excess padding (e.g. three final '='s) will be rejected as bad data.
+#define WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING ((uint32_t)0x00000001)
+
+// WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING means that, when encoding base-64,
+// the output will be padded with '=' bytes so that the overall encoded length
+// in bytes is a multiple of 4.
+#define WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING ((uint32_t)0x00000002)
+
+// WUFFS_BASE__BASE_64__URL_ALPHABET means that, for base-64, the URL-friendly
+// and file-name-friendly alphabet be used, as per RFC 4648 section 5. When
+// this option bit is off, the standard alphabet from section 4 is used.
+#define WUFFS_BASE__BASE_64__URL_ALPHABET ((uint32_t)0x00000100)
+
+// wuffs_base__base_64__decode transforms base-64 encoded bytes from src to
+// arbitrary bytes in dst.
+//
+// It will not permit line breaks or other whitespace in src. Filtering those
+// out is the responsibility of the caller.
+//
+// For modular builds that divide the base module into sub-modules, using this
+// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
+// just WUFFS_CONFIG__MODULE__BASE__CORE.
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__decode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options);
+
+// wuffs_base__base_64__encode transforms arbitrary bytes from src to base-64
+// encoded bytes in dst.
+//
+// For modular builds that divide the base module into sub-modules, using this
+// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
+// just WUFFS_CONFIG__MODULE__BASE__CORE.
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__encode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options);
+
 // ---------------- Unicode and UTF-8
 
 #define WUFFS_BASE__UNICODE_CODE_POINT__MIN_INCL 0x00000000
@@ -8391,6 +8453,7 @@
 const char wuffs_base__error__bad_argument_length_too_short[] = "#base: bad argument (length too short)";
 const char wuffs_base__error__bad_argument[] = "#base: bad argument";
 const char wuffs_base__error__bad_call_sequence[] = "#base: bad call sequence";
+const char wuffs_base__error__bad_data[] = "#base: bad data";
 const char wuffs_base__error__bad_receiver[] = "#base: bad receiver";
 const char wuffs_base__error__bad_restart[] = "#base: bad restart";
 const char wuffs_base__error__bad_sizeof_receiver[] = "#base: bad sizeof receiver";
@@ -11940,6 +12003,325 @@
   return len;
 }
 
+// ---------------- Base-64
+
+// The two base-64 alphabets, std and url, differ only in the last two codes.
+//  - std: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+//  - url: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+
+static const uint8_t wuffs_base__base_64__decode_std[256] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x00 ..= 0x07.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x08 ..= 0x0F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x10 ..= 0x17.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x18 ..= 0x1F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x20 ..= 0x27.
+    0x80, 0x80, 0x80, 0x3E, 0x80, 0x80, 0x80, 0x3F,  // 0x28 ..= 0x2F.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,  // 0x30 ..= 0x37.
+    0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x38 ..= 0x3F.
+
+    0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,  // 0x40 ..= 0x47.
+    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  // 0x48 ..= 0x4F.
+    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,  // 0x50 ..= 0x57.
+    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x58 ..= 0x5F.
+    0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,  // 0x60 ..= 0x67.
+    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,  // 0x68 ..= 0x6F.
+    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,  // 0x70 ..= 0x77.
+    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x78 ..= 0x7F.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x80 ..= 0x87.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x88 ..= 0x8F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x90 ..= 0x97.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x98 ..= 0x9F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA0 ..= 0xA7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA8 ..= 0xAF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB0 ..= 0xB7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB8 ..= 0xBF.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC0 ..= 0xC7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC8 ..= 0xCF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD0 ..= 0xD7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD8 ..= 0xDF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE0 ..= 0xE7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE8 ..= 0xEF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF0 ..= 0xF7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF8 ..= 0xFF.
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+};
+
+static const uint8_t wuffs_base__base_64__decode_url[256] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x00 ..= 0x07.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x08 ..= 0x0F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x10 ..= 0x17.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x18 ..= 0x1F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x20 ..= 0x27.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x3E, 0x80, 0x80,  // 0x28 ..= 0x2F.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,  // 0x30 ..= 0x37.
+    0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x38 ..= 0x3F.
+
+    0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,  // 0x40 ..= 0x47.
+    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  // 0x48 ..= 0x4F.
+    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,  // 0x50 ..= 0x57.
+    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x3F,  // 0x58 ..= 0x5F.
+    0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,  // 0x60 ..= 0x67.
+    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,  // 0x68 ..= 0x6F.
+    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,  // 0x70 ..= 0x77.
+    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x78 ..= 0x7F.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x80 ..= 0x87.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x88 ..= 0x8F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x90 ..= 0x97.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x98 ..= 0x9F.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA0 ..= 0xA7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA8 ..= 0xAF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB0 ..= 0xB7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xB8 ..= 0xBF.
+
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC0 ..= 0xC7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xC8 ..= 0xCF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD0 ..= 0xD7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xD8 ..= 0xDF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE0 ..= 0xE7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xE8 ..= 0xEF.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF0 ..= 0xF7.
+    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xF8 ..= 0xFF.
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+};
+
+static const uint8_t wuffs_base__base_64__encode_std[64] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,  // 0x00 ..= 0x07.
+    0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,  // 0x08 ..= 0x0F.
+    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,  // 0x10 ..= 0x17.
+    0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,  // 0x18 ..= 0x1F.
+    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,  // 0x20 ..= 0x27.
+    0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,  // 0x28 ..= 0x2F.
+    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,  // 0x30 ..= 0x37.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F,  // 0x38 ..= 0x3F.
+};
+
+static const uint8_t wuffs_base__base_64__encode_url[64] = {
+    // 0     1     2     3     4     5     6     7
+    // 8     9     A     B     C     D     E     F
+    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,  // 0x00 ..= 0x07.
+    0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,  // 0x08 ..= 0x0F.
+    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,  // 0x10 ..= 0x17.
+    0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,  // 0x18 ..= 0x1F.
+    0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,  // 0x20 ..= 0x27.
+    0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,  // 0x28 ..= 0x2F.
+    0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,  // 0x30 ..= 0x37.
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2D, 0x5F,  // 0x38 ..= 0x3F.
+};
+
+// --------
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__decode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options) {
+  const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET)
+                                ? wuffs_base__base_64__decode_url
+                                : wuffs_base__base_64__decode_std;
+  wuffs_base__transform__output o;
+  uint8_t* d_ptr = dst.ptr;
+  size_t d_len = dst.len;
+  const uint8_t* s_ptr = src.ptr;
+  size_t s_len = src.len;
+  bool pad = false;
+
+  while (s_len >= 4) {
+    uint32_t s = wuffs_base__load_u32le__no_bounds_check(s_ptr);
+    uint32_t s0 = alphabet[0xFF & (s >> 0)];
+    uint32_t s1 = alphabet[0xFF & (s >> 8)];
+    uint32_t s2 = alphabet[0xFF & (s >> 16)];
+    uint32_t s3 = alphabet[0xFF & (s >> 24)];
+
+    if (((s0 | s1 | s2 | s3) & 0xC0) != 0) {
+      if (s_len > 4) {
+        o.status.repr = wuffs_base__error__bad_data;
+        goto done;
+      } else if (!src_closed) {
+        o.status.repr = wuffs_base__suspension__short_read;
+        goto done;
+      } else if ((options & WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING) &&
+                 (s_ptr[3] == '=')) {
+        pad = true;
+        if (s_ptr[2] == '=') {
+          goto src2;
+        }
+        goto src3;
+      }
+      o.status.repr = wuffs_base__error__bad_data;
+      goto done;
+    }
+
+    if (d_len < 3) {
+      o.status.repr = wuffs_base__suspension__short_write;
+      goto done;
+    }
+
+    s_ptr += 4;
+    s_len -= 4;
+    s = (s0 << 18) | (s1 << 12) | (s2 << 6) | (s3 << 0);
+    *d_ptr++ = (uint8_t)(s >> 16);
+    *d_ptr++ = (uint8_t)(s >> 8);
+    *d_ptr++ = (uint8_t)(s >> 0);
+    d_len -= 3;
+  }
+
+  if (!src_closed) {
+    o.status.repr = wuffs_base__suspension__short_read;
+    goto done;
+  }
+
+  if (s_len == 0) {
+    o.status.repr = NULL;
+    goto done;
+  } else if (s_len == 1) {
+    o.status.repr = wuffs_base__error__bad_data;
+    goto done;
+  } else if (s_len == 2) {
+    goto src2;
+  }
+
+src3:
+  do {
+    uint32_t s = wuffs_base__load_u24le__no_bounds_check(s_ptr);
+    uint32_t s0 = alphabet[0xFF & (s >> 0)];
+    uint32_t s1 = alphabet[0xFF & (s >> 8)];
+    uint32_t s2 = alphabet[0xFF & (s >> 16)];
+    if ((s0 & 0xC0) || (s1 & 0xC0) || (s2 & 0xC3)) {
+      o.status.repr = wuffs_base__error__bad_data;
+      goto done;
+    }
+    if (d_len < 2) {
+      o.status.repr = wuffs_base__suspension__short_write;
+      goto done;
+    }
+    s_ptr += pad ? 4 : 3;
+    s = (s0 << 18) | (s1 << 12) | (s2 << 6);
+    *d_ptr++ = (uint8_t)(s >> 16);
+    *d_ptr++ = (uint8_t)(s >> 8);
+    o.status.repr = NULL;
+    goto done;
+  } while (0);
+
+src2:
+  do {
+    uint32_t s = wuffs_base__load_u16le__no_bounds_check(s_ptr);
+    uint32_t s0 = alphabet[0xFF & (s >> 0)];
+    uint32_t s1 = alphabet[0xFF & (s >> 8)];
+    if ((s0 & 0xC0) || (s1 & 0xCF)) {
+      o.status.repr = wuffs_base__error__bad_data;
+      goto done;
+    }
+    if (d_len < 1) {
+      o.status.repr = wuffs_base__suspension__short_write;
+      goto done;
+    }
+    s_ptr += pad ? 4 : 2;
+    s = (s0 << 18) | (s1 << 12);
+    *d_ptr++ = (uint8_t)(s >> 16);
+    o.status.repr = NULL;
+    goto done;
+  } while (0);
+
+done:
+  o.num_dst = d_ptr - dst.ptr;
+  o.num_src = s_ptr - src.ptr;
+  return o;
+}
+
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_64__encode(wuffs_base__slice_u8 dst,
+                            wuffs_base__slice_u8 src,
+                            bool src_closed,
+                            uint32_t options) {
+  const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET)
+                                ? wuffs_base__base_64__encode_url
+                                : wuffs_base__base_64__encode_std;
+  wuffs_base__transform__output o;
+  uint8_t* d_ptr = dst.ptr;
+  size_t d_len = dst.len;
+  const uint8_t* s_ptr = src.ptr;
+  size_t s_len = src.len;
+
+  do {
+    while (s_len >= 3) {
+      if (d_len < 4) {
+        o.status.repr = wuffs_base__suspension__short_write;
+        goto done;
+      }
+      uint32_t s = wuffs_base__load_u24be__no_bounds_check(s_ptr);
+      s_ptr += 3;
+      s_len -= 3;
+      *d_ptr++ = alphabet[0x3F & (s >> 18)];
+      *d_ptr++ = alphabet[0x3F & (s >> 12)];
+      *d_ptr++ = alphabet[0x3F & (s >> 6)];
+      *d_ptr++ = alphabet[0x3F & (s >> 0)];
+      d_len -= 4;
+    }
+
+    if (!src_closed) {
+      o.status.repr = wuffs_base__suspension__short_read;
+      goto done;
+    }
+
+    if (s_len == 2) {
+      if (d_len <
+          ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 3)) {
+        o.status.repr = wuffs_base__suspension__short_write;
+        goto done;
+      }
+      uint32_t s = ((uint32_t)(wuffs_base__load_u16be__no_bounds_check(s_ptr)))
+                   << 8;
+      s_ptr += 2;
+      *d_ptr++ = alphabet[0x3F & (s >> 18)];
+      *d_ptr++ = alphabet[0x3F & (s >> 12)];
+      *d_ptr++ = alphabet[0x3F & (s >> 6)];
+      if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) {
+        *d_ptr++ = '=';
+      }
+      o.status.repr = NULL;
+      goto done;
+
+    } else if (s_len == 1) {
+      if (d_len <
+          ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 2)) {
+        o.status.repr = wuffs_base__suspension__short_write;
+        goto done;
+      }
+      uint32_t s = ((uint32_t)(wuffs_base__load_u8__no_bounds_check(s_ptr)))
+                   << 16;
+      s_ptr += 1;
+      *d_ptr++ = alphabet[0x3F & (s >> 18)];
+      *d_ptr++ = alphabet[0x3F & (s >> 12)];
+      if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) {
+        *d_ptr++ = '=';
+        *d_ptr++ = '=';
+      }
+      o.status.repr = NULL;
+      goto done;
+
+    } else {
+      o.status.repr = NULL;
+      goto done;
+    }
+  } while (0);
+
+done:
+  o.num_dst = d_ptr - dst.ptr;
+  o.num_src = s_ptr - src.ptr;
+  return o;
+}
+
 #endif  // !defined(WUFFS_CONFIG__MODULES) ||
         // defined(WUFFS_CONFIG__MODULE__BASE) ||
         // defined(WUFFS_CONFIG__MODULE__BASE__I64CONV)
diff --git a/test/c/std/json.c b/test/c/std/json.c
index 5884997..7c8f527 100644
--- a/test/c/std/json.c
+++ b/test/c/std/json.c
@@ -471,6 +471,94 @@
 }
 
 const char*  //
+test_wuffs_strconv_base_64() {
+  CHECK_FOCUS(__func__);
+
+  char* foobar = "foobar";
+  const char* wants[] = {
+      "",          //
+      "Zg==",      //
+      "Zm8=",      //
+      "Zm9v",      //
+      "Zm9vYg==",  //
+      "Zm9vYmE=",  //
+      "Zm9vYmFy",  //
+  };
+
+  size_t tc;
+  for (tc = 0; tc < WUFFS_TESTLIB_ARRAY_SIZE(wants); tc++) {
+    const bool src_closed = true;
+
+    wuffs_base__transform__output e = wuffs_base__base_64__encode(
+        g_have_slice_u8, wuffs_base__make_slice_u8(((uint8_t*)foobar), tc),
+        src_closed, WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING);
+    if (e.status.repr) {
+      RETURN_FAIL("tc=%zu: encode: \"%s\"", tc, e.status.repr);
+    }
+    if (e.num_src != tc) {
+      RETURN_FAIL("tc=%zu: encode: num_src: have %zu, want %zu", tc, e.num_src,
+                  tc);
+    }
+    if (e.num_dst != (((tc + 2) / 3) * 4)) {
+      RETURN_FAIL("tc=%zu: encode: num_dst: have %zu, want %zu", tc, e.num_dst,
+                  (((tc + 2) / 3) * 4));
+    }
+    if ((e.num_dst + 1) > g_have_slice_u8.len) {
+      RETURN_FAIL("tc=%zu: encode: num_dst is too large", tc);
+    }
+    g_have_slice_u8.ptr[e.num_dst] = '\x00';
+    if (strncmp((const char*)(g_have_slice_u8.ptr), wants[tc], e.num_dst) !=
+        0) {
+      RETURN_FAIL("tc=%zu: encode:\nhave \"%s\"\nwant \"%s\"", tc,
+                  g_have_slice_u8.ptr, wants[tc]);
+    }
+
+    int trim;
+    for (trim = 0; trim < 2; trim++) {
+      size_t n = e.num_dst;
+      if (trim) {
+        while ((n > 0) && (g_have_slice_u8.ptr[n - 1] == '=')) {
+          n--;
+        }
+      }
+
+      wuffs_base__transform__output d = wuffs_base__base_64__decode(
+          g_work_slice_u8, wuffs_base__make_slice_u8(g_have_slice_u8.ptr, n),
+          src_closed, WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING);
+      if (d.status.repr) {
+        RETURN_FAIL("tc=%zu: trim=%d: decode: \"%s\"", tc, trim, d.status.repr);
+      }
+      if (d.num_src != n) {
+        RETURN_FAIL("tc=%zu: trim=%d: decode: num_src: have %zu, want %zu", tc,
+                    trim, d.num_src, n);
+      }
+      if (d.num_dst != tc) {
+        RETURN_FAIL("tc=%zu: trim=%d: decode: num_dst: have %zu, want %zu", tc,
+                    trim, d.num_dst, tc);
+      }
+      if ((d.num_dst + 1) > g_work_slice_u8.len) {
+        RETURN_FAIL("tc=%zu: trim=%d: decode: num_dst is too large", tc, trim);
+      }
+      g_work_slice_u8.ptr[d.num_dst] = '\x00';
+      if ((tc + 1) > g_want_slice_u8.len) {
+        RETURN_FAIL("tc=%zu: trim=%d: decode: tc is too large", tc, trim);
+      }
+      memcpy(g_want_slice_u8.ptr, foobar, tc);
+      g_want_slice_u8.ptr[tc] = '\x00';
+      if (strcmp(((const char*)(g_work_slice_u8.ptr)),
+                 ((const char*)(g_want_slice_u8.ptr))) != 0) {
+        RETURN_FAIL("tc=%zu: trim=%d: decode:\nhave \"%s\"\nwant \"%s\"", tc,
+                    trim, g_work_slice_u8.ptr, g_want_slice_u8.ptr);
+      }
+    }
+  }
+
+  return NULL;
+}
+
+// ----------------
+
+const char*  //
 test_wuffs_strconv_parse_number_f64() {
   CHECK_FOCUS(__func__);
 
@@ -751,6 +839,8 @@
   return NULL;
 }
 
+// ----------------
+
 const char*  //
 test_wuffs_strconv_render_number_f64() {
   CHECK_FOCUS(__func__);
@@ -1636,6 +1726,8 @@
   return NULL;
 }
 
+// ----------------
+
 const char*  //
 test_wuffs_strconv_utf_8_next() {
   CHECK_FOCUS(__func__);
@@ -3346,6 +3438,7 @@
     test_wuffs_core_count_leading_zeroes_u64,
     test_wuffs_core_multiply_u64,
     test_wuffs_strconv_hexadecimal,
+    test_wuffs_strconv_base_64,
     test_wuffs_strconv_hpd_rounded_integer,
     test_wuffs_strconv_hpd_shift,
     test_wuffs_strconv_parse_number_f64,