2610C++,UI复合技术在窗口中绘画字体图标

UIComposition在Win32窗口中绘画字体图标

前面,介绍了如何使用UIComposition技术在Win32窗口中绘画文本

生成自定义字体集合

cpp 复制代码
ComPtr<IDWriteFontSetBuilder1> builder;
if (FAILED(dwriteFactory->CreateFontSetBuilder(&builder))) {
    _ASSERT_EXPR(FALSE, L"CreateFontSetBuilder, error");
    return;
}
     //Microsoft::WRL::ComPtr<IDWriteInMemoryFontFileLoader> fontLoader;
if (!fontLoader) {
    dwriteFactory->CreateInMemoryFontFileLoader(fontLoader.GetAddressOf());
    dwriteFactory->RegisterFontFileLoader(fontLoader.Get());
}
//std::vector<std::wstring> fontNames;
for (const auto& resName : fontNames) {
    auto [pData, size] = Util::getRes(resName.c_str());
// 工具方法,读取`exe`资源内的`字体文件`,见后
    if (pData == nullptr || size == 0) {
        _ASSERT_EXPR(FALSE, L"load font res, error");
        return;
    }
    ComPtr<IDWriteFontFile> fontFile;
    auto hr = fontLoader->CreateInMemoryFontFileReference(dwriteFactory.Get(), pData, size, nullptr, fontFile.GetAddressOf());
    if (FAILED(hr)) {
        _ASSERT_EXPR(FALSE, L"font res decode, error");
        continue;
    }
    builder->AddFontFile(fontFile.Get());
}
ComPtr<IDWriteFontSet> customFontSet;
if (FAILED(builder->CreateFontSet(&customFontSet))) {
    _ASSERT_EXPR(FALSE, L"CreateFontSet, error");
    return;
}
     //Microsoft::WRL::ComPtr<IDWriteFontCollection1> customFontCollection;

if (FAILED(dwriteFactory->CreateFontCollectionFromFontSet(customFontSet.Get(), customFontCollection.ReleaseAndGetAddressOf()))) {
    _ASSERT_EXPR(FALSE, L"CreateFontCollectionFromFontSet, error");
    return;
}

这段代码的作用是从应用资源中加载自定义字体,并构建一个独立的DirectWrite``字体集合,

代码首先创建IDWriteFontSetBuilder1构建器,并以懒加载单例模式,注册内存加载字体器fontLoader,确保只初化一次加载器且不污染系统全局状态.

随后遍历字体资源列表,工具函数取exe中的字体资源(二进制数据,稍后介绍此函数),

将字体二进制数据包装为DWrite可识别的字体文件引用并在构建器中添加fontFile,单个加载字体失败不会影响整体进程,保证了程序的健壮性.

最终,构建器生成字体集合(customFontSet)并按IDWriteFontCollection1对象转换.

创建自定义字体TextFormat集合

cpp 复制代码
     //std::unordered_map<std::wstring, Microsoft::WRL::ComPtr<IDWriteTextFormat>> customFormats; 
customFormats.clear();
if (!customFontCollection) return;
auto familyCount = customFontCollection->GetFontFamilyCount();
for (UINT32 i = 0; i < familyCount; i++) {
    ComPtr<IDWriteFontFamily> family;
    if (FAILED(customFontCollection->GetFontFamily(i, family.GetAddressOf()))) continue;
    ComPtr<IDWriteLocalizedStrings> names;
    if (FAILED(family->GetFamilyNames(names.GetAddressOf()))) continue;
    auto nameCount = names->GetCount();
    if (nameCount == 0) continue;
//先取第0个名字建`格式`
    UINT32 len{ 0 };
    if (FAILED(names->GetStringLength(0, &len))) continue;
    std::wstring first(len, L'\0');
    if (FAILED(names->GetString(0, first.data(), len + 1))) continue;
    ComPtr<IDWriteTextFormat> format;
    if (FAILED(dwriteFactory->CreateTextFormat(first.data(), customFontCollection.Get(),
        DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
        12.f, L"zhCN", format.GetAddressOf()))) continue;
// 各本地化名字都注册一份(同一个`格式`),调用者写哪个名字都能命中
    for (UINT32 j = 0; j < nameCount; j++) {
        if (FAILED(names->GetStringLength(j, &len))) continue;
        std::wstring name(len, L'\0');
        if (FAILED(names->GetString(j, name.data(), len + 1))) continue;
        std::transform(name.begin(), name.end(), name.begin(), ::towlower);
        customFormats[name] = format;
    }
}

这段代码首先遍历上一步生成的customFontCollection中的所有字体族,取每个字体族的本地化名列表.

因为一个字体一般包含多个名(如英文名,中文名或不同语言下的别名),代码会按主名取第一个名.

调用CreateTextFormat创建一个默认``IDWriteTextFormat对象(12pt,普通字重).

在创建好基础的格式后,代码会遍历该字体的所有本地化名,按小写统一转换它们,并全部按键(键)指向同一个格式对象(值)保存字典.

后续渲染文本时,调用者无论传入字体的英文,中文还是任意大小写组合,都能精准命中对应的文本格式.

这段代码为后续DrawTextLayout提供了开箱即用的格式模板.

使用自定义字体的TextFormat

cpp 复制代码
IDWriteTextFormat* D2D::getTextFormat(const std::wstring& fontFamily)
{
    if (fontFamily.empty() || customFormats.empty()) return baseTextFormat.Get();
    std::wstring key{ fontFamily };
    std::transform(key.begin(), key.end(), key.begin(), ::towlower);
    auto it = customFormats.find(key);
    return it == customFormats.end()   baseTextFormat.Get() : it->second.Get();
}
auto format = d2d->getTextFormat(fontFamily);
d2d->dwriteFactory->CreateTextLayout(text.data(), (UINT32)text.length(), format, FLT_MAX, FLT_MAX, textLayout.ReleaseAndGetAddressOf());

这段代码首先对传入的字体族名小写标准化处理,以确保与前构建的customFormats字典键值匹配;

随后在字典中查找对应的IDWriteTextFormat,若找不到或输入为空,则自动返回初化阶段创建的baseTextFormat(微软雅黑)为兜底.

取到安全的文本格式后,代码立即调用CreateTextLayout生成文本布局对象.

到这里,就和上一节介绍的使用系统字体``绘画文本一样了.

附:读取exe资源中的字体文件

cpp 复制代码
std::tuple<void*, DWORD> Util::getRes(const std::wstring& name)
{
    HRSRC hRes = FindResource(NULL, name.data(), RT_RCDATA);
    if (!hRes) {
        return std::make_tuple(nullptr, 0);
    }
    HGLOBAL hData = LoadResource(NULL, hRes);
    if (!hData) {
        return std::make_tuple(nullptr, 0);
    }
    void* pData = LockResource(hData);
    DWORD size = SizeofResource(NULL, hRes);
    return std::make_tuple(pData, size);
}

这段代码从WindowsPE资源段中读取二进制数据的常见工具函数.

它是标准的Win32资源读取方法(FindResource->LoadResource->LockResource)定位并锁定指定名的RT_RCDATA资源,最终返回一个包含内存指针和数据大小的元组.

只要以形式设置应用的Res.rc文件即可在exe中嵌入一个字体文件资源了.

cpp 复制代码
iconfong RCDATA "res/iconfont.ttf"

这样分发应用时就不用附加分发字体文件了.

相关推荐
见叶之秋1 小时前
【C++】红与黑的边界:红黑树从插入到自检
数据结构·c++·算法·面试
程序猿编码2 小时前
不依赖 vLLM/llama.cpp:一套面向学习的 DeepSeek-R1 CUDA 推理实现
c++·学习·语言模型·llama·vllm·大模型推理
kiracrimson2 小时前
string 容器:从 C 字符串到会自己扩容的类
开发语言·数据结构·c++·stl
程序猿编码2 小时前
纯 C++ 手写 LLM 推理引擎:分页 KV 缓存、连续批、多卡张量并行全部内置
c++·深度学习·缓存·大模型推理·自动微分·模型推理
G018_star sky♬3 小时前
nodeJS他是如何用JavaScript封装C++的呢
开发语言·javascript·c++
choumin3 小时前
C++中std::move和std::forward的关系和区别
c++·std move·std forward·关系和区别
程序猿编码3 小时前
YOLOv5/v7/YOLOX 全支持:基于 TensorRT 的工业视觉推理底座详解
c++·yolo·机器学习·推理·模型推理
kyle~3 小时前
C++ 原子操作、内存模型与无锁并发编程
linux·开发语言·c++·操作系统
another heaven3 小时前
【C/C++ C++ 面向对象架构的五大特点与能力场景:封装、继承、多态、抽象与 RAII(含实战案例)】
c语言·c++·架构