Check the font width array generated by FPDFText_LoadCidType2Font()

Add a test case to show FPDFText_LoadCidType2Font() generates a font
widths array with more entries than strictly necessary, when the CID to
GID mapping contains only a few entries.

Augment the existing CheckCompositeFontWidths() helper to be able to
count the exact number of expected CIDs, if desired.

Bug: 376781381
Change-Id: I76ef3dbf2a920f701443caa4cc8b0538939c2a97
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/128011
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp
index 799a7e1..cf5cb1b 100644
--- a/fpdfsdk/fpdf_edit_embeddertest.cpp
+++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -26,6 +26,7 @@
 #include "core/fxcrt/fx_memcpy_wrappers.h"
 #include "core/fxcrt/fx_system.h"
 #include "core/fxcrt/span_util.h"
+#include "core/fxcrt/stl_util.h"
 #include "core/fxge/cfx_defaultrenderdevice.h"
 #include "core/fxge/fx_font.h"
 #include "fpdfsdk/cpdfsdk_helpers.h"
@@ -249,7 +250,8 @@
   }
 
   void CheckCompositeFontWidths(const CPDF_Array* widths_array,
-                                CPDF_Font* typed_font) {
+                                CPDF_Font* typed_font,
+                                testing::Matcher<int> matcher) {
     // Check that W array is in a format that conforms to PDF spec 1.7 section
     // "Glyph Metrics in CIDFonts" (these checks are not
     // implementation-specific).
@@ -285,9 +287,10 @@
       }
       num_cids_checked += last_cid - cid + 1;
     }
-    // Make sure we have a good amount of cids described
-    EXPECT_GT(num_cids_checked, 200);
+    // Check the CID count.
+    EXPECT_THAT(num_cids_checked, matcher);
   }
+
   CPDF_Document* cpdf_doc() { return cpdf_doc_; }
 
  private:
@@ -3557,7 +3560,7 @@
   RetainPtr<const CPDF_Array> widths_array = cidfont_dict->GetArrayFor("W");
   ASSERT_TRUE(widths_array);
   EXPECT_GT(widths_array->size(), 1u);
-  CheckCompositeFontWidths(widths_array.Get(), typed_font);
+  CheckCompositeFontWidths(widths_array, typed_font, testing::Ge(201));
 }
 
 TEST_F(FPDFEditEmbedderTest, LoadCIDType2Font) {
@@ -3600,7 +3603,7 @@
   // Check widths
   RetainPtr<const CPDF_Array> widths_array = cidfont_dict->GetArrayFor("W");
   ASSERT_TRUE(widths_array);
-  CheckCompositeFontWidths(widths_array.Get(), typed_font);
+  CheckCompositeFontWidths(widths_array, typed_font, testing::Ge(201));
 }
 
 TEST_F(FPDFEditEmbedderTest, NormalizeNegativeRotation) {
@@ -3788,13 +3791,18 @@
 end
 )";
 
-  const std::vector<uint8_t> cid_to_gid_map = {0, 0, 0, 1, 0, 2, 0, 3, 0, 4,
-                                               0, 5, 0, 6, 0, 7, 0, 8, 0, 9};
+  static constexpr auto kCidToGidMap = fxcrt::ToArray<const uint8_t>(
+      {0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9});
 
   ScopedFPDFFont font(FPDFText_LoadCidType2Font(
       document(), font_data.data(), font_data.size(), kToUnicodeCMap,
-      cid_to_gid_map.data(), cid_to_gid_map.size()));
+      kCidToGidMap.data(), kCidToGidMap.size()));
   ASSERT_TRUE(font);
+  CPDF_Font* typed_font = CPDFFontFromFPDFFont(font.get());
+  RetainPtr<const CPDF_Array> widths_array =
+      GetWidthsArrayForCidFont(typed_font);
+  ASSERT_TRUE(widths_array);
+  CheckCompositeFontWidths(widths_array, typed_font, testing::Eq(9));
 
   FPDF_PAGEOBJECT text_object =
       FPDFPageObj_CreateTextObj(document(), font.get(), 20.0f);
@@ -3816,6 +3824,56 @@
   VerifySavedDocument(400, 400, NotoSansSCChecksum());
 }
 
+TEST_F(FPDFEditEmbedderTest, LoadCidType2FontCustomGeneratedWidths) {
+  CreateEmptyDocument();
+  std::string font_path;
+  ASSERT_TRUE(PathService::GetThirdPartyFilePath(
+      "NotoSansCJK/NotoSansSC-Regular.subset.otf", &font_path));
+
+  std::vector<uint8_t> font_data = GetFileContents(font_path.c_str());
+  ASSERT_FALSE(font_data.empty());
+
+  static const char kToUnicodeCMap[] = R"(
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo <<
+  /Registry (Adobe)
+  /Ordering (Identity)
+  /Supplement 0
+>> def
+/CMapName /Adobe-Identity-H def
+/CMapType 2 def
+1 begincodespacerange
+<0000> <FFFF>
+endcodespacerange
+3 beginbfrange
+<0002> <0003> [<3002> <2F00>]
+<0003> <0004> [<4E00> <2F06>]
+<0004> <0005> [<4E8C> <53E5>]
+endbfrange
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+)";
+
+  static constexpr auto kCidToGidMap =
+      fxcrt::ToArray<const uint8_t>({0, 0, 0, 1, 0, 2, 0, 3, 0, 4});
+
+  ScopedFPDFFont font(FPDFText_LoadCidType2Font(
+      document(), font_data.data(), font_data.size(), kToUnicodeCMap,
+      kCidToGidMap.data(), kCidToGidMap.size()));
+  ASSERT_TRUE(font);
+  CPDF_Font* typed_font = CPDFFontFromFPDFFont(font.get());
+  RetainPtr<const CPDF_Array> widths_array =
+      GetWidthsArrayForCidFont(typed_font);
+  ASSERT_TRUE(widths_array);
+  // TODO(crbug.com/376781381): Reduce `widths_array` size, given the smaller
+  // `kCidToGidMap`.
+  CheckCompositeFontWidths(widths_array, typed_font, testing::Eq(9));
+}
+
 TEST_F(FPDFEditEmbedderTest, LoadCidType2FontWithBadParameters) {
   ASSERT_TRUE(CreateNewDocument());