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"

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

相关推荐
ShineWinsu5 小时前
对于Qt:QTreeWidget常见属性以及QTreeWidgetItem类的解析
开发语言·c++·qt·gui·客户端·多叉树·qtreewidget
落羽的落羽5 小时前
【C++】C++17的主要特性
linux·服务器·开发语言·c++·算法·机器学习
汉克老师10 小时前
《从C++小白到算法高手:GESP 1—8级成长地图》第一讲:认识GESP——从编程新手到算法探索者
c++·gesp·信息学竞赛·小学生学c++
xy345311 小时前
Axure 9.0 垂直菜单介绍
ui·产品经理·axure·原型设计
晴天的雨.99212 小时前
【C++】继承篇
java·开发语言·c++
青少儿编程课堂12 小时前
蓝桥杯青少组图形化 + 全国青少年信息素养大赛 + 电子学会图形化考级:校园大富翁经营闯关解析
c++·python·算法·bfs·信息学竞赛
another heaven12 小时前
【C/C++ 第一次跨越(上):用 C++11/14 停止制造新的旧代码】
c语言·c++·制造
qq_4069810312 小时前
c++ lamda(三)Lambda表达式
java·前端·c++
I_belong_to_jesus1 天前
C++关于分支预测的优化1
开发语言·c++·算法
Rabitebla1 天前
【Linux】进程控制(十二) 自定义 Shell(二):内建命令与进程工作目录管理
linux·c++·笔记·学习·bash