Implement CGdiPrinterDriver::DrawDeviceText().

This is sufficient to print text with GDI for PDFs generated by Chromium
and cannot print any arbitrary PDF. Text that cannot be printed will be
drawn as glyphs as before.

BUG=409472

Review-Url: https://codereview.chromium.org/2113563003
diff --git a/core/fxge/include/fx_ge_win32.h b/core/fxge/include/fx_ge_win32.h
index c0889c1..5b57863 100644
--- a/core/fxge/include/fx_ge_win32.h
+++ b/core/fxge/include/fx_ge_win32.h
@@ -16,6 +16,16 @@
 
 class IFX_RenderDeviceDriver;
 
+#if defined(PDFIUM_PRINT_TEXT_WITH_GDI)
+typedef void (*PDFiumEnsureTypefaceCharactersAccessible)(const LOGFONT* font,
+                                                         const wchar_t* text,
+                                                         size_t text_length);
+
+extern bool g_pdfium_print_text_with_gdi;
+extern PDFiumEnsureTypefaceCharactersAccessible
+    g_pdfium_typeface_accessible_func;
+#endif
+
 typedef struct WINDIB_Open_Args_ {
   int flags;
 
diff --git a/core/fxge/win32/fx_win32_print.cpp b/core/fxge/win32/fx_win32_print.cpp
index aef6062..e1071e7 100644
--- a/core/fxge/win32/fx_win32_print.cpp
+++ b/core/fxge/win32/fx_win32_print.cpp
@@ -10,12 +10,47 @@
 
 #include <windows.h>
 
+#include <algorithm>
+
 #include "core/fxge/dib/dib_int.h"
 #include "core/fxge/ge/fx_text_int.h"
 #include "core/fxge/include/fx_freetype.h"
 #include "core/fxge/include/fx_ge_win32.h"
 #include "core/fxge/win32/win32_int.h"
 
+#if defined(PDFIUM_PRINT_TEXT_WITH_GDI)
+namespace {
+
+class ScopedState {
+ public:
+  ScopedState(HDC hDC, HFONT hFont) : m_hDC(hDC) {
+    m_iState = SaveDC(m_hDC);
+    m_hFont = SelectObject(m_hDC, hFont);
+  }
+
+  ~ScopedState() {
+    HGDIOBJ hFont = SelectObject(m_hDC, m_hFont);
+    DeleteObject(hFont);
+    RestoreDC(m_hDC, m_iState);
+  }
+
+ private:
+  HDC m_hDC;
+  HGDIOBJ m_hFont;
+  int m_iState;
+
+  ScopedState(const ScopedState&) = delete;
+  void operator=(const ScopedState&) = delete;
+};
+
+}  // namespace
+
+bool g_pdfium_print_text_with_gdi = false;
+
+PDFiumEnsureTypefaceCharactersAccessible g_pdfium_typeface_accessible_func =
+    nullptr;
+#endif
+
 CGdiPrinterDriver::CGdiPrinterDriver(HDC hDC)
     : CGdiDeviceDriver(hDC, FXDC_PRINTER),
       m_HorzSize(::GetDeviceCaps(m_hDC, HORZSIZE)),
@@ -146,17 +181,138 @@
                          bFlipY ? -full_rect.Height() : full_rect.Height(),
                          nullptr, 0, blend_type);
   }
-  if (FXSYS_fabs(pMatrix->a) < 0.5f && FXSYS_fabs(pMatrix->d) < 0.5f) {
-    std::unique_ptr<CFX_DIBitmap> pTransformed(
-        pSource->SwapXY(pMatrix->c > 0, pMatrix->b < 0));
-    if (!pTransformed)
-      return FALSE;
+  if (FXSYS_fabs(pMatrix->a) >= 0.5f || FXSYS_fabs(pMatrix->d) >= 0.5f)
+    return FALSE;
 
-    return StretchDIBits(pTransformed.get(), color, full_rect.left,
-                         full_rect.top, full_rect.Width(), full_rect.Height(),
-                         nullptr, 0, blend_type);
-  }
-  return FALSE;
+  std::unique_ptr<CFX_DIBitmap> pTransformed(
+      pSource->SwapXY(pMatrix->c > 0, pMatrix->b < 0));
+  if (!pTransformed)
+    return FALSE;
+
+  return StretchDIBits(pTransformed.get(), color, full_rect.left, full_rect.top,
+                       full_rect.Width(), full_rect.Height(), nullptr, 0,
+                       blend_type);
 }
 
