熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Web編程 >> 正文

使用 DirectWrite 和最新 C++ 管理字體

2022-06-13   來源: Web編程 

  DirectWrite 是一種相當強大的文本布局 API 它支持從 XAML 和 Office 的 Windows 運行時 (WinRT) 實現到 Internet Explorer 和更高版本的幾乎所有領先 Windows 應用程序和技術 它本身並不是呈現引擎但與 DirectD 有很近的關系是 DirectD 在 DirectX 系列中的同級產品 當然DirectD 是首要的硬件加速即時模式圖形 API

  您可以將 DirectWrite 與 DirectD 結合使用以提供硬件加速的文本呈現 說明一下之前我在 DirectWrite 方面的著述並不多 我不希望您認為 DirectD 只是 DirectWrite 呈現引擎 DirectD 遠不只如此 DirectWrite 還有其他很多功能在本月的專欄中我將演示一些使用 DirectWrite 可以完成的任務看看最新 C++ 是如何幫助簡化編程模型的
DirectWrite API

  我將使用 DirectWrite 探究系統字體集 首先我需要獲取 DirectWrite 工廠對象 這是編寫任何要使用 DirectWrite 的出色排版功能的應用程序的第一步 與大多數 Windows API 相同DirectWrite 也依賴於 COM 基礎內容 我需要調用 DWriteCreateFactory 函數來創建 DirectWrite 工廠對象 此函數返回一個指向該工廠對象的 COM 接口

  ComPtr<IDWriteFactory> factory;
           

  IDWriteFactory 接口是今年早些時候隨 Windows 和 DirectX 推出的最新版本 DirectWrite 工廠接口 IDWriteFactory 繼承自 IDWrite­Factory而 IDWrite­Factory 繼承自 IDWriteFactory 後者是原始的 DirectWrite 工廠接口它公開了大部分工廠功能

  我將基於前面的 ComPtr 類模板調用 DWriteCreateFactory 函數

  HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED
     __uuidof(factory)
     reinterpret_cast<IUnknown **>(factoryGetAddressOf())));
           

  DirectWrite 包含一項名為 Windows 字體緩存服務 (FontCache) 的 Windows 服務 第一個參數指示獲得的工廠是否參與此跨過程緩存的字體使用 有 DWRITE_FACTORY_TYPE_SHARED 和 DWRITE_FACTORY_TYPE_ISOLATED 兩個選項 SHARED 和 ISOLATED 工廠都可以利用已緩存的字體數據 只有 SHARED 工廠向緩存返回字體數據 第二個參數具體指示我希望在第三個和最後一個參數中返回哪個版本的 DirectWrite 工廠接口

  在 DirectWrite 工廠對象給定的情況下我可以直接要求其提供系統字體集

  ComPtr<IDWriteFontCollection> fonts;
   HR(factory>GetSystemFontCollection(fontsGetAddressOf()));
           

  GetSystemFontCollection 方法的第二個參數是可選的指示其是否檢查已安裝字體集的更新或更改 幸運的是這個參數默認為 false因此除非要確保結果集反映最近的更改否則不必考慮它 在字體集給定的情況下我可以獲取集合中的字體系列數如下所示

  unsigned const count = fonts>GetFontFamilyCount();
           

  然後我使用 GetFontFamily 方法通過從零開始的索引檢索單個字體系列對象 一個字體系列對象表示這樣一組字體它們共享一個名稱當然也是一種設計但粗細樣式和拉伸並不相同

  ComPtr<IDWriteFontFamily> family;
   HR(fonts>GetFontFamily(index familyGetAddressOf()));
           

  IDWriteFontFamily 接口繼承自 IDWriteFontList 接口因此我可以枚舉該字體系列中的各種字體 能夠檢索字體系列名稱合乎情理並且非常有用 不過系列名稱已經過本地化所以它並不像您期待的那樣簡單直接 首先我需要字體系列提供一個本地化字符串對象該對象針對每種支持的區域設置均包含一個系列名稱

  ComPtr<IDWriteLocalizedStrings> names;
   HR(family>GetFamilyNames(namesGetAddressOf()));
           

  我也可以枚舉系列名稱但一般只查找用戶默認區域設置對應的名稱 事實上IDWriteLocalizedStrings 接口提供了 FindLocaleName 方法來檢索本地化系列名稱的索引 我從調用 GetUserDefaultLocaleName 函數以獲取用戶默認區域設置開始

  wchar_t locale[LOCALE_NAME_MAX_LENGTH];
   VERIFY(GetUserDefaultLocaleName(locale countof(locale)));
           

  然後我將默認區域設置傳遞給 IDWriteLocalizedStrings FindLocaleName 方法確定該字體系列是否有針對當前用戶本地化的名稱

  unsigned index;
   BOOL exists;
   HR(names>FindLocaleName(locale &index &exists));
           

  如果請求的區域設置在集中不存在我可能會回到一些默認設置enus假設存在就可以使用 IDWriteLocalizedStrings GetString 方法獲取副本

  if (exists)
   {
     wchar_t name[];
     HR(names>GetString(index name _countof(name)));
   }
           

  如果擔心長度可以先調用 GetString­Length 方法 只要確保緩沖區足夠大就可以 提供了一個完整列表顯示所有內容是如何共同枚舉已安裝字體的

  圖 使用 DirectWrite API 枚舉字體

  ComPtr<IDWriteFactory> factory;
   HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED
     __uuidof(factory)
     reinterpret_cast<IUnknown **>(factoryGetAddressOf())));
   ComPtr<IDWriteFontCollection> fonts;
   HR(factory>GetSystemFontCollection(fontsGetAddressOf()));
   wchar_t locale[LOCALE_NAME_MAX_LENGTH];
   VERIFY(GetUserDefaultLocaleName(locale _countof(locale)));
   unsigned const count = fonts>GetFontFamilyCount();
   for (unsigned familyIndex = ; familyIndex != count; ++familyIndex)
   {
     ComPtr<IDWriteFontFamily> family;
     HR(fonts>GetFontFamily(familyIndex familyGetAddressOf()));
     ComPtr<IDWriteLocalizedStrings> names;
     HR(family>GetFamilyNames(namesGetAddressOf()));
     unsigned nameIndex;
     BOOL exists;
     HR(names>FindLocaleName(locale &nameIndex &exists));
     if (exists)
     {
       wchar_t name[];
       HR(names>GetString(nameIndex name countof(name)));
       wprintf(L"%sn" name);
     }
   }
           

  最新 C++ 淺談

  如果您經常閱讀本雜志就會知道我已經介紹過 DirectX特別是 DirectD 的最新 C++ 處理 dxh 頭文件 (d) 也包含 DirectWrite 可以用它大大簡化我在上面提供的代碼 不必調用 DWriteCreateFactory我可以從 DirectWrite 命名空間直接調用 CreateFactory 函數

  auto factory = CreateFactory();
           

  獲取系統字體集同樣簡單

  auto fonts = factoryGetSystemFontCollection();
           

  枚舉此字體集是 dxh 的真正亮點 我不必編寫傳統的 for 循環 也不必調用 GetFontFamilyCount 和 GetFontFamily 方法 我只需要編寫一個最新的基於范圍的 for 循環

  for (auto family : fonts)
   {
     
             }
           

  這些代碼與以往相同 編譯器(在 dxh 的幫助下)進行生成我使用了一個更為自然的編程模型編寫正確有效的代碼更為簡單 前面的 GetSystemFontCollection 方法返回一個 FontCollection 類其中包含一個迭代器它將延遲提取字體系列對象 這使得編譯器能夠有效地實現基於范圍的循環 提供了完整列表 與圖 中的代碼比較它更為清晰效率更高

  圖 使用 dxh 枚舉字體

  auto factory = CreateFactory();
   auto fonts = factoryGetSystemFontCollection();
   wchar_t locale[LOCALE_NAME_MAX_LENGTH];
   VERIFY(GetUserDefaultLocaleName(locale _countof(locale)));
   for (auto family : fonts)
   {
     auto names = familyGetFamilyNames();
     unsigned index;
     if (namesFindLocaleName(locale index))
     {
       wchar_t name[];
       namesGetString(index name);
       wprintf(L"%sn" name);
     }
   }
           

  字體浏覽器與 Windows 運行時

  DirectWrite 的作用遠不只是枚舉字體 我將前面這些內容與 DirectD 結合起來創建一個簡單的字體浏覽器應用程序 在我 月的專欄 () 中介紹了如何用標准 C++ 編寫基本的 WinRT 應用程序模型 在我 月的專欄 (msd/magazine/dn) 中介紹了如何通過 DirectX(具體而言是 DirectD)在此基於 CoreWindow 的應用程序中進行呈現 現在介紹如何在 DirectWrite 的幫助下擴展代碼從而使用 DirectD 呈現文本

  這些專欄發表後Windows 發布了它對 DPI 縮放在最新應用程序和桌面應用程序中的處理方式有一些改動 以後我會詳細介紹 DPI這裡暫不討論這些更改 我們只重點擴展我在八月提出在十月擴展過的 SampleWindow 類以便支持文本呈現並簡化字體浏覽

  首先將 DirectWrite Factory 類添加為成員變量

  DirectWrite::Factory m_writeFactory;
           

  在 SampleWindow CreateDeviceIndependentResources 方法內可以創建 DirectWrite 工廠

  m_writeFactory = DirectWrite::CreateFactory();
           

  在這裡我還可以獲取系統字體集和用戶的默認區域設置為枚舉字體系列做准備

  auto fonts = m_writeFactoryGetSystemFontCollection();
   wchar_t locale[LOCALE_NAME_MAX_LENGTH];
   VERIFY(GetUserDefaultLocaleName(locale _countof(locale)));
           

  我將使應用程序在用戶按下向上和向下箭頭鍵時循環切換字體 我不通過 COM 接口不斷枚舉字體集而只是將字體系列名稱復制到一個標准 set 容器

  set<wstring> m_fonts;
           

  現在我可以在 CreateDeviceIndependentResources 中使用圖 中基於范圍的 for 循環將名稱添加到 set

  m_fontsinsert(name);
           

  set 填充後使用一個指向 set 開頭的迭代器開始應用程序 將迭代器存儲為成員變量

  set<wstring>::iterator m_font;
           

  SampleWindow CreateDeviceIndependentResources 方法初始化迭代器並調用 CreateTextFormat 方法定義如下

  m_font = begin(m_fonts);
   CreateTextFormat();
           

  在 DirectD 繪制文本前需要創建一個文本格式對象 這需要字體系列名稱和所需字號 我允許用戶使用向左和向右箭頭鍵更改字號因此添加一個成員變量來跟蹤字號

  float m_size;
           

  Visual C++ 編譯器很快會允許我初始化像這樣的類內非靜態數據成員 現在需要在 SampleWindow 的構造函數中將它設置為合理的默認值 接下來需要定義 CreateTextFormat 方法 這只是 DirectWrite 工廠方法的同名包裝但它更新了一個成員變量DirectD 可以使用該變量定義要繪制的文本的格式

  TextFormat m_textFormat;
           

  然後CreateTextFormat 方法從 set 迭代器檢索字體系列名稱將其與當前字號組合創建一個新的文本格式對象

  void CreateTextFormat()
   {
     m_textFormat = m_writeFactoryCreateTextFormat(m_font>c_str()m_size);
   }
           

  我已經把它包裝起來因此除了最初在 CreateDeviceIndependentResources 末尾調用它外我還可以在每次用戶按下一個箭頭鍵更改字體系列或字號時調用它 這引出了在 WinRT 應用程序模型中如何處理按鍵的問題 在桌面應用程序中這與 WM_KEYDOWN 消息處理有關 好在 CoreWindow 提供了 KeyDown 事件它是這一消息的最新等效項 我從定義 IKeyEventHandler 接口開始SampleWindow 需要實現該接口

  typedef ITypedEventHandler<CoreWindow * KeyEventArgs *> IKeyEventHandler;
           

  然後可以將此接口添加到繼承接口的 SampleWindow 列表中並相應地更新 QueryInterface 實現 接下來只需提供它的 Invoke 實現

  auto __stdcall Invoke(
     ICoreWindow *IKeyEventArgs * args) > HRESULT override
   {
     
             return S_OK;
   }
           

  IKeyEventArgs 接口提供的信息與 LPARAM 和 WPARAM 向 WM_KEYDOWN 消息提供的信息基本相同 它的 get_VirtualKey 方法對應於後者的 WPARAM指示按下了哪個非系統鍵

  VirtualKey key;
   HR(args>get_VirtualKey(&key));
           

  與此類似它的 get_KeyStatus 方法對應於 WM_KEYDOWN 的 LPARAM 這樣可以提供有關按鍵事件狀態的豐富信息

  CorePhysicalKeyStatus status;
   HR(args>get_KeyStatus(&status));
           

  為方便用戶我支持在用戶按住箭頭鍵時加速以便更快地調整所呈現字體的字號 為此需要另一個成員變量

  unsigned m_accelerate;
           

  然後可以使用該事件的鍵狀態來確定是將字號更改一個增量還是增加一定的大小

  if (!statusWasKeyDown)
   {
     m_accelerate = ;
   }
   else
   {
     m_accelerate += ;
     m_accelerate = std::min(U m_accelerate);
   }
           

  我設置了上限因此加速不會太過 現在可以分別處理不同的按鍵 首先是向左箭頭鍵用於縮小字號

  if (VirtualKey_Left == key)
   {
     m_size = std::max(f m_size m_accelerate);
   }
           

  我很小心不會使字號無效 然後是向右箭頭鍵用於增加字號

  else if (VirtualKey_Right == key)
   {
     m_size += m_accelerate;
   }
           

  接下來移到上一字體系列處理向上箭頭鍵

  if (begin(m_fonts) == m_font)
   {
     m_font = end(m_fonts);
   }
   m_font;
           

  然後我仔細循環到最後一個字體看看迭代器是否會回到序列開頭 接下來移到下一字體系列處理向下箭頭鍵

  else if (VirtualKey_Down == key)
   {
     ++m_font;
     if (end(m_fonts) == m_font)
     {
         m_font = begin(m_fonts);
     }
   }
           

  在這裡再一次仔細循環到開頭看看迭代器是否會回到序列結尾 最後我可以在事件處理程序末尾調用 CreateTextFormat 方法來重新創建文本格式對象

  剩下的事情是更新 SampleWindow Draw 方法用當前文本格式繪制一些文本 方法如下

  wchar_t const text [] = L"The quick brown fox jumps over the lazy dog";
   m_targetDrawText(text _countof(text)
     m_textFormat
     RectF(f f sizeWidth f sizeHeight f)
     m_brush);
           

  DirectD 呈現目標的 DrawText 方法直接支持 DirectWrite現在 DirectWrite 就可以處理文本布局了而且呈現速度非常快這就是該腳本所執行的所有操作 是執行效果我可以按向上和向下箭頭鍵遍歷字體系列按向左和向右箭頭鍵調整字號DirectD 根據當前選擇自動重新呈現

  
字體浏覽器

彩色字體

  Windows 引入了一個稱為彩色字體的新功能去掉了一些實現彩色字體的次優解決方案當然這都是 DirectWrite 和 DirectD 促成的令人高興的是調用 DirectD DrawText 方法就像使用 DD_DRAW_TEXT_OPTIONS_ENABLE_COLOR_­FONT 常量一樣簡單我可以將 SampleWindow 的 Draw 方法更新為使用相應范圍的枚舉值

  m_targetDrawText(text _countof(text)
     m_textFormat
     RectF(f f sizeWidth f sizeHeight f)
     m_brush);
   DrawTextOptions::EnableColorFont);
           

   也是字體浏覽器這次是一些 Unicode 表情

  
彩色字體

  彩色字體的亮點在於可以自動縮放而不影響質量我可以在字體浏覽器應用程序中按向右箭頭鍵更近地查看細節結果如 所示

  
放大的彩色字體

  通過提供彩色字體硬件加速文本呈現以及流暢有效的代碼DirectWrite 在 DirectD 和最新 C++ 的幫助下煥發出生命力


From:http://tw.wingwit.com/Article/program/Web/201405/30797.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.