示例工程【免费下载链接】Windows-universal-samplesAPI samples for the Universal Windows Platform.项目地址https://gitcode.com/gh_mirrors/wi/Windows-universal-samples点击查看免费下载本指南以 Windows-universal-samples 仓库中的 DWriteTextLayoutCloudFont 示例关联文档为核心讲解 Windows 10 引入的 DirectWrite 可下载字体Downloadable Fonts又称云字体机制以及它如何与IDWriteTextLayout3文本布局 API 协同工作。读完本文你将掌握如何通过IDWriteFactory3启用可下载字体、如何让文本布局自动把远程字体请求加入下载队列、如何实现IDWriteFontDownloadListener并在字体下载完成后刷新布局以及如何结合源码理解该机制在真实 UWP 应用中的完整落地流程。背景什么是 DirectWrite 可下载字体Windows 10 为 DirectWrite 新增了一项能力应用可以使用设备上并未安装的字体来排版文本内容字体数据按需从 Microsoft 服务下载。DirectWrite 为此提供了底层 API而最便捷的用法是借助 DirectWrite 的文本布局接口IDWriteTextLayout3——它把底层 API 集成起来并替你完成了一部分工作当使用某个本地未安装的 Windows 字体创建文本布局时布局过程会自动把所需的字体数据请求加入字体下载队列font download queue应用只需要补充两段代码一段发起下载一段在下载完成时做出响应。示例官方描述见 Scenario_CloudFontOverview.xaml给出了该机制的完整行为链布局首次使用时会先选用回退字体fallback fonts布局执行过程中 DirectWrite 会判断要显示内容实际需要请求字体的哪些部分并把请求加入队列下载完成后应用调用IDWriteTextLayout3::InvalidateLayout强制重新排版这次就能使用已下载的字体数据了。用户看到的效果是内容先用回退字体显示短暂延迟后自动更新为目标字体的样式。示例工程结构双项目拆分示例由两个 Visual Studio 工程组成解决方案文件为 DWriteTextLayoutCloudFont.sln工程角色说明DWriteTextLayoutCloudFontImplementationWindows Runtime Component封装 DirectWrite 的文本布局与可下载字体 API并利用 DirectX 实现 XAMLSurfaceImageSource来显示文本布局是示例的技术核心DWriteTextLayoutCloudFontUWP 应用外壳基于 XAML 的示例应用作为上述 DirectWrite API 的客户端负责场景导航与 UI这种实现组件 XAML 外壳的组织方式让示例能利用 XAML 的简洁性来导航场景同时把真正有价值的原生 DirectWrite 代码独立出来便于读者聚焦研究。Implementation 工程的关键文件TextLayout.h/.cpp封装 DirectWrite 文本布局 APIIDWriteTextLayout3。FontDownloadListener.h/.cpp封装 DirectWrite 底层下载机制提供IDWriteFontDownloadListener接口实现用于响应下载完成事件。FontNameCollector.h/.cpp封装附加的 DirectWrite API用于确定文本布局实际用到了哪些字体。使用下载机制本身不需要它但它能在示例运行时让你直观看到布局与下载机制的交互过程。TextLayoutImageSource.h/.cpp用 DirectX API 实现 XAMLSurfaceImageSource负责渲染文本布局。应用工程的关键文件Scenario_Document1.*、Scenario_Document2.*、Scenario_Document3.*三个场景页面各自用一种不同的可下载字体呈现文本布局并在字体下载后更新布局。DocumentPresenter.h/.cpp三个文档场景的高层实现。它创建文本布局与用于呈现的SurfaceImageSource创建与 DirectWrite 下载机制交互的FontDownloadListener发起远程字体下载并在字体下载完成及其他相关事件发生时更新布局与图像源。Scenario_SampleOverview.*示例使用方法说明。Scenario_CloudFontOverview.*可下载字体机制的背景知识说明。其余文件为 UWP 示例应用通用的样板代码。核心实现一TextLayout——创建布局并把下载请求入队TextLayout.h 中TextLayout类持有IDWriteFactory3与IDWriteTextLayout3两个核心 COM 对象。构造流程见 TextLayout.cppTextLayout::TextLayout(Platform::String^ text, Platform::String^ languageTag, Platform::String^ fontFamily, float fontSize, float width) : m_fontSize(fontSize), m_width(width) { // 1. 创建 IDWriteFactory3共享工厂类型 DX::ThrowIfFailed( DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), m_dwriteFactory) ); // 2. 获取包含可下载字体的系统字体集合 ComPtrIDWriteFontCollection systemFontCollection; ComPtrIDWriteFontCollection1 systemFontCollection1; DX::ThrowIfFailed( m_dwriteFactory-GetSystemFontCollection(true, systemFontCollection1, false) ); DX::ThrowIfFailed(systemFontCollection1.As(systemFontCollection)); // 3. 用该字体集合创建文本格式 ComPtrIDWriteTextFormat textFormat; DX::ThrowIfFailed( m_dwriteFactory-CreateTextFormat( m_fontFamily.c_str(), systemFontCollection.Get(), DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, m_fontSize, m_languageTag.c_str(), textFormat) ); // 4. 创建文本布局宽度由调用方决定最大高度传 0 ComPtrIDWriteTextLayout textLayout; DX::ThrowIfFailed( m_dwriteFactory-CreateTextLayout( m_text.c_str(), static_castUINT32(m_text.length()), textFormat.Get(), m_width, 0.0f, textLayout) ); DX::ThrowIfFailed(textLayout.As(m_textLayout)); ... }这段代码有四个值得注意的要点必须使用IDWriteFactory3的新版GetSystemFontCollection重载。该重载第一个参数传true返回一个启用了可下载字体的系统字体集合。旧的重载出于兼容性考虑只包含本地已安装的字体IDWriteTextLayout默认也使用本地字体集合。要启用可下载字体必须在创建IDWriteTextFormat时指定包含可下载字体的集合或之后调用IDWriteTextLayout::SetFontCollection切换。布局宽度由调用方决定高度由布局推导SurfaceImageSource的尺寸必须在构造时确定因此示例先创建文本布局再用布局尺寸决定图像源尺寸。Width属性 setter 会调用m_textLayout-SetMaxWidth(value)并重新计算高度见 TextLayout.cpp。GetMetrics是触发下载请求入队的开关。UpdateHeight 中的注释说明了这个隐蔽但关键的行为访问文本度量会强制 DirectWrite 完成布局过程中会判断是否需要可下载字体数据并把请求加入字体下载队列真正执行下载由应用控制。InvalidateLayout让下载后的字体生效下载完成后调用m_textLayout-InvalidateLayout()布局会使用本地可用的字体含刚下载并缓存的字体数据重新排版随后重新取高度和字体名见 TextLayout.cpp。核心实现二FontDownloadListener——下载队列与监听器FontDownloadListener.h 中定义了两个类原生Listener类继承IDWriteFontDownloadListener实现DownloadCompleted回调WinRT 包装类FontDownloadListener以静态单例 静态事件的方式向 UI 层暴露能力Initialize、BeginDownload、DownloadCompleted事件。FontDownloadListener.cpp 的初始化逻辑如下FontDownloadListener::FontDownloadListener() { // 获取系统的字体下载队列 DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), dwriteFactory); dwriteFactory-GetFontDownloadQueue(m_downloadQueue); // 创建 IDWriteFontDownloadListener 实例 m_registeredListener new Listener(); // 把监听器注册到队列上AddListener 而非在 BeginDownload 时传入 m_downloadQueue-AddListener(m_registeredListener.Get(), m_token); }这里体现了示例的一个重要设计决策使用全局注册的监听器而不是把监听器作为参数传给BeginDownload。源码注释解释了原因UWP 应用宿主在 XAML 中XAML 框架与你的应用共享同一个字体下载队列并且也会调用BeginDownload。例如你创建了使用可下载字体的布局并调用BeginDownload随后在下载完成前对布局调用SetMaxWidth同一个字体数据的新请求会再次入队但不会导致重复下载XAML 定期检查队列发现非空就会自己发起下载而它不会传入你期望的 context。因此只把监听器传给BeginDownload你将只能收到自己发起的下载的回调若队列被多个可独立调用BeginDownload的对象共享应始终AddListener注册全局监听器让所有需要响应的对象都能看到DownloadCompleted事件。BeginDownload实现也做了空队列保护FontDownloadListener.cpp仅当!m_downloadQueue-IsEmpty()时才调用BeginDownload()。DownloadCompleted回调发生在平台工作线程不能把异常留在回调外FontDownloadListener.cppvoid STDMETHODCALLTYPE Listener::DownloadCompleted( _In_ IDWriteFontDownloadQueue* downloadQueue, _In_opt_ IUnknown* context, HRESULT downloadResult) { try { if (SUCCEEDED(downloadResult)) { // 示例未使用 context队列被 XAML 共享时context 不可靠 FontDownloadListener::RaiseDownloadCompleted(); } } catch (...) { return; } }核心实现三FontNameCollector——观察实际使用的字体FontNameCollector.h 是一个IDWriteTextRenderer实现目的纯粹是从文本布局回读实际用到了哪些字体与可下载字体机制本身无关大多数渲染方法都是平凡实现。其关键方法是DrawGlyphRunFontNameCollector.cpp从glyphRun-fontFace拿到IDWriteFontFace3再通过GetFamilyNames获取本地化字体族名用FindLocaleName(Len-US, ...)定位英文名称找不到时回退到索引 0最后把名字插入去重集合m_uniqueFontNames。TextLayout::GetFontNamesTextLayout.cpp则通过调用m_textLayout-Draw(NULL, m_fontNameCollector.Get(), 0, 0)触发渲染回调从而拿到字体名列表。Draw调用返回前回调即已完成所以这个假渲染过程是同步的。示例应用会把这个结果与请求的字体名做比较若两者一致状态栏显示正常状态消息否则以错误样式提示当前使用的是回退字体见 DocumentPresenter.cpp。核心实现四TextLayoutImageSource——用 SurfaceImageSource 呈现文本TextLayoutImageSource.h 中的类继承自Windows::UI::Xaml::Media::Imaging::SurfaceImageSource负责把 DirectWrite 文本布局绘制进 XAML 图像控件。设备资源创建流程TextLayoutImageSource.cpp包括D3D11CreateDevice创建 Direct3D 11 设备声明从 9_1 到 11_1 的 feature level 支持并加D3D11_CREATE_DEVICE_BGRA_SUPPORT标志以兼容 Direct2DDebug 构建下若 SDK Layers 可用则加D3D11_CREATE_DEVICE_DEBUG硬件设备创建失败时回退到 WARP 软件设备。通过IDXGIDevice创建 Direct2D 设备与设备上下文SetDpi让上下文以 DIP 为单位工作文本抗锯齿使用灰度模式D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALEWindows Runtime 应用推荐。查询ISurfaceImageSourceNative接口并SetDevice把 DXGI 设备与SurfaceImageSource关联起来。绘制过程BeginDraw/EndDraw/DrawText见 TextLayoutImageSource.cpp遵循SurfaceImageSource的标准用法BeginDraw获取目标 DXGI surface 与 offset创建位图渲染目标用 offset 换算坐标施加裁剪与平移变换DrawText内先填充背景矩形再调用DrawTextLayout绘制整个文本布局EndDraw复原变换并提交。Draw方法还处理了设备丢失场景捕获D2DERR_RECREATE_TARGET、DXGI_ERROR_DEVICE_REMOVED、DXGI_ERROR_DEVICE_RESET后重建设备资源并重绘。另外应用挂起时通过IDXGIDevice3::Trim向显示驱动提示进入空闲状态。已知限制旧设备上可绘制画布尺寸有限。示例将文本量与承载帧的尺寸控制在 200% 缩放下最大维度不超过 2048 像素DirectX feature level 9_1 的最低要求当布局宽度过窄导致高度过大时SurfaceImageSource可能超出旧设备的图形资源而产生裁剪。支持 feature level 9_3 及以上的设备不会出现该问题示例对此按已知问题保留处理见 Scenario_Document1.xaml.cpp。核心实现五DocumentPresenter——端到端编排DocumentPresenter.cpp 把上述组件串成完整流程DocumentPresenter::DocumentPresenter( String^ text, String^ downloadableFontName, String^ languageTag, Color textColor, Color textBackgroundColor, Image^ imageTarget) { // 1. 创建文本布局字号 20默认宽度 300 DIP m_textLayout ref new TextLayout(text, languageTag, m_downloadableFontName, /* fontSize */ 20, /* default width */ 300); // 2. 查询实际使用字体会触发布局从而把远程字体请求加入下载队列 UpdateStatus(); // 3. 初始化下载监听器单例订阅完成事件发起下载 FontDownloadListener::Initialize(); m_downloadCompletedEventToken FontDownloadListener::DownloadCompleted ref new FontDownloadCompletedHandler(this, DocumentPresenter::FontDownloadListener_DownloadCompleted); FontDownloadListener::BeginDownload(); // 4. 响应 DPI 变化与窗口可见性变化SurfaceImageSource 需要重建 m_displayInformation DisplayInformation::GetForCurrentView(); m_dpiChangedEventToken m_displayInformation-DpiChanged ...; m_visibilityChangedEventToken Window::Current-VisibilityChanged ...; }下载完成后的响应链体现了多线程与节流设计DocumentPresenter.cppDownloadCompleted回调运行在工作线程先用Dispatcher-RunAsyncCoreDispatcherPriority::Normal切回 UI 线程UI 线程处理器调用RequestTextLayoutUpdate内部用m_layoutUpdateInProgress标志防止重复调度并以Low 优先级排队更新任务——这样短时间内连续触发的多个DownloadCompleted事件会被合并成一次更新实际执行时调用m_textLayout-InvalidateLayout()让布局用已下载字体重新排版再重建TextLayoutImageSource并更新状态栏显示实际使用的字体。事件注册与反注册是对称的析构函数中移除下载完成、DPI 变化、可见性变化的处理程序DocumentPresenter.cpp。页面导航离开时把 presenter 置空各Scenario_Document*的OnNavigatedFrom避免悬挂引用。三个文档场景不同语言与字体组合三个场景页面展示了可下载字体在多种语言文字下的效果场景文本内容请求字体语言标签Scenario_Document1拉丁文Lorem ipsum 风格Neue Haas Grotesk Text Prohi印地语Scenario_Document2简体中文FangSong仿宋zh-HansScenario_Document3印地语Kokilahi每个场景都在构造函数中用ColorHelper::FromArgb(0xFF, 0xFF, 0xFF, 0xFF)指定白色文字、0xFF303030指定深灰背景并把TextLayoutImage图像控件作为SurfaceImageSource的呈现目标。TextLayoutFrame_SizeChanged处理器把 XAML 的可用宽度ActualWidth减去左右 Padding注意 XAML 的 DIP 是 double 而 DirectWrite 是 float需要强转通过SetLayoutWidth同步给文本布局页面 XAML 中为 Grid 设置了MinWidth避免窄窗口下减去 padding 后出现负宽度。观察与调试状态栏、网络活动与缓存清理观察字体下载过程示例运行时可以打开任务管理器切到性能页观察网络活动每次打开一个文档场景字体下载时都会看到一次短暂的网络活动峰值说明见 Scenario_SampleOverview.xaml。同时应用状态栏会实时显示Fonts actually used: ...用于区分当前使用的是请求字体还是回退字体。注意桌面系统上部分示例字体可能已经存在。如果字体已在本地应用会立刻用该字体显示文档无需下载。重置可下载字体机制ClearDownloadableFontCache.ps1字体数据下载后会缓存在本地且缓存是跨系统、跨应用共享的甚至你的应用首次使用某字体时它可能已被其他应用缓存长时间未使用后缓存会被清理。要重复演示字体未缓存时加载文档的完整场景需要先清空缓存。ClearDownloadableFontCache.ps1 正是为此提供的 PowerShell 脚本它不属于示例工程本身但会随示例发布。其核心逻辑$Identity [Security.Principal.WindowsIdentity]::GetCurrent() $Principal [Security.Principal.WindowsPrincipal]$Identity if (!$Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Warning You must run this script as administrator. Exit } $FontCachePath $env:WinDir\ServiceProfiles\LocalService\AppData\Local\FontCache\Fonts if (Test-Path $FontCachePath) { Get-Service FontCache | Stop-Service Get-ChildItem -Path $FontCachePath -Filter *.bin | Remove-Item Get-Service FontCache | Start-Service }使用步骤示例应用内 Scenario_SampleOverview.xaml 也给出了指引关闭示例应用以管理员身份打开命令行窗口cmd.exe进入示例工程所在目录运行powershell -ExecutionPolicy Bypass -File ClearDownloadableFontCache.ps1重新启动示例应用。脚本要求管理员权限脚本内会校验当前身份是否属于 Administrators 角色否则警告后退出通过停止FontCache服务、删除%WinDir%\ServiceProfiles\LocalService\AppData\Local\FontCache\Fonts目录下的.bin文件、再重启服务来重置字体缓存。这套操作同样适用于调试你自己的应用。关于字体缓存与部分下载的行为细节Scenario_CloudFontOverview.xaml 还补充了几个值得留意的平台行为部分下载部分字体非常大尤其中日韩字体。DirectWrite 只入队足以显示当前内容的字体数据块若后续内容包含之前未出现的字符会再追加请求更多数据块。离线行为无网络连接时布局使用回退字体或先前已下载的数据块。流量提醒按量计费连接上该机制会产生网络数据消耗应谨慎使用避免加重用户流量负担。静态 UI 建议应用静态界面应只使用所有 Windows 10 设备上保证可用的推荐字体相关规范见 MSDN 的《Guidelines for fonts》文档页面 ID hh700394。构建与运行示例系统要求客户端Windows 10服务器Windows Server 2016 Technical Preview手机Windows 10构建步骤若下载的是整个样例集合的 ZIP 包务必解压整个压缩包而不是只解压单个示例文件夹共享依赖位于集合根目录的SharedContent等位置示例清单中extendedZipContent也声明了SharedContent与LICENSE的拷贝规则。启动 Visual Studio选择FileOpenProject/Solution。进入解压后的目录依次打开Samples子目录、本示例子目录、目标语言子目录C双击解决方案文件 DWriteTextLayoutCloudFont.sln。按CtrlShiftB或选择BuildBuild Solution构建。运行步骤仅部署选择BuildDeploy Solution。部署并运行按F5带调试或CtrlF5不带调试启动。涉及的核心 API 与延伸阅读本示例围绕以下 DirectWrite 接口展开理解它们有助于进一步阅读 TextLayout.cpp 与 FontDownloadListener.cpp 的实现IDWriteFactory3::GetSystemFontCollection新增重载返回启用了可下载字体的系统字体集合。IDWriteTextLayout3集成可下载字体的文本布局接口InvalidateLayout用于下载完成后重新排版。IDWriteFontDownloadQueue字体下载队列AddListener、BeginDownload、IsEmpty是示例使用的主要方法。IDWriteFontDownloadListener应用必须实现的监听接口DownloadCompleted回调通知下载结果。字体使用规范Windows 10 静态 UI 推荐字体指南MSDN 文档 ID hh700394示例 Scenario_CloudFontOverview.xaml 中有指引。若要观察更完整的 UWP 示例组织方式可将本示例与其他 Samples 目录下的文本与控件类示例对照阅读本示例的 README.md 也保留了原始的下载与导航说明。小结DWriteTextLayoutCloudFont 示例完整展示了一条可下载字体的端到端链路IDWriteFactory3获取可下载字体集合 →CreateTextFormat/CreateTextLayout创建布局 → 首次排版GetMetrics触发自动把远程字体请求入队 →IDWriteFontDownloadQueue发起下载 →IDWriteFontDownloadListener::DownloadCompleted回调 → 切回 UI 线程并InvalidateLayout用下载字体重新排版 →SurfaceImageSource呈现更新后的布局。三个文档场景覆盖拉丁文、简体中文与印地语配合FontNameCollector的字体使用观察、任务管理器的网络活动监控以及ClearDownloadableFontCache.ps1的缓存重置可以直观验证整个下载机制的行为并可作为你自己应用集成云字体的参考模板。赞分享示例工程【免费下载链接】Windows-universal-samplesAPI samples for the Universal Windows Platform.项目地址https://gitcode.com/gh_mirrors/wi/Windows-universal-samples点击查看免费下载相关推荐从企业设备到个人电脑15分钟解锁被MDM锁定的Mac从企业设备到个人电脑15分钟解锁被MDM锁定的Mac 你是否曾经兴奋地打开一台新买的二手Mac准备开始全新的数字生活却被Remote ManagCLI代码字体渲染革命FreeType与DirectWrite下的JetBrains Mono显示差异深度解析代码字体渲染革命FreeType与DirectWrite下的JetBrains Mono显示差异深度解析 作为开发者你是否曾在不同操作系统间切换时感觉同一开发工具HarfBuzz 字体与字体对象深度解析HarfBuzz 字体与字体对象深度解析 引言 在现代文本渲染系统中字体处理是核心环节之一。HarfBuzz 作为业界领先的文本整形引擎其字体和字体对象的设图形学上一篇终极指南用ProceduralToolkit打造智能游戏关卡生成系统——迷宫与细胞自动机完全教程下一篇Causify development system数据治理的实现方法创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
