Add wuffs_base__table__flattened_length
diff --git a/example/imageviewer/imageviewer.cc b/example/imageviewer/imageviewer.cc
index 717711c..37eb73d 100644
--- a/example/imageviewer/imageviewer.cc
+++ b/example/imageviewer/imageviewer.cc
@@ -120,6 +120,25 @@
     fclose(file);
   }
 
+  // wuffs_aux::DecodeImageCallbacks's default implementation should give us an
+  // interleaved (not multi-planar) pixel buffer, so that all of the pixel data
+  // is in a single 2-dimensional table (plane 0). Later on, we re-interpret
+  // that table as XCB image data, which isn't something we could do if we had
+  // e.g. multi-planar YCbCr.
+  if (!res.pixbuf.pixcfg.pixel_format().is_interleaved()) {
+    printf("%s: non-interleaved pixbuf\n", adj_filename);
+    return false;
+  }
+  wuffs_base__table_u8 tab = res.pixbuf.plane(0);
+  if (tab.width != tab.stride) {
+    // The xcb_image_create_native call, later on, assumes that (tab.height *
+    // tab.stride) bytes are readable, which isn't quite the same as what
+    // wuffs_base__table__flattened_length(tab.width, tab.height, tab.stride)
+    // returns unless the table is tight (its width equals its stride).
+    printf("%s: could not allocate tight pixbuf\n", adj_filename);
+    return false;
+  }
+
   g_width = res.pixbuf.pixcfg.width();
   g_height = res.pixbuf.pixcfg.height();
   g_pixbuf_mem_owner = std::move(res.pixbuf_mem_owner);
diff --git a/internal/cgen/base/fundamental-public.h b/internal/cgen/base/fundamental-public.h
index 1087ec8..14c0911 100644
--- a/internal/cgen/base/fundamental-public.h
+++ b/internal/cgen/base/fundamental-public.h
@@ -1208,6 +1208,45 @@
   return wuffs_base__make_slice_u8(NULL, 0);
 }
 
+// wuffs_base__table__flattened_length returns the number of elements covered
+// by the 1-dimensional span that backs a 2-dimensional table. This counts the
+// elements inside the table and, when width != stride, the elements outside
+// the table but between its rows.
+//
+// For example, consider a width 10, height 4, stride 10 table. Mark its first
+// and last (inclusive) elements with 'a' and 'z'. This function returns 40.
+//
+//    a123456789
+//    0123456789
+//    0123456789
+//    012345678z
+//
+// Now consider the sub-table of that from (2, 1) inclusive to (8, 4) exclusive.
+//
+//    a123456789
+//    01iiiiiioo
+//    ooiiiiiioo
+//    ooiiiiii8z
+//
+// This function (called with width 6, height 3, stride 10) returns 26: 18 'i'
+// inside elements plus 8 'o' outside elements. Note that 26 is less than a
+// naive (height * stride = 30) computation. Indeed, advancing 29 elements from
+// the first 'i' would venture past 'z', out of bounds of the original table.
+//
+// It does not check for overflow, but if the arguments come from a table that
+// exists in memory and each element occupies a positive number of bytes then
+// the result should be bounded by the amount of allocatable memory (which
+// shouldn't overflow SIZE_MAX).
+static inline size_t  //
+wuffs_base__table__flattened_length(size_t width,
+                                    size_t height,
+                                    size_t stride) {
+  if (height == 0) {
+    return 0;
+  }
+  return ((height - 1) * stride) + width;
+}
+
 // ---------------- Magic Numbers
 
 // wuffs_base__magic_number_guess_fourcc guesses the file format of some data,
diff --git a/internal/cgen/data/data.go b/internal/cgen/data/data.go
index 40bfc69..7d9423e 100644
--- a/internal/cgen/data/data.go
+++ b/internal/cgen/data/data.go
@@ -115,7 +115,9 @@
 	"  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u16  //\nwuffs_base__empty_slice_u16() {\n  wuffs_base__slice_u16 ret;\n  ret.ptr = NULL;\n  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u32  //\nwuffs_base__empty_slice_u32() {\n  wuffs_base__slice_u32 ret;\n  ret.ptr = NULL;\n  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__slice_u64  //\nwuffs_base__empty_slice_u64() {\n  wuffs_base__slice_u64 ret;\n  ret.ptr = NULL;\n  ret.len = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u8  //\nwuffs_base__make_table_u8(uint8_t* ptr,\n                          size_t width,\n                          size_t height,\n                          size_t stride) {\n  wuffs_base__table_u8 ret;\n  ret.ptr = ptr;\n  ret.width = width;\n  ret.height = height;\n  ret.stride = stride;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u16  //\nwuffs_base__make_table_u16(uint16_t* ptr,\n                           size_t width,\n                           size_t height,\n                           size_t stride) " +
 	"{\n  wuffs_base__table_u16 ret;\n  ret.ptr = ptr;\n  ret.width = width;\n  ret.height = height;\n  ret.stride = stride;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u32  //\nwuffs_base__make_table_u32(uint32_t* ptr,\n                           size_t width,\n                           size_t height,\n                           size_t stride) {\n  wuffs_base__table_u32 ret;\n  ret.ptr = ptr;\n  ret.width = width;\n  ret.height = height;\n  ret.stride = stride;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u64  //\nwuffs_base__make_table_u64(uint64_t* ptr,\n                           size_t width,\n                           size_t height,\n                           size_t stride) {\n  wuffs_base__table_u64 ret;\n  ret.ptr = ptr;\n  ret.width = width;\n  ret.height = height;\n  ret.stride = stride;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u8  //\nwuffs_base__empty_table_u8() {\n  wuffs_base__table_u8 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__ta" +
 	"ble_u16  //\nwuffs_base__empty_table_u16() {\n  wuffs_base__table_u16 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u32  //\nwuffs_base__empty_table_u32() {\n  wuffs_base__table_u32 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\nstatic inline wuffs_base__table_u64  //\nwuffs_base__empty_table_u64() {\n  wuffs_base__table_u64 ret;\n  ret.ptr = NULL;\n  ret.width = 0;\n  ret.height = 0;\n  ret.stride = 0;\n  return ret;\n}\n\nstatic inline bool  //\nwuffs_base__slice_u8__overlaps(wuffs_base__slice_u8 s, wuffs_base__slice_u8 t) {\n  return ((s.ptr <= t.ptr) && (t.ptr < (s.ptr + s.len))) ||\n         ((t.ptr <= s.ptr) && (s.ptr < (t.ptr + t.len)));\n}\n\n// wuffs_base__slice_u8__subslice_i returns s[i:].\n//\n// It returns an empty slice if i is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_i(wuffs_base__slice_u8 s, uint64_t i) {\n  if ((i <= SIZE_MAX) && (i <= s.len)) {\n   " +
-	" return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(s.len - i)));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n// wuffs_base__slice_u8__subslice_j returns s[:j].\n//\n// It returns an empty slice if j is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_j(wuffs_base__slice_u8 s, uint64_t j) {\n  if ((j <= SIZE_MAX) && (j <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr, ((size_t)j));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n// wuffs_base__slice_u8__subslice_ij returns s[i:j].\n//\n// It returns an empty slice if i or j is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_ij(wuffs_base__slice_u8 s,\n                                  uint64_t i,\n                                  uint64_t j) {\n  if ((i <= j) && (j <= SIZE_MAX) && (j <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(j - i)));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n" +
+	" return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(s.len - i)));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n// wuffs_base__slice_u8__subslice_j returns s[:j].\n//\n// It returns an empty slice if j is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_j(wuffs_base__slice_u8 s, uint64_t j) {\n  if ((j <= SIZE_MAX) && (j <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr, ((size_t)j));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n// wuffs_base__slice_u8__subslice_ij returns s[i:j].\n//\n// It returns an empty slice if i or j is out of bounds.\nstatic inline wuffs_base__slice_u8  //\nwuffs_base__slice_u8__subslice_ij(wuffs_base__slice_u8 s,\n                                  uint64_t i,\n                                  uint64_t j) {\n  if ((i <= j) && (j <= SIZE_MAX) && (j <= s.len)) {\n    return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(j - i)));\n  }\n  return wuffs_base__make_slice_u8(NULL, 0);\n}\n\n// wuffs_base__table__flattened_length returns the number o" +
+	"f elements covered\n// by the 1-dimensional span that backs a 2-dimensional table. This counts the\n// elements inside the table and, when width != stride, the elements outside\n// the table but between its rows.\n//\n// For example, consider a width 10, height 4, stride 10 table. Mark its first\n// and last (inclusive) elements with 'a' and 'z'. This function returns 40.\n//\n//    a123456789\n//    0123456789\n//    0123456789\n//    012345678z\n//\n// Now consider the sub-table of that from (2, 1) inclusive to (8, 4) exclusive.\n//\n//    a123456789\n//    01iiiiiioo\n//    ooiiiiiioo\n//    ooiiiiii8z\n//\n// This function (called with width 6, height 3, stride 10) returns 26: 18 'i'\n// inside elements plus 8 'o' outside elements. Note that 26 is less than a\n// naive (height * stride = 30) computation. Indeed, advancing 29 elements from\n// the first 'i' would venture past 'z', out of bounds of the original table.\n//\n// It does not check for overflow, but if the arguments come from a table that\n// exists in memory and each el" +
+	"ement occupies a positive number of bytes then\n// the result should be bounded by the amount of allocatable memory (which\n// shouldn't overflow SIZE_MAX).\nstatic inline size_t  //\nwuffs_base__table__flattened_length(size_t width,\n                                    size_t height,\n                                    size_t stride) {\n  if (height == 0) {\n    return 0;\n  }\n  return ((height - 1) * stride) + width;\n}\n\n" +
 	"" +
 	"// ---------------- Magic Numbers\n\n// wuffs_base__magic_number_guess_fourcc guesses the file format of some data,\n// given its opening bytes. It returns a positive FourCC value on success.\n//\n// It returns zero if nothing matches its hard-coded list of 'magic numbers'.\n//\n// It returns a negative value if a longer prefix is required for a conclusive\n// result. For example, seeing a single 'B' byte is not enough to discriminate\n// the BMP and BPG image file formats.\n//\n// It does not do a full validity check. Like any guess made from a short\n// prefix of the data, it may return false positives. Data that starts with 99\n// bytes of valid JPEG followed by corruption or truncation is an invalid JPEG\n// image overall, but this function will still return WUFFS_BASE__FOURCC__JPEG.\n//\n// Another source of false positives is that some 'magic numbers' are valid\n// ASCII data. A file starting with \"GIF87a and GIF89a are the two versions of\n// GIF\" will match GIF's 'magic number' even if it's plain text, not an image.\n//" +
 	"\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__MAGIC sub-module, not just\n// WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC int32_t  //\nwuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix);\n" +
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 52c326f..cd10956 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -1419,6 +1419,45 @@
   return wuffs_base__make_slice_u8(NULL, 0);
 }
 
+// wuffs_base__table__flattened_length returns the number of elements covered
+// by the 1-dimensional span that backs a 2-dimensional table. This counts the
+// elements inside the table and, when width != stride, the elements outside
+// the table but between its rows.
+//
+// For example, consider a width 10, height 4, stride 10 table. Mark its first
+// and last (inclusive) elements with 'a' and 'z'. This function returns 40.
+//
+//    a123456789
+//    0123456789
+//    0123456789
+//    012345678z
+//
+// Now consider the sub-table of that from (2, 1) inclusive to (8, 4) exclusive.
+//
+//    a123456789
+//    01iiiiiioo
+//    ooiiiiiioo
+//    ooiiiiii8z
+//
+// This function (called with width 6, height 3, stride 10) returns 26: 18 'i'
+// inside elements plus 8 'o' outside elements. Note that 26 is less than a
+// naive (height * stride = 30) computation. Indeed, advancing 29 elements from
+// the first 'i' would venture past 'z', out of bounds of the original table.
+//
+// It does not check for overflow, but if the arguments come from a table that
+// exists in memory and each element occupies a positive number of bytes then
+// the result should be bounded by the amount of allocatable memory (which
+// shouldn't overflow SIZE_MAX).
+static inline size_t  //
+wuffs_base__table__flattened_length(size_t width,
+                                    size_t height,
+                                    size_t stride) {
+  if (height == 0) {
+    return 0;
+  }
+  return ((height - 1) * stride) + width;
+}
+
 // ---------------- Magic Numbers
 
 // wuffs_base__magic_number_guess_fourcc guesses the file format of some data,