Update pdfium::span<> to more closely match C++20 base::span<>

Differences between base::span<>
  (from  9095c77f3e41ca927f443ad1b1625da853bc01a3)
are documented in the header.

-- update reinterpret_span<>() to infer extent parameter.

Bug: 42271100
Change-Id: I509d0441c86a5b64642c90c42ded4500288474c4
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/131051
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fxcrt/span.h b/core/fxcrt/span.h
index 88d811a..f73cc0d 100644
--- a/core/fxcrt/span.h
+++ b/core/fxcrt/span.h
@@ -1,27 +1,341 @@
 // Copyright 2024 The PDFium Authors
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+//
+// This is Chromium's span.h with the following modificiations:
+// - namespace changed to pdfium
+// - include paths changed for check.h, compiler_specific.h,
+//   integral_constant_like.h, and safe_conversions.h.
+// - include removed for checked_iterators.h,  cstring_view.h, to_string.h,
+//   and to_address.h.
+// - span_forward_internal.h contents inlined in this file.
+// - iterator types changed from checked iterators to ordinary pointers,
+//   and adjusted begin(), end()
+// - removed to_address() calls as iterators are now pointers.
+// - removed operator<<() that pretty-prints to std::stream.
+// - removed any references to std::string_view.
+// - removed any references to base::cstring_view<>.
+// - Added reinterpret_span() helper.
+// - Added make_span() helper method to ease conversion to CTAD.
+// - DCHECK() verbose logging message not supported and removed.
+// - Kept EnableIfSpanCompatibleContainer as in prior versions.
 
-#ifndef CORE_FXCRT_SPAN_H_
-#define CORE_FXCRT_SPAN_H_
+// This file intentionally uses the `CHECK()` macro instead of the `CHECK_op()`
+// macros, as `CHECK()` generates significantly less code and is more likely to
+// optimize reasonably, even in non-official release builds. Please do not
+// change the `CHECK()` calls back to `CHECK_op()` calls.
+
+#ifndef BASE_CONTAINERS_SPAN_H_
+#define BASE_CONTAINERS_SPAN_H_
 
 #include <stddef.h>
 #include <stdint.h>
+#include <string.h>
 
+#include <algorithm>
 #include <array>
+#include <concepts>
+#include <functional>
+#include <initializer_list>
+#include <iosfwd>
 #include <iterator>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <ranges>
+#include <span>
 #include <type_traits>
 #include <utility>
+#include <vector>
 
 #include "core/fxcrt/check.h"
 #include "core/fxcrt/compiler_specific.h"
-#include "core/fxcrt/unowned_ptr_exclusion.h"
+#include "core/fxcrt/numerics/integral_constant_like.h"
+#include "core/fxcrt/numerics/safe_conversions.h"
 
-// SAFETY: TODO(crbug.com/pdfium/2085): this entire file is to be replaced
-// with the fully annotated one that is being prepared in base/.
+// A span is a view of contiguous elements that can be accessed like an array,
+// intended for use as a parameter or local. Unlike direct use of pointers and
+// sizes, it enforces safe usage (and simplifies callers); unlike container
+// refs, it is agnostic to the element container, expressing only "access to
+// some sequence of elements". It is similar to `std::string_view`, but for
+// arbitrary elements instead of just characters, and additionally allowing
+// mutation if the element type is non-`const`.
+//
+// Spans can be constructed from arrays, range-like objects (generally, objects
+// which expose `begin()`, `end()`, `data()`, and `size()`), and initializer
+// lists. As with all view types, spans do not own the underlying memory, so
+// users must ensure they do not outlive their backing stores; storing a span as
+// a member object is usually incorrect. (For the rare case this is useful,
+// prefer `raw_span<>` so the underlying storage pointer will be protected by
+// MiraclePtr.)
+//
+// Since spans only consist of a pointer and (for dynamic-extent spans) a size,
+// they are lightweight; constructing and copying spans is cheap and they should
+// be passed by value.
+//
+// Scopes which only need read access to the underlying data should use
+// `span<const T>`, which can be implicitly constructed from `span<T>`.
+// Habitually using `span<const T>` also avoids confusing compile errors when
+// trying to construct spans from compile-time constants or non-borrowed ranges,
+// which won't convert to `span<T>`.
+//
+// Without span:
+// ```
+//   /* Read-only usage */
+//
+//   // Implementation must avoid OOB reads.
+//   std::string HexEncode(const uint8_t* data, size_t size) { ... }
+//
+//   // Must use a separate variable to avoid repeated generation calls below.
+//   std::vector<uint8_t> data_buffer = GenerateData();
+//   // Prone to accidentally passing the wrong size.
+//   std::string r = HexEncode(data_buffer.data(), data_buffer.size());
+//
+//   /* Mutable usage */
+//
+//   // Same concerns apply in this example.
+//   ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args...) { ... }
+//
+//   char str_buffer[100];
+//   SafeSNPrintf(str_buffer, sizeof(str_buffer), "Pi ~= %lf", 3.14);
+// ```
+//
+// With span:
+// ```
+//   /* Read-only usage */
+//
+//   // Automatically `CHECK()`s on attempted OOB accesses.
+//   std::string HexEncode(span<const uint8_t> data) { ... }
+//
+//   // Can pass return value directly, since it lives until the end of the full
+//   // expression, outlasting the function call. Can't pass wrong size.
+//   std::string r = HexEncode(GenerateData());
+//
+//   /* Mutable usage */
+//
+//   // Can write to `buf`, but only within bounds.
+//   ssize_t SafeSNPrintf(span<char> buf, const char* fmt, Args...) { ... }
+//
+//   char str_buffer[100];
+//   // Automatically infers span size as array size (i.e. 100).
+//   SafeSNPrintf(str_buffer, "Pi ~= %lf", 3.14);
+// ```
+//
+// Dynamic-extent vs. fixed-extent spans
+// -------------------------------------
+// By default spans have dynamic extent, which means that the size is available
+// at runtime via `size()`, a la other containers and views. By using a second
+// template parameter or passing a `std::integral_constant` to the second (size)
+// constructor arg, a span's extent can be fixed at compile time; this can move
+// some constraint checks to compile time and slightly improve codegen, at the
+// cost of verbosity and more template instantiations. Methods like `first()` or
+// `subspan()` also provide templated overloads that produce fixed-extent spans;
+// these are preferred when the size is known at compile time, in part because
+// e.g. `first(1)` is a compile-error (the `int` arg is not compatible with the
+// `StrictNumeric<size_t>` param; use `first(1u)` instead), but `first<1>()` is
+// not.
+//
+// A fixed-extent span implicitly converts to a dynamic-extent span (e.g.
+// `span<int, 6>` is implicitly convertible to `span<int>`), so most code that
+// operates on spans of arbitrary length can just accept a `span<T>`; there is
+// no need to add an additional overload for specially handling the `span<T, N>`
+// case.
+//
+// There are several ways to go from a dynamic-extent span to a fixed-extent
+// span:
+// - Explicit construction of `span<T, N>`, which `CHECK()`s if the size doesn't
+//   match.
+// - Construction of `span(T*, fixed_extent<N>)`, which is equivalent to the
+//   above.
+// - `to_fixed_extent<N>()`, which returns `std::nullopt` if the size doesn't
+//   match.
+// - `first<N>()`, `last<N>()`, and `subspan<Index, N>()`, which `CHECK()` if
+//   the size is insufficient.
+//
+// Spans, `const`, and pointer-type element types
+// ----------------------------------------------
+// Pointer-type elements can make translating `const` from container types to
+// spans confusing. Fundamentally, if you analogize types this way:
+//   `std::vector<T>`       => `span<T>`
+// Then this would be const version:
+//   `const std::vector<T>` => `span<const T>`
+//    (or, more verbosely:) => `span<std::add_const_t<T>>`
+//
+// However, note that if `T` is `int*`, then `const T` is `int* const`. So:
+//   `const std::vector<int*>`       => `span<int* const>`
+//   `std::vector<const int*>`       => `span<const int*>`
+//   `const std::vector<const int*>` => `span<const int* const>`
+//
+// (N.B. There is no entry above for `std::vector<int* const>`, since per the
+// C++ standard, `std::vector`'s element type must be non-const.)
+//
+// Byte spans, `std::has_unique_object_representations_v<>`, and conversions
+// -------------------------------------------------------------------------
+// Because byte spans are often used to copy and hash objects, the byte span
+// conversion functions (e.g. `as_bytes()`, `as_byte_span()`) require the
+// element type to meet `std::has_unique_object_representations_v<>`. For types
+// which do not meet this requirement but need conversion to a byte span, there
+// are two workarounds:
+//   1. If the type is safe to convert to a byte span in general, specialize
+//      `kCanSafelyConvertToByteSpan<T>` to be true for it. For example, Blink's
+//      `AtomicString` is not trivially copyable, but it is interned, so hashing
+//      and comparing the hashed values is safe.
+//   2. If the type is not safe in general but is safe for a particular use
+//      case, pass `base::allow_nonunique_obj` as the first arg to the byte span
+//      conversion functions. For example, floating-point values are not unique
+//      (among other reasons, because `+0` and `-0` are distinct but compare
+//      equal), but they are trivially copyable, so serializing them to disk and
+//      then deserializing is OK.
+//
+// Spans using `raw_ptr<T>` for internal storage
+// ---------------------------------------------
+// Provided via the type alias `raw_span<T[, N]>` (see base/memory/raw_span.h).
+// Use only for the uncommon case when a span should be a data member of an
+// object; for locals and params, use `span` (similarly to where you'd use a
+// `raw_ptr<T>` vs. a `T*`).
+//
+// Beware the risk of dangling pointers! The object owning the member span must
+// not access that span's data after the backing storage's lifetime ends. This
+// is the same risk as with all spans, but members tend to be longer-lived than
+// params/locals, and thus more prone to dangerous use.
+//
+// Differences from `std::span`
+// ----------------------------
+// https://eel.is/c++draft/views contains the latest C++ draft of `std::span`
+// and related utilities. Chromium aims to follow the draft except where noted
+// below; please report other divergences you find.
+//
+// Differences from [span.syn]:
+// - For convenience, provides `fixed_extent<N>` as an alias to
+//   `std::integral_constant<size_t, N>`, to aid in constructing fixed-extent
+//   spans from pointers.
+//
+// Differences from [span.overview]:
+// - `span` takes an optional third template argument that can be used to
+//   customize the underlying storage pointer type. This allows implementing
+//   `raw_span` as a specialization.
+//
+// Differences from [span.cons]:
+// - The constructor which takes an iterator and a count uses
+//   `StrictNumeric<size_type>` instead of `size_type` to prevent unsafe type
+//   conversions.
+// - Omits constructors from `std::array`, since separating these from the range
+//   constructor is only useful to mark them `noexcept`, and Chromium doesn't
+//   care about that.
+// - Fixed-extent constructor from range is only `explicit` for ranges whose
+//   extent cannot be statically computed. This matches the spirit of
+//   `std::span`, which handles these (so far as it is aware) via other
+//   overloads. Without this, we would not only need the dedicated constructors
+//   from `std::array`, we would also need dedicated constructors from
+//   fixed-extent `std::span`.
+// - Adds move construction and assignment. These can avoid refcount churn when
+//   the storage pointer is not `T*`. Not necessary for `std::span` since it
+//   does not allow customizing the storage pointer type.
+// - Provides implicit conversion in both directions between fixed-extent `span`
+//   and `std::span`. The general-purpose range constructors that would
+//   otherwise handle these cases are explicit for both fixed-extent span types.
+// - For convenience, provides `span::copy_from[_nonoverlapping]()` as wrappers
+//   around `std::ranges::copy()` that enforce equal-size spans.
+// - For convenience, provides `span::copy_prefix_from()` to allow copying into
+//   the beginning of the current span.
+//
+// Differences from [span.deduct]:
+// - The deduction guide from a range creates fixed-extent spans if the source
+//   extent is available at compile time.
+//
+// Differences from [span.sub]:
+// - As in [span.cons], `size_t` parameters are changed to
+//   `StrictNumeric<size_type>`.
+// - There are separate overloads for one-arg and two-arg forms of subspan,
+//   and the two-arg form does not accept dynamic_extent as a count.
+// - For convenience, provides `span::split_at()` to split a single span into
+//   two at a given offset.
+// - For convenience, provides `span::take_first[_elem]()` to remove the first
+//   portion of a dynamic-extent span and return it.
+//
+// Differences from [span.obs]:
+// - For convenience, provides `span::operator==()` to check whether two spans
+//   refer to equal-sized ranges of equal objects. This was intentionally
+//   removed from `std::span` because it makes the type non-Regular; see
+//   http://wg21.link/p1085 for details.
+// - Similarly, provides `span::operator<=>()`, which performs lexicographic
+//   comparison between spans.
+//
+// Differences from [span.elem]:
+// - Because Chromium does not use exceptions, `span::at()` behaves identically
+//   to `span::operator[]()` (i.e. it `CHECK()`s on out-of-range indexes rather
+//   than throwing).
+// - For convenience, provides `span::get_at()` to return a pointer (rather than
+//   reference) to an element. This is necessary if the backing memory may be
+//   uninitialized, since forming a reference would be UB.
+//
+// Differences from [span.objectrep]:
+// - For convenience, provides `span::to_fixed_extent<N>()` to attempt
+//   conversion to a fixed-extent span, and return null on failure.
+// - Because Chromium bans `std::byte`, `as_[writable_]bytes()` use `uint8_t`
+//   instead of `std::byte` as the returned element type.
+// - For convenience, provides `as_[writable_]chars()` and `as_string_view()`
+//   to convert to other "view of bytes"-like objects.
+// - For convenience, provides an `operator<<()` overload that accepts a span
+//   and prints a byte representation. Also provides a `PrintTo()` overload to
+//   convince GoogleTest to use this operator to print.
+// - For convenience, provides `[byte_]span_from_ref()` to convert single
+//   (non-range) objects to spans.
+// - For convenience, provides `[byte_]span_[with_nul_]from_cstring()` to
+//   convert `const char[]` literals to spans.
+// - For convenience, provides `[byte_]span_with_nul_from_cstring_view()` to
+//   convert `basic_cstring_view<T>` to spans, preserving the null terminator.
+// - For convenience, provides `as_[writable_]byte_span()` to convert
+//   spanifiable objects directly to byte spans.
+// - For safety, bans types which do not meet
+//   `std::has_unique_object_representations_v<>` from all byte span conversion
+//   functions by default. See more detailed comments above for workarounds.
 
 namespace pdfium {
 
+// [span.syn]: Constants
+inline constexpr size_t dynamic_extent = std::numeric_limits<size_t>::max();
+
+// [views.span]: class template `span<>`
+template <typename ElementType,
+          size_t Extent = dynamic_extent,
+          // Storage pointer customization. By default this is not a
+          // `raw_ptr<>`, since `span` is mostly used for stack variables. Use
+          // `raw_span` instead for class fields, which sets this to
+          // `raw_ptr<T>`.
+          typename InternalPtrType = ElementType*>
+class span;
+
+// Provides a compile-time fixed extent to the `count` argument of the span
+// constructor.
+//
+// (Not in `std::`.)
+template <size_t N>
+using fixed_extent = std::integral_constant<size_t, N>;
+
+}  // namespace pdfium
+
+// Mark `span` as satisfying the `view` and `borrowed_range` concepts. This
+// should be done before the definition of `span`, so that any inlined calls to
+// range functionality use the correct specializations.
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+inline constexpr bool std::ranges::enable_view<
+    pdfium::span<ElementType, Extent, InternalPtrType>> = true;
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+inline constexpr bool std::ranges::enable_borrowed_range<
+    pdfium::span<ElementType, Extent, InternalPtrType>> = true;
+
+namespace pdfium {
+
+// Allows global use of a type for conversion to byte spans.
+template <typename T>
+inline constexpr bool kCanSafelyConvertToByteSpan =
+    std::has_unique_object_representations_v<T>;
+template <typename T, typename U>
+inline constexpr bool kCanSafelyConvertToByteSpan<std::pair<T, U>> =
+    kCanSafelyConvertToByteSpan<std::remove_cvref_t<T>> &&
+    kCanSafelyConvertToByteSpan<std::remove_cvref_t<U>>;
+
 // Type tag to provide to byte span conversion functions to bypass
 // `std::has_unique_object_representations_v<>` check.
 struct allow_nonunique_obj_t {
@@ -29,16 +343,6 @@
 };
 inline constexpr allow_nonunique_obj_t allow_nonunique_obj{};
 
-constexpr size_t dynamic_extent = static_cast<size_t>(-1);
-
-template <typename T>
-using DefaultSpanInternalPtr = UNOWNED_PTR_EXCLUSION T*;
-
-template <typename T,
-          size_t Extent = dynamic_extent,
-          typename InternalPtr = DefaultSpanInternalPtr<T>>
-class span;
-
 namespace internal {
 
 template <typename T>
@@ -102,287 +406,1260 @@
                             ContainerHasConvertibleData<Container, T>::value &&
                             ContainerHasIntegralSize<Container>::value>::type;
 
+// Exposition-only concept from [span.syn]
+template <typename T>
+inline constexpr size_t MaybeStaticExt = dynamic_extent;
+template <typename T>
+  requires IntegralConstantLike<T>
+inline constexpr size_t MaybeStaticExt<T> = {T::value};
+
+template <typename From, typename To>
+concept LegalDataConversion = std::is_convertible_v<From (*)[], To (*)[]>;
+
+// Akin to `std::constructible_from<span, T>`, but meant to be used in a
+// type-deducing context where we don't know what args would be deduced;
+// `std::constructible_from` can't be directly used in such a case since the
+// type parameters must be fully-specified (e.g. `span<int>`), requiring us to
+// have that knowledge already.
+template <typename T>
+concept SpanConstructibleFrom = requires(T&& t) { span(std::forward<T>(t)); };
+
+// Returns the element type of `span(T)`.
+template <typename T>
+  requires SpanConstructibleFrom<T>
+using ElementTypeOfSpanConstructedFrom =
+    typename decltype(span(std::declval<T>()))::element_type;
+
+template <typename T, typename It>
+concept CompatibleIter =
+    std::contiguous_iterator<It> &&
+    LegalDataConversion<std::remove_reference_t<std::iter_reference_t<It>>, T>;
+
+// True when `T` is a `span`.
+template <typename T>
+inline constexpr bool kIsSpan = false;
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+inline constexpr bool kIsSpan<span<ElementType, Extent, InternalPtrType>> =
+    true;
+
+template <typename T, typename R>
+concept CompatibleRange =
+    std::ranges::contiguous_range<R> && std::ranges::sized_range<R> &&
+    (std::ranges::borrowed_range<R> || (std::is_const_v<T>)) &&
+    // `span`s should go through the copy constructor.
+    (!kIsSpan<std::remove_cvref_t<R>> &&
+     // Arrays should go through the array constructors.
+     (!std::is_array_v<std::remove_cvref_t<R>>)) &&
+    LegalDataConversion<
+        std::remove_reference_t<std::ranges::range_reference_t<R>>,
+        T>;
+
+// Whether source object extent `X` will work to create a span of fixed extent
+// `N`. This is not intended for use in dynamic-extent spans.
+template <size_t N, size_t X>
+concept FixedExtentConstructibleFromExtent = X == N || X == dynamic_extent;
+
+// Computes a fixed extent if possible from a source container type `T`.
+template <typename T>
+inline constexpr size_t kComputedExtentImpl = dynamic_extent;
+template <typename T>
+  requires requires { std::tuple_size<T>(); }
+inline constexpr size_t kComputedExtentImpl<T> = std::tuple_size_v<T>;
+template <typename T, size_t N>
+inline constexpr size_t kComputedExtentImpl<T[N]> = N;
+template <typename T, size_t N>
+inline constexpr size_t kComputedExtentImpl<std::span<T, N>> = N;
+template <typename T, size_t N, typename InternalPtrType>
+inline constexpr size_t kComputedExtentImpl<span<T, N, InternalPtrType>> = N;
+template <typename T>
+inline constexpr size_t kComputedExtent =
+    kComputedExtentImpl<std::remove_cvref_t<T>>;
+
+template <typename T>
+concept CanSafelyConvertToByteSpan =
+    kCanSafelyConvertToByteSpan<std::remove_cvref_t<T>>;
+
+template <typename T>
+concept ByteSpanConstructibleFrom =
+    SpanConstructibleFrom<T> &&
+    CanSafelyConvertToByteSpan<ElementTypeOfSpanConstructedFrom<T>>;
+
+// Allows one-off use of a type that wouldn't normally convert to a byte span.
+template <typename T>
+concept CanSafelyConvertNonUniqueToByteSpan =
+    // Non-trivially-copyable elements usually aren't safe even to serialize;
+    // when they are that's normally unconditionally true and can be handled
+    // using `kCanSafelyConvertToByteSpan`.
+    std::is_trivially_copyable_v<T> &&
+    // If this fails, `allow_nonunique_obj` wasn't necessary.
+    !std::has_unique_object_representations_v<T>;
+
+template <typename T>
+concept ByteSpanConstructibleFromNonUnique =
+    SpanConstructibleFrom<T> &&
+    CanSafelyConvertNonUniqueToByteSpan<ElementTypeOfSpanConstructedFrom<T>>;
+
+template <typename ByteType,
+          typename ElementType,
+          size_t Extent,
+          typename InternalPtrType>
+  requires((std::same_as<std::remove_const_t<ByteType>, char> ||
+            std::same_as<std::remove_const_t<ByteType>, unsigned char>) &&
+           (std::is_const_v<ByteType> || !std::is_const_v<ElementType>))
+constexpr auto as_byte_span(
+    span<ElementType, Extent, InternalPtrType> s) noexcept {
+  constexpr size_t kByteExtent =
+      Extent == dynamic_extent ? dynamic_extent : sizeof(ElementType) * Extent;
+  // SAFETY: `s.data()` points to at least `s.size_bytes()` bytes' worth of
+  // valid elements, so the size computed below must only contain valid
+  // elements. Since `ByteType` is an alias to a character type, it has a size
+  // of 1 byte, the resulting pointer has no alignment concerns, and it is not
+  // UB to access memory contents inside the allocation through it.
+  return UNSAFE_BUFFERS(span<ByteType, kByteExtent>(
+      reinterpret_cast<ByteType*>(s.data()), s.size_bytes()));
+}
+
 }  // namespace internal
 
-// A span is a value type that represents an array of elements of type T. Since
-// it only consists of a pointer to memory with an associated size, it is very
-// light-weight. It is cheap to construct, copy, move and use spans, so that
-// users are encouraged to use it as a pass-by-value parameter. A span does not
-// own the underlying memory, so care must be taken to ensure that a span does
-// not outlive the backing store.
-//
-// span is somewhat analogous to StringPiece, but with arbitrary element types,
-// allowing mutation if T is non-const.
-//
-// span is implicitly convertible from C++ arrays, as well as most [1]
-// container-like types that provide a data() and size() method (such as
-// std::vector<T>). A mutable span<T> can also be implicitly converted to an
-// immutable span<const T>.
-//
-// Consider using a span for functions that take a data pointer and size
-// parameter: it allows the function to still act on an array-like type, while
-// allowing the caller code to be a bit more concise.
-//
-// For read-only data access pass a span<const T>: the caller can supply either
-// a span<const T> or a span<T>, while the callee will have a read-only view.
-// For read-write access a mutable span<T> is required.
-//
-// Without span:
-//   Read-Only:
-//     // std::string HexEncode(const uint8_t* data, size_t size);
-//     std::vector<uint8_t> data_buffer = GenerateData();
-//     std::string r = HexEncode(data_buffer.data(), data_buffer.size());
-//
-//  Mutable:
-//     // ssize_t SafeSNPrintf(char* buf, size_t N, const char* fmt, Args...);
-//     char str_buffer[100];
-//     SafeSNPrintf(str_buffer, sizeof(str_buffer), "Pi ~= %lf", 3.14);
-//
-// With span:
-//   Read-Only:
-//     // std::string HexEncode(base::span<const uint8_t> data);
-//     std::vector<uint8_t> data_buffer = GenerateData();
-//     std::string r = HexEncode(data_buffer);
-//
-//  Mutable:
-//     // ssize_t SafeSNPrintf(base::span<char>, const char* fmt, Args...);
-//     char str_buffer[100];
-//     SafeSNPrintf(str_buffer, "Pi ~= %lf", 3.14);
-//
-// Spans with "const" and pointers
-// -------------------------------
-//
-// Const and pointers can get confusing. Here are vectors of pointers and their
-// corresponding spans (you can always make the span "more const" too):
-//
-//   const std::vector<int*>        =>  base::span<int* const>
-//   std::vector<const int*>        =>  base::span<const int*>
-//   const std::vector<const int*>  =>  base::span<const int* const>
-//
-// Differences from the working group proposal
-// -------------------------------------------
-//
-// https://wg21.link/P0122 is the latest working group proposal, Chromium
-// currently implements R6. The biggest difference is span does not support a
-// static extent template parameter. Other differences are documented in
-// subsections below.
-//
-// Differences in constants and types:
-// - no element_type type alias
-// - no index_type type alias
-// - no different_type type alias
-// - no extent constant
-//
-// Differences from [span.cons]:
-// - no constructor from a pointer range
-//
-// Differences from [span.sub]:
-// - using size_t instead of ptrdiff_t for indexing
-//
-// Differences from [span.obs]:
-// - using size_t instead of ptrdiff_t to represent size()
-//
-// Differences from [span.elem]:
-// - no operator ()()
-// - using size_t instead of ptrdiff_t for indexing
-//
-// Additions beyond the C++ standard draft
-// - as_chars() function.
-// - as_writable_chars() function.
-// - as_byte_span() function.
-// - as_writable_byte_span() function.
-// - span_from_ref() function.
-// - byte_span_from_ref() function.
-
-// [span], class template span
-template <typename T, size_t Extent, typename InternalPtr>
-class TRIVIAL_ABI GSL_POINTER span {
+// [span]: class `span` (non-dynamic `Extent`s)
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+class GSL_POINTER span {
  public:
-  using value_type = typename std::remove_cv<T>::type;
-  using pointer = T*;
-  using reference = T&;
-  using iterator = T*;
-  using const_iterator = const T*;
+  using element_type = ElementType;
+  using value_type = std::remove_cv_t<element_type>;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+  using pointer = element_type*;
+  using const_pointer = const element_type*;
+  using reference = element_type&;
+  using const_reference = const element_type&;
+  using iterator = element_type*;
+  using const_iterator = const element_type*;
   using reverse_iterator = std::reverse_iterator<iterator>;
+  // TODO(C++23): When `std::const_iterator<>` is available, switch to
+  // `std::const_iterator<reverse_iterator>` as the standard specifies.
   using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+  static constexpr size_type extent = Extent;
 
-  // [span.cons], span constructors, copy, assignment, and destructor
-  constexpr span() noexcept = default;
+  // [span.cons]: Constructors, copy, and assignment
+  // Default constructor.
+  constexpr span() noexcept
+    requires(extent == 0)
+  = default;
 
-  UNSAFE_BUFFER_USAGE constexpr span(T* data, size_t size) noexcept
-      : data_(data), size_(size) {
-    DCHECK(data_ || size_ == 0);
+  // Iterator + count.
+  template <typename It>
+    requires(internal::CompatibleIter<element_type, It>)
+  // SAFETY: `first` must point to the first of at least `count` contiguous
+  // valid elements, or the span will allow access to invalid elements,
+  // resulting in UB.
+  UNSAFE_BUFFER_USAGE constexpr explicit span(It first,
+                                              StrictNumeric<size_type> count)
+      : data_(first) {
+    CHECK(size_type{count} == extent);
+
+    // Non-zero `count` implies non-null `data_`. Use `SpanOrSize<T>` to
+    // represent a size that might not be accompanied by the actual data.
+    DCHECK(count == 0 || !!data_);
   }
 
-  // TODO(dcheng): Implement construction from a |begin| and |end| pointer.
-  template <size_t N>
-  constexpr span(T (&array)[N]) noexcept
-      // SAFETY: The type signature guarantees `array` contains `N` elements.
-      : UNSAFE_BUFFERS(span(array, N)) {
-    static_assert(Extent == dynamic_extent || Extent == N);
+  // Iterator + sentinel.
+  template <typename It, typename End>
+    requires(internal::CompatibleIter<element_type, It> &&
+             std::sized_sentinel_for<End, It> &&
+             !std::is_convertible_v<End, size_t>)
+  // SAFETY: `first` and `last` must be for the same allocation and all elements
+  // in the range [first, last) must be valid, or the span will allow access to
+  // invalid elements, resulting in UB.
+  UNSAFE_BUFFER_USAGE constexpr explicit span(It first, End last)
+      // SAFETY: The caller must guarantee that `first` and `last` point into
+      // the same allocation. In this case, the extent will be the number of
+      // elements between the iterators and thus a valid size for the pointer to
+      // the element at `first`.
+      //
+      // It is safe to check for underflow after subtraction because the
+      // underflow itself is not UB and `size_` is not converted to an invalid
+      // pointer (which would be UB) before the check.
+      : UNSAFE_BUFFERS(span(first, static_cast<size_type>(last - first))) {
+    // Verify `last - first` did not underflow.
+    CHECK(first <= last);
   }
 
-  template <size_t N>
-  constexpr span(std::array<T, N>& array) noexcept
-      // SAFETY: The type signature guarantees `array` contains `N` elements.
-      : UNSAFE_BUFFERS(span(array.data(), N)) {
-    static_assert(Extent == dynamic_extent || Extent == N);
-  }
+  // Array of size `extent`.
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(
+      std::type_identity_t<element_type> (&arr LIFETIME_BOUND)[extent]) noexcept
+      // SAFETY: The type signature guarantees `arr` contains `extent` elements.
+      : UNSAFE_BUFFERS(span(arr, extent)) {}
 
-  template <size_t N>
-  constexpr span(const std::array<std::remove_cv_t<T>, N>& array) noexcept
-      // SAFETY: The type signature guarantees `array` contains `N` elements.
-      : UNSAFE_BUFFERS(span(array.data(), N)) {
-    static_assert(Extent == dynamic_extent || Extent == N);
-  }
+  // Range.
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(internal::CompatibleRange<element_type, R> &&
+             internal::FixedExtentConstructibleFromExtent<extent, N>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr explicit(N != extent) span(R&& range LIFETIME_BOUND)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(internal::CompatibleRange<element_type, R> &&
+             internal::FixedExtentConstructibleFromExtent<extent, N> &&
+             std::ranges::borrowed_range<R>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr explicit span(R&& range)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
 
-  // Conversion from a container that provides |T* data()| and |integral_type
-  // size()|. Note that |data()| may not return nullptr for some empty
-  // containers, which can lead to container overflow errors when probing
-  // raw ptrs.
-#if defined(ADDRESS_SANITIZER) && defined(PDF_USE_PARTITION_ALLOC)
-  template <typename Container,
-            typename = internal::EnableIfSpanCompatibleContainer<Container, T>>
-  constexpr span(Container& container)
-      // SAFETY: `size()` is the number of elements that can be safely accessed
-      // at `data()`, if non-empty.
-      : UNSAFE_BUFFERS(span(container.size() ? container.data() : nullptr,
-                            container.size())) {}
-#else
-  template <typename Container,
-            typename = internal::EnableIfSpanCompatibleContainer<Container, T>>
-  constexpr span(Container& container)
-      // SAFETY: `size()` is the number of elements that can be safely accessed
-      // at `data()`.
-      : UNSAFE_BUFFERS(span(container.data(), container.size())) {}
-#endif
-
-  template <
-      typename Container,
-      typename = internal::EnableIfConstSpanCompatibleContainer<Container, T>>
-  span(const Container& container)
+  // Initializer list.
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr explicit span(std::initializer_list<value_type> il LIFETIME_BOUND)
+    requires(std::is_const_v<element_type>)
       // SAFETY: `size()` is exactly the number of elements in the initializer
       // list, so accessing that many will be safe.
-      : UNSAFE_BUFFERS(span(container.data(), container.size())) {}
+      : UNSAFE_BUFFERS(span(il.begin(), il.size())) {}
 
+  // Copy and move.
   constexpr span(const span& other) noexcept = default;
-
-  // Conversions from spans of compatible types: this allows a span<T> to be
-  // seamlessly used as a span<const T>, but not the other way around.
-  template <typename U,
-            size_t M,
-            typename R,
-            typename = internal::EnableIfLegalSpanConversion<U, T>>
-  constexpr span(const span<U, M, R>& other)
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires((OtherExtent == dynamic_extent || extent == OtherExtent) &&
+             internal::LegalDataConversion<OtherElementType, element_type>)
+  constexpr explicit(OtherExtent == dynamic_extent)
+      span(const span<OtherElementType, OtherExtent, OtherInternalPtrType>&
+               other) noexcept
       // SAFETY: `size()` is the number of elements that can be safely accessed
       // at `data()`.
       : UNSAFE_BUFFERS(span(other.data(), other.size())) {}
+  constexpr span(span&& other) noexcept = default;
 
-  span& operator=(const span& other) noexcept = default;
-  span& operator=(span&& other) noexcept = default;
+  // Copy and move assignment.
+  constexpr span& operator=(const span& other) noexcept = default;
+  constexpr span& operator=(span&& other) noexcept = default;
 
-  ~span() noexcept = default;
+  // Performs a deep copy of the elements referenced by `other` to those
+  // referenced by `this`. The spans must be the same size.
+  //
+  // If it's known the spans can not overlap, `copy_from_nonoverlapping()`
+  // provides an unsafe alternative that avoids intermediate copies.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from(span<const element_type, extent> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    if (std::is_constant_evaluated()) {
+      // Comparing pointers to different objects at compile time yields
+      // unspecified behavior, which would halt compilation. Instead,
+      // unconditionally use a separate buffer in the constexpr context. This
+      // would be inefficient at runtime, but that's irrelevant.
+      std::vector<element_type> vec(other.begin(), other.end());
+      std::ranges::copy(vec, begin());
+    } else {
+      // Using `<=` to compare pointers to different allocations is UB, but
+      // using `std::less_equal` is well-defined ([comparisons.general]).
+      if (std::less_equal{}(begin(), other.begin())) {
+        std::ranges::copy(other, begin());
+      } else {
+        std::ranges::copy_backward(other, end());
+      }
+    }
+  }
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(!std::is_const_v<element_type> &&
+             // Fixed-extent ranges should implicitly convert to use the
+             // overload above; if they don't, it's because the extent doesn't
+             // match. Rejecting this here improves the resulting errors.
+             N == dynamic_extent &&
+             std::convertible_to<R &&, span<const element_type>>)
+  constexpr void copy_from(R&& other) {
+    // Note: The constructor `CHECK()`s that a dynamic-extent `other` has the
+    // right size.
+    copy_from(span<const element_type, extent>(std::forward<R>(other)));
+  }
 
-  // [span.sub], span subviews
+  // Like `copy_from()`, but may be more performant; however, the caller must
+  // guarantee the spans do not overlap, or this will invoke UB.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from_nonoverlapping(
+      span<const element_type, extent> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    // Comparing pointers to different objects at compile time yields
+    // unspecified behavior, which would halt compilation. Instead implement in
+    // terms of the guaranteed-safe behavior; performance is irrelevant in the
+    // constexpr context.
+    if (std::is_constant_evaluated()) {
+      copy_from(other);
+      return;
+    }
+
+    // See comments in `copy_from()` re: use of templated comparison objects.
+    DCHECK(std::less_equal{}(end(), other.begin()) ||
+           std::greater_equal{}(begin(), other.end()));
+    std::ranges::copy(other, begin());
+  }
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(!std::is_const_v<element_type> && N == dynamic_extent &&
+             std::convertible_to<R &&, span<const element_type>>)
+  constexpr void copy_from_nonoverlapping(R&& other) {
+    // Note: The constructor `CHECK()`s that a dynamic-extent `other` has the
+    // right size.
+    copy_from_nonoverlapping(
+        span<const element_type, extent>(std::forward<R>(other)));
+  }
+
+  // Like `copy_from()`, but allows the source to be smaller than this span, and
+  // will only copy as far as the source size, leaving the remaining elements of
+  // this span unwritten.
+  //
+  // (Not in `std::`; allows caller code to elide repeated size information and
+  // makes it easier to preserve fixed-extent spans in the process.)
+  template <typename R, size_t N = internal::kComputedExtent<R>>
+    requires(!std::is_const_v<element_type> &&
+             (N <= extent || N == dynamic_extent) &&
+             std::convertible_to<R &&, span<const element_type>>)
+  constexpr void copy_prefix_from(R&& other) {
+    if constexpr (N == dynamic_extent) {
+      return first(other.size()).copy_from(other);
+    } else {
+      return first<N>().copy_from(other);
+    }
+  }
+
+  // Implicit conversion to fixed-extent `std::span<>`. (The fixed-extent
+  // `std::span` range constructor is explicit.)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  operator std::span<element_type, extent>() const {
+    return std::span<element_type, extent>(*this);
+  }
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  operator std::span<const element_type, extent>() const
+    requires(!std::is_const_v<element_type>)
+  {
+    return std::span<const element_type, extent>(*this);
+  }
+
+  // [span.sub]: Subviews
+  // First `count` elements.
   template <size_t Count>
-  span first() const {
-    // TODO(tsepez): The following check isn't yet good enough to replace
-    // the runtime check since we are still allowing unchecked conversions
-    // to arbitrary non-dynamic_extent spans.
-    static_assert(Extent == dynamic_extent || Count <= Extent);
-    return first(Count);
+  constexpr auto first() const
+    requires(Count <= extent)
+  {
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type, Count>(data(), Count));
   }
-  const span first(size_t count) const {
-    CHECK(count <= size_);
-    // SAFETY: CHECK() on line above.
-    return UNSAFE_BUFFERS(span(static_cast<T*>(data_), count));
+  constexpr auto first(StrictNumeric<size_type> count) const {
+    CHECK(size_type{count} <= extent);
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type>(data(), count));
   }
 
+  // Last `count` elements.
   template <size_t Count>
-  span last() const {
-    // TODO(tsepez): The following check isn't yet good enough to replace
-    // the runtime check since we are still allowing unchecked conversions
-    // to arbitrary non-dynamic_extent spans.
-    static_assert(Extent == dynamic_extent || Count <= Extent);
-    return last(Count);
-  }
-  const span last(size_t count) const {
-    CHECK(count <= size_);
+  constexpr auto last() const
+    requires(Count <= extent)
+  {
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
     return UNSAFE_BUFFERS(
-        span(static_cast<T*>(data_) + (size_ - count), count));
+        span<element_type, Count>(data() + (extent - Count), Count));
+  }
+  constexpr auto last(StrictNumeric<size_type> count) const {
+    CHECK(size_type{count} <= extent);
+    // SAFETY: `data()` points to at least `extent` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + (extent - size_type{count}), count));
   }
 
+  // `count` elements beginning at `offset`.
   template <size_t Offset, size_t Count = dynamic_extent>
-  span subspan() const {
-    // TODO(tsepez): The following check isn't yet good enough to replace
-    // the runtime check since we are still allowing unchecked conversions
-    // to arbitrary non-dynamic_extent spans.
-    static_assert(Extent == dynamic_extent || Count == dynamic_extent ||
-                  Offset + Count <= Extent);
-    return subspan(Offset, Count);
+  constexpr auto subspan() const
+    requires(Offset <= extent &&
+             (Count == dynamic_extent || Count <= extent - Offset))
+  {
+    if constexpr (Count == dynamic_extent) {
+      constexpr size_t kRemaining = extent - Offset;
+      // SAFETY: `data()` points to at least `extent` elements, so `Offset`
+      // specifies a valid element index or the past-the-end index, and
+      // `kRemaining` cannot index past-the-end elements.
+      return UNSAFE_BUFFERS(
+          span<element_type, kRemaining>(data() + Offset, kRemaining));
+    } else {
+      // SAFETY: `data()` points to at least `extent` elements, so `Offset`
+      // specifies a valid element index or the past-the-end index, and `Count`
+      // is no larger than the number of remaining valid elements.
+      return UNSAFE_BUFFERS(span<element_type, Count>(data() + Offset, Count));
+    }
   }
-  const span subspan(size_t pos, size_t count = dynamic_extent) const {
-    CHECK(pos <= size_);
-    CHECK(count == dynamic_extent || count <= size_ - pos);
-    // SAFETY: CHECK()s on lines above.
-    return UNSAFE_BUFFERS(span(static_cast<T*>(data_) + pos,
-                               count == dynamic_extent ? size_ - pos : count));
+  constexpr auto subspan(StrictNumeric<size_type> offset) const {
+    CHECK(size_type{offset} <= extent);
+    const size_type remaining = extent - size_type{offset};
+    // SAFETY: `data()` points to at least `extent` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and
+    // `remaining` cannot index past-the-end elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, remaining));
+  }
+  constexpr auto subspan(StrictNumeric<size_type> offset,
+                         StrictNumeric<size_type> count) const {
+    // PDFium does not allow dynamic_extent in two-arg subspan()
+    DCHECK(size_type{count} != dynamic_extent);
+    // Deliberately combine tests to minimize code size.
+    CHECK(size_type{offset} <= size() &&
+          size_type{count} <= size() - size_type{offset});
+    // SAFETY: `data()` points to at least `extent` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and `count` is
+    // no larger than the number of remaining valid elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, count));
   }
 
-  // [span.obs], span observers
-  constexpr size_t size() const noexcept { return size_; }
-  constexpr size_t size_bytes() const noexcept { return size() * sizeof(T); }
-  constexpr bool empty() const noexcept { return size_ == 0; }
-
-  // [span.elem], span element access
-  T& operator[](size_t index) const noexcept {
-    CHECK(index < size_);
-    return UNSAFE_BUFFERS(static_cast<T*>(data_)[index]);
+  // Splits a span a given offset, returning a pair of spans that cover the
+  // ranges strictly before the offset and starting at the offset, respectively.
+  //
+  // (Not in `std::span`; inspired by Rust's `slice::split_at()` and
+  // `split_at_mut()`.)
+  template <size_t Offset>
+    requires(Offset <= extent)
+  constexpr auto split_at() const {
+    return std::pair(first<Offset>(), subspan<Offset, extent - Offset>());
+  }
+  constexpr auto split_at(StrictNumeric<size_type> offset) const {
+    return std::pair(first(offset), subspan(offset));
   }
 
-  constexpr T& front() const noexcept {
-    CHECK(!empty());
-    return *data();
+  // [span.obs]: Observers
+  // Size.
+  constexpr size_type size() const noexcept { return extent; }
+  constexpr size_type size_bytes() const noexcept {
+    return extent * sizeof(element_type);
   }
 
-  constexpr T& back() const noexcept {
-    CHECK(!empty());
-    return UNSAFE_BUFFERS(*(data() + size() - 1));
+  // Empty.
+  [[nodiscard]] constexpr bool empty() const noexcept { return extent == 0; }
+
+  // Returns true if `lhs` and `rhs` are equal-sized and are per-element equal.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr bool operator==(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type, extent>(lhs),
+                              span<const element_type, extent>(rhs));
+  }
+  friend constexpr bool operator==(span lhs,
+                                   span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type, extent>(lhs), rhs);
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires((OtherExtent == dynamic_extent || extent == OtherExtent) &&
+             std::equality_comparable_with<const element_type,
+                                           const OtherElementType>)
+  friend constexpr bool operator==(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    return std::ranges::equal(span<const element_type, extent>(lhs),
+                              span<const OtherElementType, OtherExtent>(rhs));
   }
 
-  constexpr T* data() const noexcept { return static_cast<T*>(data_); }
+  // Performs lexicographical comparison of `lhs` and `rhs`.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr auto operator<=>(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const element_type>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
+  }
+  friend constexpr auto operator<=>(span lhs,
+                                    span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    return span<const element_type>(lhs) <=> rhs;
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires((OtherExtent == dynamic_extent || extent == OtherExtent) &&
+             std::three_way_comparable_with<const element_type,
+                                            const OtherElementType>)
+  friend constexpr auto operator<=>(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const OtherElementType, OtherExtent>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
+  }
 
-  // [span.iter], span iterator support
-  constexpr iterator begin() const noexcept { return static_cast<T*>(data_); }
+  // [span.elem]: Element access
+  // Reference to specific element.
+  // When `idx` is outside the span, the underlying call will `CHECK()`.
+  //
+  // Intentionally does not take `StrictNumeric<size_t>`, unlike all other APIs.
+  // There are far too many false positives on integer literals (e.g. `s[0]`),
+  // and while `ENABLE_IF_ATTR` can be used to work around those for Clang, that
+  // would leave the gcc build broken. The consequence of not upgrading this is
+  // that some errors will only be detected at runtime instead of compile time.
+  constexpr reference operator[](size_type idx) const
+    requires(extent > 0)
+  {
+    return at(idx);
+  }
+  // When `idx` is outside the span, the underlying call will `CHECK()`.
+  constexpr reference at(StrictNumeric<size_type> idx) const
+    requires(extent > 0)
+  {
+    return *get_at(idx);
+  }
+
+  // Returns a pointer to an element in the span.
+  //
+  // (Not in `std::`; necessary when underlying memory is not yet initialized.)
+  constexpr pointer get_at(StrictNumeric<size_type> idx) const
+    requires(extent > 0)
+  {
+    CHECK(size_type{idx} < extent);
+    // SAFETY: `data()` points to at least `extent` elements, so `idx` must be
+    // the index of a valid element.
+    return UNSAFE_BUFFERS(data() + size_type{idx});
+  }
+
+  // Reference to first/last elements.
+  // When `empty()`, the underlying call will `CHECK()`.
+  constexpr reference front() const
+    requires(extent > 0)
+  {
+    return operator[](0);
+  }
+  // When `empty()`, the underlying call will `CHECK()`.
+  constexpr reference back() const
+    requires(extent > 0)
+  {
+    return operator[](size() - 1);
+  }
+
+  // Underlying memory.
+  constexpr pointer data() const noexcept { return data_; }
+
+  // [span.iter]: Iterator support
+  // Forward iterators.
+  constexpr iterator begin() const noexcept { return iterator(data()); }
+  constexpr const_iterator cbegin() const noexcept {
+    return const_iterator(begin());
+  }
   constexpr iterator end() const noexcept {
-    return UNSAFE_BUFFERS(begin() + size_);
+    // SAFETY: `data()` points to at least `extent` elements, so `data() +
+    // extent` is no larger than just past the end of the corresponding
+    // allocation, which is a legal pointer to construct and compare to (though
+    // not dereference).
+    return UNSAFE_BUFFERS(iterator(data() + extent));
+  }
+  constexpr const_iterator cend() const noexcept {
+    return const_iterator(end());
   }
 
-  constexpr const_iterator cbegin() const noexcept { return begin(); }
-  constexpr const_iterator cend() const noexcept { return end(); }
-
+  // Reverse iterators.
   constexpr reverse_iterator rbegin() const noexcept {
     return reverse_iterator(end());
   }
+  constexpr const_reverse_iterator crbegin() const noexcept {
+    return const_iterator(rbegin());
+  }
   constexpr reverse_iterator rend() const noexcept {
     return reverse_iterator(begin());
   }
-
-  constexpr const_reverse_iterator crbegin() const noexcept {
-    return const_reverse_iterator(cend());
-  }
   constexpr const_reverse_iterator crend() const noexcept {
-    return const_reverse_iterator(cbegin());
+    return const_iterator(rend());
   }
 
  private:
-  template <typename U>
-  friend constexpr span<U> make_span(U* data, size_t size) noexcept;
+  InternalPtrType data_ = nullptr;
+};
 
-  InternalPtr data_ = nullptr;
+// [span]: class <span> (dynamic `Extent`)
+template <typename ElementType, typename InternalPtrType>
+class GSL_POINTER span<ElementType, dynamic_extent, InternalPtrType> {
+ public:
+  using element_type = ElementType;
+  using value_type = std::remove_cv_t<element_type>;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+  using pointer = element_type*;
+  using const_pointer = const element_type*;
+  using reference = element_type&;
+  using const_reference = const element_type&;
+  using iterator = element_type*;
+  using const_iterator = const element_type*;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+  // TODO(C++23): When `std::const_iterator<>` is available, switch to
+  // `std::const_iterator<reverse_iterator>` as the standard specifies.
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+  static constexpr size_type extent = dynamic_extent;
+
+  // [span.cons]: Constructors, copy, and assignment
+  // Default constructor.
+  constexpr span() noexcept = default;
+
+  // Iterator + count.
+  template <typename It>
+    requires(internal::CompatibleIter<element_type, It>)
+  // SAFETY: `first` must point to the first of at least `count` contiguous
+  // valid elements, or the span will allow access to invalid elements,
+  // resulting in UB.
+  UNSAFE_BUFFER_USAGE constexpr span(It first, StrictNumeric<size_type> count)
+      : data_(first), size_(count) {
+    // Non-zero `count` implies non-null `data_`. Use `SpanOrSize<T>` to
+    // represent a size that might not be accompanied by the actual data.
+    DCHECK(count == 0 || !!data_);
+  }
+
+  // Iterator + sentinel.
+  template <typename It, typename End>
+    requires(internal::CompatibleIter<element_type, It> &&
+             std::sized_sentinel_for<End, It> &&
+             !std::is_convertible_v<End, size_t>)
+  // SAFETY: `first` and `last` must be for the same allocation and all elements
+  // in the range [first, last) must be valid, or the span will allow access to
+  // invalid elements, resulting in UB.
+  UNSAFE_BUFFER_USAGE constexpr span(It first, End last)
+      // SAFETY: The caller must guarantee that `first` and `last` point into
+      // the same allocation. In this case, `size_` will be the number of
+      // elements between the iterators and thus a valid size for the pointer to
+      // the element at `first`.
+      //
+      // It is safe to check for underflow after subtraction because the
+      // underflow itself is not UB and `size_` is not converted to an invalid
+      // pointer (which would be UB) before the check.
+      : UNSAFE_BUFFERS(span(first, static_cast<size_type>(last - first))) {
+    // Verify `last - first` did not underflow.
+    CHECK(first <= last);
+  }
+
+  // Array of size N.
+  template <size_t N>
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(
+      std::type_identity_t<element_type> (&arr LIFETIME_BOUND)[N]) noexcept
+      // SAFETY: The type signature guarantees `arr` contains `N` elements.
+      : UNSAFE_BUFFERS(span(arr, N)) {}
+
+  // Range.
+  template <typename R>
+    requires(internal::CompatibleRange<element_type, R>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(R&& range LIFETIME_BOUND)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
+  template <typename R>
+    requires(internal::CompatibleRange<element_type, R> &&
+             std::ranges::borrowed_range<R>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(R&& range)
+      // SAFETY: `std::ranges::size()` returns the number of elements
+      // `std::ranges::data()` will point to, so accessing those elements will
+      // be safe.
+      : UNSAFE_BUFFERS(
+            span(std::ranges::data(range), std::ranges::size(range))) {}
+
+  // Initializer list.
+  constexpr span(std::initializer_list<value_type> il LIFETIME_BOUND)
+    requires(std::is_const_v<element_type>)
+      // SAFETY: `size()` is exactly the number of elements in the initializer
+      // list, so accessing that many will be safe.
+      : UNSAFE_BUFFERS(span(il.begin(), il.size())) {}
+
+  // Copy and move.
+  constexpr span(const span& other) noexcept = default;
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires(internal::LegalDataConversion<OtherElementType, element_type>)
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  constexpr span(
+      const span<OtherElementType, OtherExtent, OtherInternalPtrType>&
+          other) noexcept
+      : data_(other.data()), size_(other.size()) {}
+  constexpr span(span&& other) noexcept = default;
+
+  // Copy and move assignment.
+  constexpr span& operator=(const span& other) noexcept = default;
+  constexpr span& operator=(span&& other) noexcept = default;
+
+  // Performs a deep copy of the elements referenced by `other` to those
+  // referenced by `this`. The spans must be the same size.
+  //
+  // If it's known the spans can not overlap, `copy_from_nonoverlapping()`
+  // provides an unsafe alternative that avoids intermediate copies.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from(span<const element_type> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    CHECK(size() == other.size());
+    if (std::is_constant_evaluated()) {
+      // Comparing pointers to different objects at compile time yields
+      // unspecified behavior, which would halt compilation. Instead,
+      // unconditionally use a separate buffer in the constexpr context. This
+      // would be inefficient at runtime, but that's irrelevant.
+      std::vector<element_type> vec(other.begin(), other.end());
+      std::ranges::copy(vec, begin());
+    } else {
+      // Using `<=` to compare pointers to different allocations is UB, but
+      // using `std::less_equal` is well-defined ([comparisons.general]).
+      if (std::less_equal{}(begin(), other.begin())) {
+        std::ranges::copy(other, begin());
+      } else {
+        std::ranges::copy_backward(other, end());
+      }
+    }
+  }
+
+  // Like `copy_from()`, but may be more performant; however, the caller must
+  // guarantee the spans do not overlap, or this will invoke UB.
+  //
+  // (Not in `std::`; inspired by Rust's `slice::copy_from_slice()`.)
+  constexpr void copy_from_nonoverlapping(span<const element_type> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    // Comparing pointers to different objects at compile time yields
+    // unspecified behavior, which would halt compilation. Instead implement in
+    // terms of the guaranteed-safe behavior; performance is irrelevant in the
+    // constexpr context.
+    if (std::is_constant_evaluated()) {
+      copy_from(other);
+      return;
+    }
+
+    CHECK(size() == other.size());
+    // See comments in `copy_from()` re: use of templated comparison objects.
+    DCHECK(std::less_equal{}(end(), other.begin()) ||
+           std::greater_equal{}(begin(), other.end()));
+    std::ranges::copy(other, begin());
+  }
+
+  // Like `copy_from()`, but allows the source to be smaller than this span, and
+  // will only copy as far as the source size, leaving the remaining elements of
+  // this span unwritten.
+  //
+  // (Not in `std::`; allows caller code to elide repeated size information and
+  // makes it easier to preserve fixed-extent spans in the process.)
+  constexpr void copy_prefix_from(span<const element_type> other)
+    requires(!std::is_const_v<element_type>)
+  {
+    return first(other.size()).copy_from(other);
+  }
+
+  // [span.sub]: Subviews
+  // First `count` elements.
+  template <size_t Count>
+  constexpr auto first() const {
+    CHECK(Count <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type, Count>(data(), Count));
+  }
+  constexpr auto first(StrictNumeric<size_t> count) const {
+    CHECK(size_type{count} <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(span<element_type>(data(), count));
+  }
+
+  // Last `count` elements.
+  template <size_t Count>
+  constexpr auto last() const {
+    CHECK(Count <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(
+        span<element_type, Count>(data() + (size() - Count), Count));
+  }
+  constexpr auto last(StrictNumeric<size_type> count) const {
+    CHECK(size_type{count} <= size());
+    // SAFETY: `data()` points to at least `size()` elements, so the new data
+    // scope is a strict subset of the old.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + (size() - size_type{count}), count));
+  }
+
+  // `count` elements beginning at `offset`.
+  template <size_t Offset, size_t Count = dynamic_extent>
+  constexpr auto subspan() const {
+    CHECK(Offset <= size());
+    const size_type remaining = size() - Offset;
+    if constexpr (Count == dynamic_extent) {
+      // SAFETY: `data()` points to at least `size()` elements, so `Offset`
+      // specifies a valid element index or the past-the-end index, and
+      // `remaining` cannot index past-the-end elements.
+      return UNSAFE_BUFFERS(
+          span<element_type, Count>(data() + Offset, remaining));
+    }
+    CHECK(Count <= remaining);
+    // SAFETY: `data()` points to at least `size()` elements, so `Offset`
+    // specifies a valid element index or the past-the-end index, and `Count` is
+    // no larger than the number of remaining valid elements.
+    return UNSAFE_BUFFERS(span<element_type, Count>(data() + Offset, Count));
+  }
+  constexpr auto subspan(StrictNumeric<size_type> offset) const {
+    CHECK(size_type{offset} <= size());
+    const size_type remaining = size() - size_type{offset};
+    // SAFETY: `data()` points to at least `size()` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and
+    // `remaining` cannot index past-the-end elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, remaining));
+  }
+  constexpr auto subspan(StrictNumeric<size_type> offset,
+                         StrictNumeric<size_type> count) const {
+    // PDFium does not allow dynamic_extent in two-arg subspan()
+    DCHECK(size_type{count} != dynamic_extent);
+    // Deliberately combine tests to minimize code size.
+    CHECK(size_type{offset} <= size() &&
+          size_type{count} <= size() - size_type{offset});
+    // SAFETY: `data()` points to at least `size()` elements, so `offset`
+    // specifies a valid element index or the past-the-end index, and `count` is
+    // no larger than the number of remaining valid elements.
+    return UNSAFE_BUFFERS(
+        span<element_type>(data() + size_type{offset}, count));
+  }
+
+  // Splits a span a given offset, returning a pair of spans that cover the
+  // ranges strictly before the offset and starting at the offset, respectively.
+  //
+  // (Not in `std::span`; inspired by Rust's `slice::split_at()` and
+  // `split_at_mut()`.)
+  template <size_t Offset>
+  constexpr auto split_at() const {
+    CHECK(Offset <= size());
+    return std::pair(first<Offset>(), subspan<Offset>());
+  }
+  constexpr auto split_at(StrictNumeric<size_type> offset) const {
+    return std::pair(first(offset), subspan(offset));
+  }
+
+  // Returns a span of the first N elements, removing them.
+  // When `Offset` is outside the span, the underlying call will `CHECK()`. For
+  // a non-fatal alternative, consider `SpanReader`.
+  //
+  // (Not in `std::span`; convenient for processing a stream of disparate
+  // objects or looping over elements.)
+  template <size_t Offset>
+  constexpr auto take_first() {
+    const auto [first, rest] = split_at<Offset>();
+    *this = rest;
+    return first;
+  }
+  // When `offset` is outside the span, the underlying call will `CHECK()`.
+  constexpr auto take_first(StrictNumeric<size_type> offset) {
+    const auto [first, rest] = split_at(offset);
+    *this = rest;
+    return first;
+  }
+
+  // Returns the first element, removing it.
+  // When `empty()`, the underlying call will `CHECK()`. For a non-fatal
+  // alternative, consider `SpanReader`.
+  //
+  // (Not in `std::span`; convenient for processing a stream of disparate
+  // objects or looping over elements.)
+  constexpr auto take_first_elem() { return take_first<1>().front(); }
+
+  // [span.obs]: Observers
+  // Size.
+  constexpr size_type size() const noexcept { return size_; }
+  constexpr size_type size_bytes() const noexcept {
+    return size() * sizeof(element_type);
+  }
+
+  // Empty.
+  [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; }
+
+  // Returns true if `lhs` and `rhs` are equal-sized and are per-element equal.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr bool operator==(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type>(lhs),
+                              span<const element_type>(rhs));
+  }
+  friend constexpr bool operator==(span lhs,
+                                   span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::equality_comparable<const element_type>)
+  {
+    return std::ranges::equal(span<const element_type>(lhs), rhs);
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires(std::equality_comparable_with<const element_type,
+                                           const OtherElementType>)
+  friend constexpr bool operator==(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    return std::ranges::equal(span<const element_type>(lhs),
+                              span<const OtherElementType, OtherExtent>(rhs));
+  }
+
+  // Performs lexicographical comparison of `lhs` and `rhs`.
+  //
+  // (Not in `std::span`; improves both ergonomics and safety.)
+  //
+  // NOTE: Using non-members here intentionally allows comparing types that
+  // implicitly convert to `span`.
+  friend constexpr auto operator<=>(span lhs, span rhs)
+    requires(std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const element_type>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
+  }
+  friend constexpr auto operator<=>(span lhs,
+                                    span<const element_type, extent> rhs)
+    requires(!std::is_const_v<element_type> &&
+             std::three_way_comparable<const element_type>)
+  {
+    return span<const element_type>(lhs) <=> rhs;
+  }
+  template <typename OtherElementType,
+            size_t OtherExtent,
+            typename OtherInternalPtrType>
+    requires(std::three_way_comparable_with<const element_type,
+                                            const OtherElementType>)
+  friend constexpr auto operator<=>(
+      span lhs,
+      span<OtherElementType, OtherExtent, OtherInternalPtrType> rhs) {
+    const auto const_lhs = span<const element_type>(lhs);
+    const auto const_rhs = span<const OtherElementType, OtherExtent>(rhs);
+    return std::lexicographical_compare_three_way(
+        const_lhs.begin(), const_lhs.end(), const_rhs.begin(), const_rhs.end());
+  }
+
+  // [span.elem]: Element access
+  // Reference to a specific element.
+  // When `idx` is outside the span, the underlying call will `CHECK()`.
+  //
+  // Intentionally does not take `StrictNumeric<size_type>`; see comments on
+  // fixed-extent version for rationale.
+  constexpr reference operator[](size_type idx) const { return at(idx); }
+
+  // When `idx` is outside the span, the underlying call will `CHECK()`.
+  constexpr reference at(StrictNumeric<size_type> idx) const {
+    return *get_at(idx);
+  }
+
+  // Returns a pointer to an element in the span.
+  //
+  // (Not in `std::`; necessary when underlying memory is not yet initialized.)
+  constexpr pointer get_at(StrictNumeric<size_type> idx) const {
+    CHECK(size_type{idx} < size());
+    // SAFETY: `data()` points to at least `size()` elements, so `idx` must be
+    // the index of a valid element.
+    return UNSAFE_BUFFERS(data() + size_type{idx});
+  }
+
+  // Reference to first/last elements.
+  // When `empty()`, the underlying call will `CHECK()`.
+  constexpr reference front() const { return operator[](0); }
+  // When `empty()`, the underlying call will `CHECK()`.
+  constexpr reference back() const { return operator[](size() - 1); }
+
+  // Underlying memory.
+  constexpr pointer data() const noexcept { return data_; }
+
+  // [span.iter]: Iterator support
+  // Forward iterators.
+  constexpr iterator begin() const noexcept { return iterator(data()); }
+  constexpr const_iterator cbegin() const noexcept {
+    return const_iterator(begin());
+  }
+  constexpr iterator end() const noexcept {
+    // SAFETY: `data()` points to at least `size()` elements, so `data() +
+    // size()` is no larger than just past the end of the corresponding
+    // allocation, which is a legal pointer to construct and compare to (though
+    // not dereference).
+    return UNSAFE_BUFFERS(data() + size());
+  }
+  constexpr const_iterator cend() const noexcept {
+    return const_iterator(end());
+  }
+
+  // Reverse iterators.
+  constexpr reverse_iterator rbegin() const noexcept {
+    return reverse_iterator(end());
+  }
+  constexpr const_reverse_iterator crbegin() const noexcept {
+    return const_iterator(rbegin());
+  }
+  constexpr reverse_iterator rend() const noexcept {
+    return reverse_iterator(begin());
+  }
+  constexpr const_reverse_iterator crend() const noexcept {
+    return const_iterator(rend());
+  }
+
+  // [span.objectrep]: Views of object representation
+  // Converts a dynamic-extent span to a fixed-extent span. Returns a
+  // `span<element_type, Extent>` iff `size() == Extent`; otherwise, returns
+  // `std::nullopt`.
+  //
+  // (Not in `std::`; provides a conditional conversion path.)
+  template <size_t Extent>
+  constexpr std::optional<span<element_type, Extent>> to_fixed_extent() const {
+    return size() == Extent ? std::optional(span<element_type, Extent>(*this))
+                            : std::nullopt;
+  }
+
+ private:
+  InternalPtrType data_ = nullptr;
   size_t size_ = 0;
 };
 
+// [span.deduct]: Deduction guides
+template <typename It, typename EndOrSize>
+  requires(std::contiguous_iterator<It>)
+span(It, EndOrSize) -> span<std::remove_reference_t<std::iter_reference_t<It>>,
+                            internal::MaybeStaticExt<EndOrSize>>;
+
+template <typename T, size_t N>
+span(T (&)[N]) -> span<T, N>;
+
+template <typename R>
+  requires(std::ranges::contiguous_range<R>)
+span(R&&) -> span<std::remove_reference_t<std::ranges::range_reference_t<R>>,
+                  internal::kComputedExtent<R>>;
+
+// [span.objectrep]: Views of object representation
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType>)
+constexpr auto as_bytes(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const uint8_t>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType>)
+constexpr auto as_bytes(allow_nonunique_obj_t,
+                        span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const uint8_t>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_bytes(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<uint8_t>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_bytes(allow_nonunique_obj_t,
+                                 span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<uint8_t>(s);
+}
+
+// Like `as_[writable_]bytes()`, but uses `[const] char` rather than `[const]
+// uint8_t`.
+//
+// (Not in `std::`; eases span adoption in Chromium, which uses `char` in many
+// cases that rightfully should be `uint8_t`.)
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType>)
+constexpr auto as_chars(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const char>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType>)
+constexpr auto as_chars(allow_nonunique_obj_t,
+                        span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<const char>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_chars(span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<char>(s);
+}
+template <typename ElementType, size_t Extent, typename InternalPtrType>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_chars(allow_nonunique_obj_t,
+                                 span<ElementType, Extent, InternalPtrType> s) {
+  return internal::as_byte_span<char>(s);
+}
+
+// Converts a `T&` to a `span<T, 1>`.
+//
+// (Not in `std::`; inspired by Rust's `slice::from_ref()`.)
+template <typename T>
+constexpr auto span_from_ref(const T& t LIFETIME_BOUND) {
+  // SAFETY: It's safe to read the memory at `t`'s address as long as the
+  // provided reference is valid.
+  return UNSAFE_BUFFERS(span<const T, 1>(std::addressof(t), 1u));
+}
+template <typename T>
+constexpr auto span_from_ref(T& t LIFETIME_BOUND) {
+  // SAFETY: It's safe to read the memory at `t`'s address as long as the
+  // provided reference is valid.
+  return UNSAFE_BUFFERS(span<T, 1>(std::addressof(t), 1u));
+}
+
+// Converts a `T&` to a `span<[const] uint8_t, sizeof(T)>`.
+//
+// (Not in `std::`.)
+template <typename T>
+  requires(internal::CanSafelyConvertToByteSpan<T>)
+constexpr auto byte_span_from_ref(const T& t LIFETIME_BOUND) {
+  return as_bytes(span_from_ref(t));
+}
+template <typename T>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<T>)
+constexpr auto byte_span_from_ref(allow_nonunique_obj_t,
+                                  const T& t LIFETIME_BOUND) {
+  return as_bytes(allow_nonunique_obj, span_from_ref(t));
+}
+template <typename T>
+  requires(internal::CanSafelyConvertToByteSpan<T>)
+constexpr auto byte_span_from_ref(T& t LIFETIME_BOUND) {
+  return as_writable_bytes(span_from_ref(t));
+}
+template <typename T>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<T>)
+constexpr auto byte_span_from_ref(allow_nonunique_obj_t, T& t LIFETIME_BOUND) {
+  return as_writable_bytes(allow_nonunique_obj, span_from_ref(t));
+}
+
+// Converts a `const CharT[]` literal to a `span<const CharT>`, omitting the
+// trailing '\0' (internal '\0's, if any, are preserved). For comparison:
+//   `span("hi")`                  => `span<const char, 3>({'h', 'i', '\0'})`
+//   `span(std::string_view("hi")) => `span<const char>({'h', 'i'})`
+//   `span_from_cstring("hi")`     => `span<const char, 2>({'h', 'i'})`
+//
+// (Not in `std::`; useful when reading and writing character subsequences in
+// larger files.)
+template <typename CharT, size_t Extent>
+constexpr auto span_from_cstring(const CharT (&str LIFETIME_BOUND)[Extent])
+    ENABLE_IF_ATTR(str[Extent - 1u] == CharT{0},
+                   "requires string literal as input") {
+  return span(str).template first<Extent - 1>();
+}
+
+// Converts a `const CharT[]` literal to a `span<const CharT>`, preserving the
+// trailing '\0'.
+//
+// (Not in `std::`; identical to constructor behavior, but more explicit.)
+template <typename CharT, size_t Extent>
+constexpr auto span_with_nul_from_cstring(
+    const CharT (&str LIFETIME_BOUND)[Extent])
+    ENABLE_IF_ATTR(str[Extent - 1u] == CharT{0},
+                   "requires string literal as input") {
+  return span(str);
+}
+
+// Converts an object which can already explicitly convert to some kind of span
+// directly into a byte span.
+//
+// (Not in `std::`.)
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFrom<const T&>)
+constexpr auto as_byte_span(const T& t LIFETIME_BOUND) {
+  return as_bytes(span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFromNonUnique<const T&>)
+constexpr auto as_byte_span(allow_nonunique_obj_t, const T& t LIFETIME_BOUND) {
+  return as_bytes(allow_nonunique_obj, span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFrom<const T&> &&
+           std::ranges::borrowed_range<T>)
+constexpr auto as_byte_span(const T& t) {
+  return as_bytes(span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFromNonUnique<const T&> &&
+           std::ranges::borrowed_range<T>)
+constexpr auto as_byte_span(allow_nonunique_obj_t, const T& t) {
+  return as_bytes(allow_nonunique_obj, span(t));
+}
+// Array arguments require dedicated specializations because if only the
+// generalized functions are available, the compiler cannot deduce the template
+// parameter.
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType>)
+constexpr auto as_byte_span(const ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_bytes(span<const ElementType, Extent>(arr));
+}
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType>)
+constexpr auto as_byte_span(allow_nonunique_obj_t,
+                            const ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_bytes(allow_nonunique_obj, span<const ElementType, Extent>(arr));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFrom<T &&> &&
+           !std::is_const_v<internal::ElementTypeOfSpanConstructedFrom<T>>)
+// NOTE: `t` is not marked as lifetimebound because the "non-const
+// `element_type`" requirement above will in turn require `T` to be a borrowed
+// range.
+constexpr auto as_writable_byte_span(T&& t) {
+  return as_writable_bytes(span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename T>
+  requires(internal::ByteSpanConstructibleFromNonUnique<T &&> &&
+           !std::is_const_v<internal::ElementTypeOfSpanConstructedFrom<T>>)
+constexpr auto as_writable_byte_span(allow_nonunique_obj_t, T&& t) {
+  return as_writable_bytes(allow_nonunique_obj, span(t));
+}
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_byte_span(
+    ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_writable_bytes(span<ElementType, Extent>(arr));
+}
+template <int&... ExplicitArgumentBarrier, typename ElementType, size_t Extent>
+  requires(internal::CanSafelyConvertNonUniqueToByteSpan<ElementType> &&
+           !std::is_const_v<ElementType>)
+constexpr auto as_writable_byte_span(
+    allow_nonunique_obj_t,
+    ElementType (&arr LIFETIME_BOUND)[Extent]) {
+  return as_writable_bytes(allow_nonunique_obj, span<ElementType, Extent>(arr));
+}
+
 // Type-deducing helpers for constructing a span.
 template <typename T>
 UNSAFE_BUFFER_USAGE constexpr span<T> make_span(T* data, size_t size) noexcept {
@@ -419,96 +1696,6 @@
   return span<T>(container);
 }
 
-// [span.objectrep], views of object representation
-template <typename T, size_t N, typename P>
-span<const uint8_t> as_bytes(span<T, N, P> s) noexcept {
-  // SAFETY: from size_bytes() method.
-  return UNSAFE_BUFFERS(
-      make_span(reinterpret_cast<const uint8_t*>(s.data()), s.size_bytes()));
-}
-
-// [span.objectrep], views of object representation
-template <typename T, size_t N, typename P>
-span<const uint8_t> as_bytes(allow_nonunique_obj_t tag,
-                             span<T, N, P> s) noexcept {
-  // SAFETY: from size_bytes() method.
-  return UNSAFE_BUFFERS(
-      make_span(reinterpret_cast<const uint8_t*>(s.data()), s.size_bytes()));
-}
-
-template <typename T,
-          size_t N,
-          typename P,
-          typename U = typename std::enable_if<!std::is_const<T>::value>::type>
-span<uint8_t> as_writable_bytes(span<T, N, P> s) noexcept {
-  // SAFETY: from size_bytes() method.
-  return UNSAFE_BUFFERS(
-      make_span(reinterpret_cast<uint8_t*>(s.data()), s.size_bytes()));
-}
-
-template <typename T, size_t N, typename P>
-span<const char> as_chars(span<T, N, P> s) noexcept {
-  // SAFETY: from size_bytes() method.
-  return UNSAFE_BUFFERS(
-      make_span(reinterpret_cast<const char*>(s.data()), s.size_bytes()));
-}
-
-template <typename T,
-          size_t N,
-          typename P,
-          typename U = typename std::enable_if<!std::is_const<T>::value>::type>
-span<char> as_writable_chars(span<T, N, P> s) noexcept {
-  // SAFETY: from size_bytes() method.
-  return UNSAFE_BUFFERS(
-      make_span(reinterpret_cast<char*>(s.data()), s.size_bytes()));
-}
-
-// `span_from_ref` converts a reference to T into a span of length 1.  This is a
-// non-std helper that is inspired by the `std::slice::from_ref()` function from
-// Rust.
-template <typename T>
-static constexpr span<T> span_from_ref(T& single_object) noexcept {
-  // SAFETY: single object passed by reference.
-  return UNSAFE_BUFFERS(make_span<T>(&single_object, 1u));
-}
-
-// `byte_span_from_ref` converts a reference to T into a span of uint8_t of
-// length sizeof(T).  This is a non-std helper that is a sugar for
-// `as_writable_bytes(span_from_ref(x))`.
-template <typename T>
-static constexpr span<const uint8_t> byte_span_from_ref(
-    const T& single_object) noexcept {
-  return as_bytes(span_from_ref(single_object));
-}
-template <typename T>
-static constexpr span<uint8_t> byte_span_from_ref(T& single_object) noexcept {
-  return as_writable_bytes(span_from_ref(single_object));
-}
-
-// Convenience function for converting an object which is itself convertible
-// to span into a span of bytes (i.e. span of const uint8_t). Typically used
-// to convert std::string or string-objects holding chars, or std::vector
-// or vector-like objects holding other scalar types, prior to passing them
-// into an API that requires byte spans.
-template <typename T>
-span<const uint8_t> as_byte_span(const T& arg) {
-  return as_bytes(make_span(arg));
-}
-template <typename T>
-span<const uint8_t> as_byte_span(T&& arg) {
-  return as_bytes(make_span(arg));
-}
-
-// Convenience function for converting an object which is itself convertible
-// to span into a span of mutable bytes (i.e. span of uint8_t). Typically used
-// to convert std::string or string-objects holding chars, or std::vector
-// or vector-like objects holding other scalar types, prior to passing them
-// into an API that requires mutable byte spans.
-template <typename T>
-constexpr span<uint8_t> as_writable_byte_span(T&& arg) {
-  return as_writable_bytes(make_span(arg));
-}
-
 }  // namespace pdfium
 
-#endif  // CORE_FXCRT_SPAN_H_
+#endif  // BASE_CONTAINERS_SPAN_H_
diff --git a/core/fxcrt/span_util.h b/core/fxcrt/span_util.h
index f1ab143..9ce1e9f 100644
--- a/core/fxcrt/span_util.h
+++ b/core/fxcrt/span_util.h
@@ -150,14 +150,15 @@
 
 template <typename T,
           typename U,
+          size_t M,
           typename = typename std::enable_if_t<std::is_const_v<T> ||
                                                !std::is_const_v<U>>>
-inline pdfium::span<T> reinterpret_span(pdfium::span<U> s) noexcept {
+inline pdfium::span<T> reinterpret_span(pdfium::span<U, M> s) noexcept {
   CHECK(alignof(T) == alignof(U) ||
         reinterpret_cast<uintptr_t>(s.data()) % alignof(T) == 0u);
   // SAFETY: relies on correct conversion of size_bytes() result.
-  return UNSAFE_BUFFERS(pdfium::make_span(reinterpret_cast<T*>(s.data()),
-                                          s.size_bytes() / sizeof(T)));
+  return UNSAFE_BUFFERS(
+      pdfium::span(reinterpret_cast<T*>(s.data()), s.size_bytes() / sizeof(T)));
 }
 
 }  // namespace fxcrt