chromium/pdf/pdfium/pdfium_font_win_unittest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "pdf/pdfium/pdfium_font_win.h"

#include <utility>

#include "base/containers/heap_array.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "pdf/pdf_features.h"
#include "pdf/pdfium/pdfium_engine.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/pdfium/public/fpdf_sysfontinfo.h"

namespace chrome_pdf {

namespace {

// Tests the SkiaFontMapper without a sandbox - this is useful to validate the
// logic within SkiaFontMapper but does not test how skia's default font manager
// is wired up.
class PDFiumFontWinTest : public testing::Test {
 public:
  // Secretly this is a skia typeface id.
  using FontId = void*;

  PDFiumFontWinTest() {
    scoped_feature_list_.InitWithFeatures({features::kWinPdfUseFontProxy}, {});
  }
  PDFiumFontWinTest(const PDFiumFontWinTest&) = delete;
  PDFiumFontWinTest& operator=(const PDFiumFontWinTest&) = delete;
  ~PDFiumFontWinTest() override = default;

 protected:
  void SetUp() override {
    InitializeSDK(/*enable_v8=*/false, /*use_skia=*/false,
                  FontMappingMode::kBlink);
    mapper_ = GetSkiaFontMapperForTesting();
    // Need these fields to do the tests.
    ASSERT_TRUE(mapper_);
    ASSERT_TRUE(mapper_->version);
    ASSERT_TRUE(mapper_->MapFont);
    ASSERT_TRUE(mapper_->GetFontData);
    ASSERT_TRUE(mapper_->DeleteFont);
  }

  void TearDown() override { ShutdownSDK(); }

  FontId MapFont(int weight,
                 bool italic,
                 int charset,
                 int pitch,
                 const char* face) {
    return mapper_->MapFont(nullptr, weight, italic, charset, pitch, face,
                            nullptr);
  }

  size_t GetFontData(FontId font,
                     unsigned int table,
                     unsigned char* buffer,
                     size_t buf_size) {
    return mapper_->GetFontData(nullptr, font, table, buffer, buf_size);
  }

  void DeleteFont(FontId font) { return mapper_->DeleteFont(nullptr, font); }

 private:
  raw_ptr<FPDF_SYSFONTINFO> mapper_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

}  // namespace

TEST_F(PDFiumFontWinTest, NonExistingFont) {
  EXPECT_EQ(nullptr, MapFont(FXFONT_FW_NORMAL, false, FXFONT_DEFAULT_CHARSET,
                             FXFONT_FF_ROMAN, "ThisIsHopefullyNeverAFont"));
}

TEST_F(PDFiumFontWinTest, Basic) {
  FontId id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_DEFAULT_CHARSET,
                      FXFONT_FF_ROMAN, "Arial");
  EXPECT_TRUE(id);
  size_t needed = GetFontData(id, 0, nullptr, 0);
  EXPECT_GT(needed, 0u);
  auto data = base::HeapArray<unsigned char>::WithSize(needed);
  size_t got = GetFontData(id, 0, data.data(), data.size());
  EXPECT_EQ(got, needed);
  DeleteFont(id);
}

TEST_F(PDFiumFontWinTest, DefaultFonts) {
  for (const auto* face :
       {"Courier", "Helvetica", "Symbol", "Times-BoldItalic"}) {
    FontId id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_DEFAULT_CHARSET,
                        FXFONT_FF_ROMAN, face);
    EXPECT_TRUE(id);
    DeleteFont(id);
  }
}

TEST_F(PDFiumFontWinTest, FallbackFontsGB) {
  // crbug.com/40109579 -> SimSun.
  FontId id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_GB2312_CHARSET,
                      FXFONT_FF_ROMAN, "\xD0\xC2\xCB\xCE\xCC\xE5");
  EXPECT_TRUE(id);
  DeleteFont(id);

  // crbug.com/40109579 -> SimSun.
  id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_GB2312_CHARSET, FXFONT_FF_ROMAN,
               "\xB7\xBD\xD5\xFD\xCF\xB8\xB5\xC8\xCF\xDF\xBC\xF2\xCC\xE5");
  EXPECT_TRUE(id);
  DeleteFont(id);

  // KaiTi is supplemental -> KaiTi or SimSun
  id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_GB2312_CHARSET, FXFONT_FF_ROMAN,
               "KaiTi");
  EXPECT_TRUE(id);
  DeleteFont(id);
}

TEST_F(PDFiumFontWinTest, FallbackFontsShiftJIS) {
  // crbug.com/40260882 -> MS Gothic.
  FontId id =
      MapFont(FXFONT_FW_NORMAL, false, FXFONT_SHIFTJIS_CHARSET, FXFONT_FF_ROMAN,
              "\x82\x6C\x82\x72\x83\x53\x83\x56\x83\x62\x83\x4E");
  EXPECT_TRUE(id);
  DeleteFont(id);

  // MS PMincho is supplemental -> MS PMincho or MS PGothic.
  id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_SHIFTJIS_CHARSET,
               FXFONT_FF_ROMAN, "MS PMincho");
  EXPECT_TRUE(id);
  DeleteFont(id);
}

TEST_F(PDFiumFontWinTest, FallbackFontsHangeul) {
  // e.g. Skia corpus 02201.pdf -> Gulim or Malgun Gothic.
  FontId id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_HANGEUL_CHARSET,
                      FXFONT_FF_ROMAN, "HYMyeongJo-Medium");
  EXPECT_TRUE(id);
  DeleteFont(id);
}

TEST_F(PDFiumFontWinTest, FinalFixupsArialBlack) {
  // ArialBlack with a low weight needs to be forced to weight kBlack.
  FontId id = MapFont(390, false, FXFONT_DEFAULT_CHARSET, FXFONT_FF_ROMAN,
                      "ArialBlack");
  EXPECT_TRUE(id);
  DeleteFont(id);
}

TEST_F(PDFiumFontWinTest, FinalFixupsComicSansMS) {
  // ComicSansMS -> Comic Sans MS.
  FontId id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_DEFAULT_CHARSET,
                      FXFONT_FF_ROMAN, "ComicSansMS");
  EXPECT_TRUE(id);
  DeleteFont(id);
}

TEST_F(PDFiumFontWinTest, FinalFixupsTrebuchetMS) {
  // TrebuchetMS -> Trebuchet MS.
  FontId id = MapFont(FXFONT_FW_NORMAL, false, FXFONT_DEFAULT_CHARSET,
                      FXFONT_FF_ROMAN, "TrebuchetMS");
  EXPECT_TRUE(id);
  DeleteFont(id);
}

}  // namespace chrome_pdf