+FX_BOOL CGdiPrinterDriver::DrawDeviceText(int nChars,
+                                          const FXTEXT_CHARPOS* pCharPos,
+                                          CFX_Font* pFont,
+                                          CFX_FontCache* pCache,
+                                          const CFX_Matrix* pObject2Device,
+                                          FX_FLOAT font_size,
+                                          uint32_t color) {
+#if defined(PDFIUM_PRINT_TEXT_WITH_GDI)
+  if (!g_pdfium_print_text_with_gdi)
+    return FALSE;
+
+  if (nChars < 1 || !pFont || !pFont->IsEmbedded() || !pFont->IsTTFont())
+    return FALSE;
+
+  // Font
+  //
+  // Note that |pFont| has the actual font to render with embedded within, but
+  // but unfortunately AddFontMemResourceEx() does not seem to cooperate.
+  // Loading font data to memory seems to work, but then enumerating the fonts
+  // fails to find it. This requires more investigation. In the meanwhile,
+  // assume the printing is happening on the machine that generated the PDF, so
+  // the embedded font, if not a web font, is available through GDI anyway.
+  // TODO(thestig): Figure out why AddFontMemResourceEx() does not work.
+  // Generalize this method to work for all PDFs with embedded fonts.
+  // In sandboxed environments, font loading may not work at all, so this may be
+  // the best possible effort.
+  LOGFONT lf = {};
+  lf.lfHeight = -font_size;
+  lf.lfWeight = pFont->IsBold() ? FW_BOLD : FW_NORMAL;
+  lf.lfItalic = pFont->IsItalic();
+  lf.lfCharSet = DEFAULT_CHARSET;
+
+  const CFX_WideString wsName = pFont->GetFaceName().UTF8Decode();
+  int iNameLen = std::min(wsName.GetLength(), LF_FACESIZE - 1);
+  memcpy(lf.lfFaceName, wsName.c_str(), sizeof(lf.lfFaceName[0]) * iNameLen);
+  lf.lfFaceName[iNameLen] = 0;
+
+  HFONT hFont = CreateFontIndirect(&lf);
+  if (!hFont)
+    return FALSE;
+
+  ScopedState state(m_hDC, hFont);
+  size_t nTextMetricSize = GetOutlineTextMetrics(m_hDC, 0, nullptr);
+  if (nTextMetricSize == 0) {
+    // Give up and fail if there is no way to get the font to try again.
+    if (!g_pdfium_typeface_accessible_func)
+      return FALSE;
+
+    // Try to get the font. Any letter will do.
+    g_pdfium_typeface_accessible_func(&lf, L"A", 1);
+    nTextMetricSize = GetOutlineTextMetrics(m_hDC, 0, nullptr);
+    if (nTextMetricSize == 0)
+      return FALSE;
+  }
+
+  std::vector<BYTE> buf(nTextMetricSize);
+  OUTLINETEXTMETRIC* pTextMetric =
+      reinterpret_cast<OUTLINETEXTMETRIC*>(buf.data());
+  if (GetOutlineTextMetrics(m_hDC, nTextMetricSize, pTextMetric) == 0)
+    return FALSE;
+
+  // If the selected font is not the requested font, then bail out. This can
+  // happen with web fonts, for example.
+  wchar_t* wsSelectedName = reinterpret_cast<wchar_t*>(
+      buf.data() + reinterpret_cast<size_t>(pTextMetric->otmpFaceName));
+  if (wsName != wsSelectedName)
+    return FALSE;
+
+  // Transforms
+  SetGraphicsMode(m_hDC, GM_ADVANCED);
+  XFORM xform;
+  xform.eM11 = pObject2Device->GetA();
+  xform.eM12 = pObject2Device->GetB();
+  xform.eM21 = -pObject2Device->GetC();
+  xform.eM22 = -pObject2Device->GetD();
+  xform.eDx = pObject2Device->GetE();
+  xform.eDy = pObject2Device->GetF();
+  ModifyWorldTransform(m_hDC, &xform, MWT_LEFTMULTIPLY);
+
+  // Color
+  int iUnusedAlpha;
+  FX_COLORREF rgb;
+  ArgbDecode(color, iUnusedAlpha, rgb);
+  SetTextColor(m_hDC, rgb);
+  SetBkMode(m_hDC, TRANSPARENT);
+
+  // Text
+  CFX_WideString wsText;
+  for (int i = 0; i < nChars; ++i) {
+    // Only works with PDFs from Skia's PDF generator. Cannot handle arbitrary
+    // values from PDFs.
+    const FXTEXT_CHARPOS& charpos = pCharPos[i];
+    ASSERT(charpos.m_OriginX == 0);
+    ASSERT(charpos.m_OriginY == 0);
+    ASSERT(charpos.m_AdjustMatrix[0] == 0);
+    ASSERT(charpos.m_AdjustMatrix[1] == 0);
+    ASSERT(charpos.m_AdjustMatrix[2] == 0);
+    ASSERT(charpos.m_AdjustMatrix[3] == 0);
+    wsText += charpos.m_GlyphIndex;
+  }
+
+  // Draw
+  SetTextAlign(m_hDC, TA_LEFT | TA_BASELINE);
+  if (ExtTextOutW(m_hDC, 0, 0, ETO_GLYPH_INDEX, nullptr, wsText.c_str(), nChars,
+                  nullptr)) {
+    return TRUE;
+  }
+
+  // Give up and fail if there is no way to get the font to try again.
+  if (!g_pdfium_typeface_accessible_func)
+    return FALSE;
+
+  // Try to get the font and draw again.
+  g_pdfium_typeface_accessible_func(&lf, wsText.c_str(), nChars);
+  return ExtTextOutW(m_hDC, 0, 0, ETO_GLYPH_INDEX, nullptr, wsText.c_str(),
+                     nChars, nullptr);
+#else
+  return FALSE;
 #endif
+}
+
+#endif  // _FX_OS_ == _FX_WIN32_DESKTOP_ || _FX_OS_ == _FX_WIN64_DESKTOP_
diff --git a/core/fxge/win32/win32_int.h b/core/fxge/win32/win32_int.h
index e844f5c..c7a7606 100644
--- a/core/fxge/win32/win32_int.h
+++ b/core/fxge/win32/win32_int.h
@@ -239,6 +239,13 @@
                       uint32_t render_flags,
                       void*& handle,
                       int blend_type) override;
