Implement fxcrt::Zip<>().

Take some ideas from C++23's zip_view<> to help improve performance
of some image manipulations over spans in the subsequent CLs.

Change-Id: Idfb062a8eda40cc11ed614542f2451652c168318
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/121351
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Tom Sepez <tsepez@google.com>
diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn
index 70ad8c9..cba3e5c 100644
--- a/core/fxcrt/BUILD.gn
+++ b/core/fxcrt/BUILD.gn
@@ -140,6 +140,7 @@
     "xml/cfx_xmlparser.h",
     "xml/cfx_xmltext.cpp",
     "xml/cfx_xmltext.h",
+    "zip.h",
   ]
   configs += [
     "../../:pdfium_strict_config",
@@ -251,6 +252,7 @@
     "xml/cfx_xmlnode_unittest.cpp",
     "xml/cfx_xmlparser_unittest.cpp",
     "xml/cfx_xmltext_unittest.cpp",
+    "zip_unittest.cpp",
   ]
   deps = [ ":unit_test_support" ]
   pdfium_root_dir = "../../"
diff --git a/core/fxcrt/zip.h b/core/fxcrt/zip.h
new file mode 100644
index 0000000..9ee703a
--- /dev/null
+++ b/core/fxcrt/zip.h
@@ -0,0 +1,84 @@
+// 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.
+
+#ifndef CORE_FXCRT_ZIP_H_
+#define CORE_FXCRT_ZIP_H_
+
+#include <stdint.h>
+
+#include <utility>
+
+#include "core/fxcrt/check_op.h"
+#include "core/fxcrt/compiler_specific.h"
+#include "core/fxcrt/span.h"
+
+namespace fxcrt {
+
+// Vastly simplified implementation of ideas from C++23 zip_view<>. Allows
+// safe traversal of two ranges with a single bounds check per iteration.
+
+// Example usage:
+//   struct RGB { uint8_t r; uint8_t g; uint8_t b; };
+//   const uint8_t gray[256] = { ... };
+//   RGB rgbs[260];
+//   for (auto [in, out] : Zip(gray, rgbs)) {
+//     out.r = in;
+//     out.g = in;
+//     out.b = in;
+//   }
+// which fills the first 256 elements of rgbs with the corresponding gray
+// value in each component, say.
+
+// Differences include:
+// - Only zips together two views instead of N.
+// - Size is determined by the first view, which must be smaller than the
+//   second view.
+// - First view is presumed to be "input-like" and is const, second view is
+//   presumed to be "output-like" and is non-const.
+// - Only those methods required to support use in a range-based for-loop
+//   are provided.
+
+template <typename T, typename U>
+class ZipView {
+ public:
+  struct Iter {
+    bool operator==(const Iter& that) const { return first == that.first; }
+
+    bool operator!=(const Iter& that) const { return first != that.first; }
+
+    UNSAFE_BUFFER_USAGE Iter& operator++() {
+      // SAFETY: required from caller, enforced by UNSAFE_BUFFER_USAGE.
+      UNSAFE_BUFFERS(++first);
+      UNSAFE_BUFFERS(++second);
+      return *this;
+    }
+
+    std::pair<typename T::reference, typename U::reference> operator*() const {
+      return {*first, *second};
+    }
+
+    T::iterator first;
+    U::iterator second;
+  };
+
+  ZipView(T first, U second) : first_(first), second_(second) {
+    CHECK_LE(first.size(), second.size());
+  }
+
+  Iter begin() { return {first_.begin(), second_.begin()}; }
+  Iter end() { return {first_.end(), second_.end()}; }
+
+ private:
+  T first_;
+  U second_;
+};
+
+template <typename T, typename U>
+auto Zip(const T& first, U&& second) {
+  return ZipView(pdfium::span(first), pdfium::span(second));
+}
+
+}  // namespace fxcrt
+
+#endif  // CORE_FXCRT_ZIP_H_
diff --git a/core/fxcrt/zip_unittest.cpp b/core/fxcrt/zip_unittest.cpp
new file mode 100644
index 0000000..2bb010a
--- /dev/null
+++ b/core/fxcrt/zip_unittest.cpp
@@ -0,0 +1,45 @@
+// 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.
+
+#include <stdint.h>
+
+#include "core/fxcrt/span.h"
+#include "core/fxcrt/zip.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::ElementsAreArray;
+
+namespace fxcrt {
+
+TEST(Zip, EmptyZip) {
+  pdfium::span<const int> nothing;
+  int stuff[] = {1, 2, 3};
+
+  auto zip_nothing_nothing = Zip(nothing, nothing);
+  EXPECT_EQ(zip_nothing_nothing.begin(), zip_nothing_nothing.end());
+
+  auto zip_nothing_stuff = Zip(nothing, stuff);
+  EXPECT_EQ(zip_nothing_stuff.begin(), zip_nothing_stuff.end());
+}
+
+TEST(Zip, ActualZip) {
+  const int stuff[] = {1, 2, 3};
+  const int expected[] = {1, 2, 3, 0};
+  int output[4] = {};
+
+  for (auto [in, out] : Zip(stuff, output)) {
+    out = in;
+  }
+  EXPECT_THAT(output, ElementsAreArray(expected));
+}
+
+TEST(Zip, BadArgumentsZip) {
+  pdfium::span<const int> nothing;
+  int stuff[] = {1, 2, 3};
+
+  EXPECT_DEATH(Zip(stuff, nothing), ".*");
+}
+
+}  // namespace fxcrt