+  FX_BOOL DrawDeviceText(int nChars,
+                         const FXTEXT_CHARPOS* pCharPos,
+                         CFX_Font* pFont,
+                         CFX_FontCache* pCache,
+                         const CFX_Matrix* pObject2Device,
+                         FX_FLOAT font_size,
+                         uint32_t color) override;
 
   const int m_HorzSize;
   const int m_VertSize;
diff --git a/fpdfsdk/fpdfview.cpp b/fpdfsdk/fpdfview.cpp
index 646fb46..34e7d23 100644
--- a/fpdfsdk/fpdfview.cpp
+++ b/fpdfsdk/fpdfview.cpp
@@ -334,6 +334,17 @@
   return FSDK_SetSandBoxPolicy(policy, enable);
 }
 
+#if defined(_WIN32) && defined(PDFIUM_PRINT_TEXT_WITH_GDI)
+DLLEXPORT void STDCALL
+FPDF_SetTypefaceAccessibleFunc(PDFiumEnsureTypefaceCharactersAccessible func) {
+  g_pdfium_typeface_accessible_func = func;
+}
+
+DLLEXPORT void STDCALL FPDF_SetPrintTextWithGDI(FPDF_BOOL use_gdi) {
+  g_pdfium_print_text_with_gdi = !!use_gdi;
+}
+#endif
+
 DLLEXPORT FPDF_DOCUMENT STDCALL FPDF_LoadDocument(FPDF_STRING file_path,
                                                   FPDF_BYTESTRING password) {
   // NOTE: the creation of the file needs to be by the embedder on the
diff --git a/public/fpdfview.h b/public/fpdfview.h
index cea431a..cbb577b 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -211,6 +211,34 @@
 DLLEXPORT void STDCALL FPDF_SetSandBoxPolicy(FPDF_DWORD policy,
                                              FPDF_BOOL enable);
 
+#if defined(_WIN32) && defined(PDFIUM_PRINT_TEXT_WITH_GDI)
+// Pointer to a helper function to make |font| with |text| of |text_length|
+// accessible when printing text with GDI. This is useful in sandboxed
+// environments where PDFium's access to GDI may be restricted.
+typedef void (*PDFiumEnsureTypefaceCharactersAccessible)(const LOGFONT* font,
+                                                         const wchar_t* text,
+                                                         size_t text_length);
+
+// Function: FPDF_SetTypefaceAccessibleFunc
+//          Set the function pointer that makes GDI fonts available in sandboxed
+//          environments. Experimental API.
+// Parameters:
+//          func -   A function pointer. See description above.
+// Return value:
+//          None.
+DLLEXPORT void STDCALL
+FPDF_SetTypefaceAccessibleFunc(PDFiumEnsureTypefaceCharactersAccessible func);
+
+// Function: FPDF_SetPrintTextWithGDI
+//          Set whether to use GDI to draw fonts when printing on Windows.
+//          Experimental API.
+// Parameters:
+//          use_gdi -   Set to true to enable printing text with GDI.
+// Return value:
+//          None.
+DLLEXPORT void STDCALL FPDF_SetPrintTextWithGDI(FPDF_BOOL use_gdi);
+#endif
+
 // Function: FPDF_LoadDocument
 //          Open and load a PDF document.
 // Parameters: