颜色字体支持(预览)

注意

一些信息与预发布产品相关,在商业发行之前可能发生实质性修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

本主题介绍颜色字体、DirectWrite 和 Direct2D 中的支持,以及如何在应用中使用它们。 有关介绍性主题,另请参阅 颜色字体支持

将颜色字体与 DirectWrite 和 Direct2D 配合使用

启用颜色的最简单方法是调用 DrawGlyphRunWithColorSupport 方法,而不是 DrawGlyphRun。 Direct2D 通过 ID2D1DeviceContext7 接口公开此方法。 DirectWrite 通过 IDWriteBitmapRenderTarget3 接口公开它。

在较低级别,启用颜色涉及将单色 基字形运行 转换为 颜色字形序列。 然后,必须使用适当的方法呈现每个颜色字形运行,具体取决于其 字形图像格式。 转换由工厂对象的 TranslateColorGlyphRun 方法执行。 以下各节介绍了它和相关 API。

使用 TranslateColorGlyphRun

TranslateColorGlyphRun 工厂方法将单色基字形运行转换为颜色字形运行序列。 有多种表示颜色标志符号的方法,称为字形图像格式,并由DWRITE_GLYPH_IMAGE_FORMATS枚举标识。 调用 TranslateColorGlyphRun 时,可以指定支持的字形图像格式的组合。 TranslateColorGlyphRun 返回一个字形运行枚举器对象(IDWriteColorGlyphRunEnumerator1 接口),该接口用于循环访问由DWRITE_COLOR_GLYPH_RUN1结构表示的颜色字形运行序列。 每个颜色字形运行都具有字形图像格式。 对颜色运行标志符号图像格式进行分支,以确定如何呈现颜色运行。

如果基字形运行没有任何请求的字形图像格式的颜色标志符号,则 TranslateColorGlyphRun 方法将返回 DWRITE_E_NOCOLOR,在这种情况下,应将基本字形运行呈现为单色字形运行。

否则,基本字形运行可能包含不同字形图像格式以及单色字形的混合颜色字形。 对于每个基本字形, TranslateColorGlyphRun 方法确定最符合所请求格式的可用字形图像格式(如果有)。 基本字形运行根据匹配的字形图像格式拆分为子范围。 不支持任何请求格式的字形被视为单色字形。

字形图像格式分为四个常规类别:

  • 单色字形(DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPEDWRITE_GLYPH_IMAGE_FORMATS_CFF
  • 纯色字形层(DWRITE_GLYPH_IMAGE_FORMATS_COLR
  • 字体中嵌入的颜色位图(DWRITE_GLYPH_IMAGE_FORMATS_PNGDWRITE_GLYPH_IMAGE_FORMATS_JPEGDWRITE_GLYPH_IMAGE_FORMATS_TIFFDWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8
  • 以 SVG 格式编码的矢量图形(DWRITE_GLYPH_IMAGE_FORMATS_SVG
  • 矢量图形作为油漆元素树DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE

以下小节描述了这些方法中的每一种方法。

单色字形

DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE和DWRITE_GLYPH_IMAGE_FORMATS_CFF字形图像格式表示单色字形,可以使用 DrawGlyphRun 方法呈现 这两种格式对应于以不同方式表示字体中的字形轮廓。 枚举 DWRITE_COLOR_GLYPH_RUN1 结构可以指定以下两种原因之一的格式:

  • 一个或多个基本字形不支持任何请求的字形图像格式,因此应呈现为单色字形。
  • 字形的最佳匹配字形图像格式是 DWRITE_GLYPH_IMAGE_FORMATS_COLR。 此图像格式的字形通过枚举每个层的指定颜色中的单色运行来呈现,如下一节中所述。

在第一种情况下,应使用应用定义的前台画笔呈现枚举标志符号运行。 这通过将DWRITE_COLOR_GLYPH_RUN结构的 paletteIndex 成员设置为DWRITE_NO_PALETTE_INDEX(0xFFFF)来指示。 如果 paletteIndex 成员具有任何其他值,则应以指定颜色呈现文本。

纯色标志符号层

DWRITE_GLYPH_IMAGE_FORMATS_COLR值指定支持 OpenType COLR 表版本 0 的颜色格式。 在该格式中,通过绘制多个单色字形来呈现颜色标志符号,每个字形都以指定颜色呈现。

用于此图像格式的调用模式不同于所有其他字形图像格式。 这是因为最终呈现的内容只是一系列普通的单色字形运行 , 只是与最初指定的字形 ID 和不同的颜色。 由于枚举的颜色字形运行呈现为普通单色字形运行,因此其字形图像格式为 DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPEDWRITE_GLYPH_IMAGE_FORMATS_CFF

例如,假设基本字形运行指定笑脸字形。 如果我们要使用 DrawGlyphRun 呈现基本字形运行,则视觉结果将是笑脸的单色表示形式(基本字形 ID)。 字体中的 COLR 表将此基本字形 ID 映射到层记录数组,其中每个表指定替换字形 ID 和索引到字体定义的调色板。 TranslateColorGlyphRun 返回的 IDWriteColorGlyphRunEnumerator 对象枚举每个层记录的一个颜色标志符号运行。 每个颜色字形运行都有一个位置,一个字形 ID(层的替换字形 ID)和一种颜色(派生自调色板条目索引)。 绘制枚举的颜色标志符号以指定坐标处的指定颜色运行,以生成所需的视觉结果。 在笑脸示例中,它可能包含以黄色呈现的填充圆字形,后跟以黑色呈现的眼睛和嘴字形。

字体中嵌入的颜色位图

DWRITE_GLYPH_IMAGE_FORMATS枚举包括多个位图格式的值:PNG、JPEG、TIFF 和预乘 BGRA32 格式。 字体可以通过将位图嵌入字体中的任何格式来表示颜色标志符号。 字体可能包括位图或多个大小,以支持不同的字号和 DPI 比例;尽管这在文件大小方面成本明显。

如果你的应用请求位图图像格式,则 TranslateColorGlyphRun 检查字体是否具有指定格式的指定输入字形的颜色位图。 如果这样做,则枚举的颜色标志符号运行指定基本字形 ID 和匹配的图像格式。 在这种情况下,没有替换字形 ID,就像DWRITE_GLYPH_IMAGE_FORMATS_COLR一样。 但是,如果不同的输入字形具有不同图像格式的颜色位图,则基本字形运行可能仍拆分为多个颜色运行;或者,如果某些字形具有颜色表示形式,而其他标志符号则仅单色。

使用 Direct2D 的应用可以通过调用 ID2DDeviceContext3::D rawColorBitmapGlyphRun 方法呈现枚举的颜色标志符号以这些格式运行。

较低级别的应用可以调用 IDWriteFontFace3::GetGlyphImageData 以获取返回的图像格式中每个字形的原始位图数据。 然后,应用负责解码和呈现位图。 Direct2D 的 DrawColorBitmapGlyphRun 的内部实现调用 GetGlyphImageData 以获取每个字形的位图。

以 SVG 格式编码的矢量图形

DWRITE_GLYPH_IMAGE_FORMATS_SVG值指定字形图像格式,其中颜色字形表示为以缩放矢量图形(SVG)格式嵌入字体的矢量图形。 嵌入式 SVG 可能以 gzip 压缩方式存储。

与 SVG 一起使用的调用模式与位图格式类似,与DWRITE_GLYPH_IMAGE_FORMATS_COLR调用模式不同。 如果应用请求 SVG 格式,并且指定的字形 ID 具有 SVG 表示形式,则枚举的颜色标志符号运行将指定基本字形 ID 和 SVG 图像格式。

使用 Direct2D 的应用可以通过调用 ID2DDeviceContext3::D rawSvgGlyphRun 方法以此格式呈现枚举的颜色标志符号运行。

较低级别的应用可以调用 IDWriteFontFace3::GetGlyphImageData 以获取每个字形的原始 SVG 数据。 然后,该应用负责分析和呈现 SVG 本身。 Direct2D 的 DrawSvgGlyphRun 的内部实现调用 GetGlyphImageData 以获取每个字形的 SVG 数据。

矢量图形作为“画图”元素树

在 OpenType COLR 表的版本 1 中,颜色标志符号由油漆元素表示。 几何图形(表示为字形)、转换、纯色填充和渐变填充以及合成都有不同类型的油漆元素。 DWRITE_GLYPH_IMAGE_FORMATS_COLR格式仅支持对彼此的纯色字形进行分层。 因此,需要不同的字形图像格式来支持 COLR 版本 1 及更高版本, 这DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE。 使用此字形图像格式时,应用还必须指定它支持的最大 油漆功能级别DWRITE_PAINT_FEATURE_LEVEL)。 这样,就可以支持将来版本的 COLR 表,而无需定义其他字形图像格式。

与此字形图像格式一起使用的调用模式类似于与 SVG 一起使用。 如果应用请求 COLR 绘制格式,并且指定的字形 ID 具有 COLR 表示形式,则枚举的颜色标志符号运行指定基本字形 ID 和 COLR 绘制图像格式。

使用 Direct2D 的应用可以通过调用 ID2DDeviceContext7::D rawPaintGlyphRun 方法以此格式呈现枚举的颜色标志符号运行。 使用 DirectWrite 的位图呈现目标 API 的应用可以调用 IDWriteBitmapRenderTarget3::D rawPaintGlyphRun

较低级别的应用可以使用下一节中所述的颜色油漆 API 获取每个字形的油漆元素树,并呈现它。 Direct2D 的 Direct2D 和 DirectWrite 的 DrawPaintGlyphRun 实现在内部使用这些较低级别的 API。

颜色油漆 API

DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE图像格式的字形表示为画图元素。 OpenType COLR 表规格描述了绘制记录的有向无环图,但特定字形的子图始终是树。 应用可以使用 油漆读取器 对象(IDWritePaintReader 接口)读取字形的油漆元素树。

每个画图元素都有一个 油漆类型DWRITE_PAINT_TYPE 枚举)。 图形元素(表示为字形)、转换和纯色填充和渐变填充等图形元素有不同的油漆类型。 画图读取器方法通过填充 DWRITE_PAINT_ELEMENT 结构(包括油漆类型和联合)返回有关油漆元素的信息。

可以在 COLR 规范的未来版本中添加其他油漆类型。 为此,应用必须指定在创建油漆读取器对象时或调用 TranslateColorGlyphRun 时支持的最大油漆功能级别DWRITE_PAINT_FEATURE_LEVEL)。 油漆读取器方法仅返回指定特征级别支持的类型的油漆元素。 此外,采用类型 为 DWRITE_PAINT_ELEMENT 的输出参数的方法也采用大小参数,因为当定义了其他油漆类型时,结构大小可能会更改。

呈现颜色标志符号的算法是递归呈现油漆元素,每个元素都根据其类型。 定义了以下油漆类型:

画图类型 呈现操作
DWRITE_PAINT_TYPE_NONE 无操作
DWRITE_PAINT_TYPE_LAYERS 按自下而上的顺序呈现子绘制元素。
DWRITE_PAINT_TYPE_SOLID_GLYPH 用指定颜色填充指定的字形形状。
DWRITE_PAINT_TYPE_SOLID 使用指定颜色填充当前剪辑。
DWRITE_PAINT_TYPE_LINEAR_GRADIENT 使用指定的渐变填充当前剪辑。
DWRITE_PAINT_TYPE_RADIAL_GRADIENT 使用指定的渐变填充当前剪辑。
DWRITE_PAINT_TYPE_SWEEP_GRADIENT 使用指定的渐变填充当前剪辑。
DWRITE_PAINT_TYPE_GLYPH 使用子画元素填充指定的字形形状。
DWRITE_PAINT_TYPE_COLOR_GLYPH 呈现子绘制元素。
DWRITE_PAINT_TYPE_TRANSFORM 使用指定的转换呈现子画元素。
DWRITE_PAINT_TYPE_COMPOSITE 呈现两个子绘制元素,并使用指定的复合模式编写它们。

有关油漆类型的详细信息,请参阅 OpenType COLR 表 规范。 DirectWrite API 中的某些油漆类型对应于 COLR 表中的多种绘制格式。 这是因为 API 隐藏了一些复杂性,例如某些画图元素在变量与非变量字体中编码方式的差异。

代码示例

下面的一些代码示例使用Windows 运行时C++模板库(WRL)中的类型(如 ComPtr)。 C++/WinRT 是Microsoft建议替换 WRL。

其他代码示例使用 Windows 实现库(WIL)。 安装 WIL 的一种便捷方法是转到 Visual Studio,单击“项目”>“管理 NuGet 包...”>“浏览”,在搜索框中键入或粘贴 Microsoft.Windows.ImplementationLibrary,选择搜索结果中的项,然后单击“安装”以安装该项目的包。

使用 Direct2D 呈现颜色标志符号运行

本部分演示了一个示例 TextRenderer::D rawGlyphRun 方法,该方法实现 IDWriteTextRenderer 接口的 DrawGlyphRun 方法。 IDWriteTextRenderer 回调接口由 IDWriteTextLayout::D raw 用来呈现字形运行、下划线等。 使用回调接口可使文本布局 API 与特定图形引擎分离。 例如,可以通过 Direct2D 或 IDWriteBitmapRenderTarget 或某种其他方式实现 IDWriteTextRenderer。 此示例使用 Direct2D。

该示例仅 包含 DrawGlyphRun 方法。 为简洁起见,省略 TextRenderer 类的其余部分,但该示例假定该类实现 IDWriteTextRenderer 接口,并且具有以下成员变量:

wil::com_ptr<IDWriteFactory4> m_dwriteFactory;
wil::com_ptr<ID2D1DeviceContext4> m_deviceContext;
wil::com_ptr<ID2D1Brush> m_foregroundBrush;

示例方法以两种方式之一呈现颜色标志符号。 最简单的方法是调用 ID2D1DeviceContext7::D rawGlyphRunWithColorSupport。 但是,如果 Direct2D 的可用版本未实现 ID2D1DeviceContext7 接口,则此示例还包括回退代码路径。 回退代码路径使用 TranslateColorGlyphRun

// Implementation of IDWriteTextRenderer::DrawGlyphRun.
HRESULT STDMETHODCALLTYPE TextRenderer::DrawGlyphRun(
    _In_opt_ void* clientDrawingContext,
    FLOAT baselineOriginX,
    FLOAT baselineOriginY,
    DWRITE_MEASURING_MODE measuringMode,
    _In_ DWRITE_GLYPH_RUN const* glyphRun,
    _In_ DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
    _In_opt_ IUnknown* clientDrawingEffect
) noexcept
{
    D2D_POINT_2F baselineOrigin{ baselineOriginX, baselineOriginY };

    // If a brush is specified via the clientDrawingEffect parameter, then use it as
    // the foreground brush for this glyph run. Otherwise, use the brush specified
    // by the m_foregroundBrush member.
    wil::com_ptr<ID2D1Brush> foregroundBrush;
    if (clientDrawingEffect != nullptr)
    {
        foregroundBrush = wil::try_com_query<ID2D1Brush>(clientDrawingEffect);
    }
    if (foregroundBrush == nullptr)
    {
        foregroundBrush = m_foregroundBrush;
    }

    // If available, use the ID2D1DeviceContext7 interface to draw
    // the glyph run with color the "easy" way.
    if (auto deviceContext7 = m_deviceContext.try_query<ID2D1DeviceContext7>())
    {
        // Note: Direct2D drawing methods return void. If a drawing
        // operation fails, then an error is returned by EndDraw.
        deviceContext7->DrawGlyphRunWithColorSupport(
            baselineOrigin,
            glyphRun,
            foregroundBrush.get(),
            measuringMode,
            /*colorPaletteIndex*/ 0
        );
        return S_OK;
    }

    // If we fall through to here, then we're using an older version of
    // Direct2D that doesn't implement the ID2D1DeviceContext7 interface.
    // We'll need to call TranslateColorGlyphRun. First determine the
    // world to device transform.
    D2D_MATRIX_3X2_F transform;
    m_deviceContext->GetTransform(&transform);
    float dpiX, dpiY;
    m_deviceContext->GetDpi(&dpiX, &dpiY);
    transform = transform * D2D1::Matrix3x2F::Scale(dpiX, dpiY);

    // Combination of image formats we support. Do *not* include
    // DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE, because this code
    // path is for an older version of Direct2D which doesn't support
    // the ID2D1DeviceContext7 interface.
    constexpr DWRITE_GLYPH_IMAGE_FORMATS desiredGlyphImageFormats =
        DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE |
        DWRITE_GLYPH_IMAGE_FORMATS_CFF |
        DWRITE_GLYPH_IMAGE_FORMATS_COLR |
        DWRITE_GLYPH_IMAGE_FORMATS_SVG |
        DWRITE_GLYPH_IMAGE_FORMATS_PNG |
        DWRITE_GLYPH_IMAGE_FORMATS_JPEG |
        DWRITE_GLYPH_IMAGE_FORMATS_TIFF |
        DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8;

    // Perform color translation.
    wil::com_ptr<IDWriteColorGlyphRunEnumerator1> colorRunEnumerator;
    HRESULT hr = m_dwriteFactory->TranslateColorGlyphRun(
        baselineOrigin,
        glyphRun,
        glyphRunDescription,
        desiredGlyphImageFormats,
        measuringMode,
        reinterpret_cast<DWRITE_MATRIX const*>(&transform),
        /*colorPaletteIndex*/ 0,
        /*out*/ & colorRunEnumerator
    );

    // Handle DWRITE_E_NOCOLOR, which is returned if the base glyph run doesn't
    // have any color glyphs in the desired glyph image formats. In that case,
    // just render the base glyph run as a monochrome glyph run.
    if (hr == DWRITE_E_NOCOLOR)
    {
        // Note: Direct2D drawing methods return void. If a drawing
        // operation fails, then an error is returned by EndDraw.
        m_deviceContext->DrawGlyphRun(
            baselineOrigin,
            glyphRun,
            foregroundBrush.get(),
            measuringMode
        );
        return S_OK;
    }

    // Any other failure HRESULT from TranslateColorGlyphRun is an error.
    RETURN_IF_FAILED(hr);

    // Solid color brush to be lazily created if needed.
    wil::com_ptr<ID2D1SolidColorBrush> solidBrush;

    // Use the returned enumerator object to iterate over the color glyph runs.
    for (;;)
    {
        // Advanced to the first or next run.
        BOOL haveRun;
        RETURN_IF_FAILED(colorRunEnumerator->MoveNext(&haveRun));
        if (!haveRun)
            break;

        // Retrieve a pointer to the color glyph run structure.
        DWRITE_COLOR_GLYPH_RUN1 const* colorGlyphRun;
        RETURN_IF_FAILED(colorRunEnumerator->GetCurrentRun(&colorGlyphRun));

        // Determine the brush to use for this color glyph run.
        wil::com_ptr<ID2D1Brush> runBrush;
        if (colorGlyphRun->paletteIndex == DWRITE_NO_PALETTE_INDEX)
        {
            // Special palette index meaning use the text foreground brush.
            runBrush = foregroundBrush;
        }
        else
        {
            // Use the specified color from the font's color palette.
            // Lazily create the solid color brush, or set its color if already created.
            if (solidBrush == nullptr)
            {
                RETURN_IF_FAILED(m_deviceContext->CreateSolidColorBrush(
                    colorGlyphRun->runColor,
                    &solidBrush
                    ));
            }
            else
            {
                solidBrush->SetColor(colorGlyphRun->runColor);
            }

            // Use the solid color brush as the current run's brush.
            runBrush = solidBrush;
        }

        // Branch depending on the run's glyph image format.
        switch (colorGlyphRun->glyphImageFormat)
        {
        case DWRITE_GLYPH_IMAGE_FORMATS_NONE:
            // do nothing
            break;

        case DWRITE_GLYPH_IMAGE_FORMATS_PNG:
        case DWRITE_GLYPH_IMAGE_FORMATS_JPEG:
        case DWRITE_GLYPH_IMAGE_FORMATS_TIFF:
        case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8:
            // Note: Direct2D drawing methods return void. If a drawing
            // operation fails, then an error is returned by EndDraw.
            m_deviceContext->DrawColorBitmapGlyphRun(
                colorGlyphRun->glyphImageFormat,
                baselineOrigin,
                &colorGlyphRun->glyphRun,
                colorGlyphRun->measuringMode,
                D2D1_COLOR_BITMAP_GLYPH_SNAP_OPTION_DEFAULT
            );
            break;

        case DWRITE_GLYPH_IMAGE_FORMATS_SVG:
            // Note: Direct2D drawing methods return void. If a drawing
            // operation fails, then an error is returned by EndDraw.
            m_deviceContext->DrawSvgGlyphRun(
                baselineOrigin,
                &colorGlyphRun->glyphRun,
                runBrush.get(),
                /*SVG style*/ nullptr,
                /*colorPaletteIndex*/ 0,
                colorGlyphRun->measuringMode
            );
            break;

        default:
            // Treat any other format as a monochrome glyph run.
            // This is used for monochrome glyphs, or for each layer
            // of a glyph in DWRITE_GLYPH_IMAGE_FORMATS_COLR.
            m_deviceContext->DrawGlyphRun(
                baselineOrigin,
                &colorGlyphRun->glyphRun,
                colorGlyphRun->glyphRunDescription,
                runBrush.get(),
                colorGlyphRun->measuringMode
            );
            break;
        }
    }
    return S_OK;
}

使用颜色画图 API

DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE格式的颜色标志符号表示为图形元素的可视化树,称为画图元素 本节中的示例演示如何使用 IDWritePaintReader 接口遍历标志符号的油漆元素树。

本节中的 DumpPaintTree 示例函数不呈现颜色标志符号,而是输出其画树的文本表示形式。 下面是输出的示例:

  - glyphIndex: 35
  - clipBox: { 0, -1, 0.5, -0.5 }
  - attributes: DWRITE_PAINT_ATTRIBUTES_USES_PALETTE
  - paintTree:
    DWRITE_PAINT_TYPE_COMPOSITE:
      - mode: DWRITE_COLOR_COMPOSITE_SRC_OVER
      - children (source, destination):
        DWRITE_PAINT_TYPE_SOLID_GLYPH:
          - glyphIndex: 84
          - color: { 0.501961, 0.501961, 0.501961, 0.400024 }
        DWRITE_PAINT_TYPE_COLOR_GLYPH:
          - glyphIndex: 88
          - clipBox: { 0.1, -0.9, 0.9, -0.1 }
          - child:
            DWRITE_PAINT_TYPE_COLOR_GLYPH:
              - glyphIndex: 20
              - clipBox: { 0, -1, 1, -0 }
              - child:
                DWRITE_PAINT_TYPE_GLYPH:
                  - glyphIndex: 82
                  - child:
                    DWRITE_PAINT_TYPE_RADIAL_GRADIENT:
                      - center0: (0.166, -0.768)
                      - radius0: 0
                      - center1: (0.166, -0.768)
                      - radius1: 0.256
                      - extendMode: D2D1_EXTEND_MODE_MIRROR (2)
                      - gradientStops:
                        D2D1_GRADIENT_STOP:
                          - position: 0
                          - color: { 0, 0.501961, 0, 1 }
                        D2D1_GRADIENT_STOP:
                          - position: 0.5
                          - color: { 1, 1, 1, 1 }
                        D2D1_GRADIENT_STOP:
                          - position: 1
                          - color: { 1, 0, 0, 1 }

为方便起见,此示例定义了 与关联的流输出运算符的缩进PropName 帮助程序类型。 它还为各种 API 类型定义流输出运算符。 本部分末尾显示了这些帮助程序类型和运算符。

DumpPaintTree 函数创建 IDWritePaintReader 对象,设置当前字形,输出字形属性,然后调用递归 DumpPaintElement 函数来输出画图元素树。

void DumpPaintTree(std::ostream& out, IDWriteFontFace7* fontFace, uint32_t glyphIndex)
{
    wil::com_ptr<IDWritePaintReader> paintReader;
    THROW_IF_FAILED(fontFace->CreatePaintReader(
        DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE,
        DWRITE_PAINT_FEATURE_LEVEL_COLR_V1,
        &paintReader
    ));

    DWRITE_PAINT_ELEMENT paintElement;
    D2D_RECT_F clipBox;
    DWRITE_PAINT_ATTRIBUTES attributes;
    THROW_IF_FAILED(paintReader->SetCurrentGlyph(glyphIndex, &paintElement, &clipBox, &attributes));

    out << PropName{ "glyphIndex" } << glyphIndex << '\n'
        << PropName{ "clipBox" } << clipBox << '\n'
        << PropName{ "attributes" } << attributes << '\n'
        << PropName{ "paintTree" } << '\n';
    DumpPaintElement(out, Indent{ 1 }, paintReader.get(), paintElement);
}

递归 DumpPaintElement 函数输出指定油漆元素及其子元素的说明。 DWRITE_PAINT_ELEMENT结构是带标记的联合。 DumpPaintElement 在其 paintType 成员上分支,以确定要写入的内容。

void DumpPaintElement(std::ostream& out, Indent indent, IDWritePaintReader* reader, DWRITE_PAINT_ELEMENT& element)
{
    // Helper to write gradient information, for gradient paint types.
    auto WriteGradient = [&](D2D1_EXTEND_MODE extendMode, uint32_t gradientStopCount)
    {
        out << indent << PropName{ "extendMode" } << extendMode << '\n';
        out << indent << PropName{ "gradientStops" } << '\n';
        DumpGradientStops(out, indent + 1, reader, gradientStopCount);
    };

    // Helper to recursively call DumpPaintElement for a child paint element.
    auto Recurse = [&]()
    {
        DumpPaintElement(out, indent + 1, reader, element);
    };

    // Helper to write the specified number of children.
    // The number of children is specified by the caller, because it depends on
    // the paint type. See the documentation for the DWRITE_PAINT_ELEMENT structure
    // for more information.
    auto WriteChildren = [&](uint32_t childCount)
    {
        if (childCount != 0)
        {
            HR(reader->MoveToFirstChild(&element));
            Recurse();

            for (uint32_t i = 1; i < childCount; i++)
            {
                HR(reader->MoveToNextSibling(&element));
                Recurse();
            }

            HR(reader->MoveToParent());
        }
    };

    // Write information about the paint element, depending on its type.
    switch (element.paintType)
    {
    case DWRITE_PAINT_TYPE_NONE:
        out << indent << "DWRITE_PAINT_TYPE_NONE\n";
        break;

    case DWRITE_PAINT_TYPE_LAYERS:
    {
        out << indent << "DWRITE_PAINT_TYPE_LAYERS:\n";
        auto const& paint = element.paint.layers;
        // Write the children.
        // A layers paint element has a variable number of children.
        WriteChildren(paint.childCount);
        break;
    }

    case DWRITE_PAINT_TYPE_SOLID_GLYPH:
    {
        auto const& paint = element.paint.solidGlyph;
        // Write the properties.
        // A solid glyph paint element has no children.
        out << indent << "DWRITE_PAINT_TYPE_SOLID_GLYPH:\n"
            << indent << PropName{ "glyphIndex" } << paint.glyphIndex << '\n'
            << indent << PropName{ "color" } << paint.color.value << '\n';
        break;
    }

    case DWRITE_PAINT_TYPE_SOLID:
    {
        auto const& paint = element.paint.solid;
        // Write the properties.
        // A solid paint element has no children.
        out << indent << "DWRITE_PAINT_TYPE_SOLID:\n"
            << indent << PropName{ "color" } << paint.value << '\n';
        break;
    }

    case DWRITE_PAINT_TYPE_LINEAR_GRADIENT:
    {
        auto const& paint = element.paint.linearGradient;
        // Write the properties, including gradient stops.
        // A linear gradient paint element has no children.
        out << indent << "DWRITE_PAINT_TYPE_LINEAR_GRADIENT:\n"
            << indent << PropName{ "p0" } << D2D_POINT_2F{ paint.x0, paint.y0 } << '\n'
            << indent << PropName{ "p1" } << D2D_POINT_2F{ paint.x1, paint.y1 } << '\n'
            << indent << PropName{ "p2" } << D2D_POINT_2F{ paint.x2, paint.y2 } << '\n';
        WriteGradient(static_cast<D2D1_EXTEND_MODE>(paint.extendMode), paint.gradientStopCount);
        break;
    }

    case DWRITE_PAINT_TYPE_RADIAL_GRADIENT:
    {
        auto const& paint = element.paint.radialGradient;
        // Write the properties, including gradient stops.
        // A radial gradient paint element has no children.
        out << indent << "DWRITE_PAINT_TYPE_RADIAL_GRADIENT:\n"
            << indent << PropName{ "center0" } << D2D_POINT_2F{ paint.x0, paint.y0 } << '\n'
            << indent << PropName{ "radius0" } << paint.radius0 << '\n'
            << indent << PropName{ "center1" } << D2D_POINT_2F{ paint.x1, paint.y1 } << '\n'
            << indent << PropName{ "radius1" } << paint.radius1 << '\n';
        WriteGradient(static_cast<D2D1_EXTEND_MODE>(paint.extendMode), paint.gradientStopCount);
        break;
    }

    case DWRITE_PAINT_TYPE_SWEEP_GRADIENT:
    {
        auto const& paint = element.paint.sweepGradient;
        // Write the properties, including gradient stops.
        // A sweep gradient paint element has no children.
        out << indent << "DWRITE_PAINT_TYPE_SWEEP_GRADIENT:\n"
            << indent << PropName{ "center" } << D2D_POINT_2F{ paint.centerX, paint.centerY } << '\n'
            << indent << PropName{ "startAngle" } << paint.startAngle << '\n'
            << indent << PropName{ "endAngle" } << paint.endAngle << '\n';
        WriteGradient(static_cast<D2D1_EXTEND_MODE>(paint.extendMode), paint.gradientStopCount);
        break;
    }

    case DWRITE_PAINT_TYPE_GLYPH:
    {
        auto const& paint = element.paint.glyph;
        // Write the properties and the child element.
        // A glyph paint element always has one child, which represents the fill
        // for the glyph shape.
        out << indent << "DWRITE_PAINT_TYPE_GLYPH:\n"
            << indent << PropName{ "glyphIndex" } << paint.glyphIndex << '\n'
            << indent << PropName{ "child" } << '\n';
        WriteChildren(1);
        break;
    }

    case DWRITE_PAINT_TYPE_COLOR_GLYPH:
    {
        auto const& paint = element.paint.colorGlyph;
        // Write the properties and the child element.
        // A color glyph paint element always has one child, which is the root
        // of the paint tree for the glyph specified by glyphIndex.
        out << indent << "DWRITE_PAINT_TYPE_COLOR_GLYPH:\n"
            << indent << PropName{ "glyphIndex" } << paint.glyphIndex << '\n'
            << indent << PropName{ "clipBox" } << paint.clipBox << '\n'
            << indent << PropName{ "child" } << '\n';
        WriteChildren(1);
        break;
    }

    case DWRITE_PAINT_TYPE_TRANSFORM:
    {
        DWRITE_MATRIX const& paint = element.paint.transform;
        // Write the properties and the child element.
        // A transform paint element always has one child, which represents the
        // transformed content.
        out << indent << "DWRITE_PAINT_TYPE_TRANSFORM:\n"
            << indent << PropName{ "transform" } << paint << '\n'
            << indent << PropName{ "child" } << '\n';
        WriteChildren(1);
        break;
    }

    case DWRITE_PAINT_TYPE_COMPOSITE:
    {
        auto const& paint = element.paint.composite;
        // Write the properties and the child elements.
        // A composite paint element always has two children, which represent
        // the source and destination of the composite operation.
        out << indent << "DWRITE_PAINT_TYPE_COMPOSITE:\n"
            << indent << PropName{ "mode" } << paint.mode << '\n'
            << indent << PropName{ "children (source, destination)" } << '\n';
        WriteChildren(2);
        break;
    }

    default:
        out << indent << "Unknown paint type: " << element.paintType << '\n';
        break;
    }
}

DumpPaintElement 函数调用以下 DumpGradientStops 函数来输出画图元素的渐变停止点数组:

void DumpGradientStops(std::ostream& out, Indent indent, IDWritePaintReader* reader, uint32_t gradientStopCount)
{
    std::vector<D2D1_GRADIENT_STOP> stops;
    stops.resize(gradientStopCount);
    HR(reader->GetGradientStops(0, gradientStopCount, /*out*/ stops.data()));

    for (auto& stop : stops)
    {
        out << indent << "D2D1_GRADIENT_STOP:\n"
            << indent << PropName{ "position" } << stop.position << '\n'
            << indent << PropName{ "color" } << stop.color << '\n';
    }
}

缩进帮助程序类型表示缩进级别。 其关联的流运算符为每个缩进级别输出四个空格:

struct Indent { int value; };
std::ostream& operator<<(std::ostream& out, Indent const& indent)
{
    for (int i = 0; i < indent.value; i++)
    {
        out << "    ";
    }
    return out;
}
Indent operator+(Indent lhs, int rhs)
{
    return Indent{ lhs.value + rhs };
}

PropName 帮助程序类型表示属性名称:

struct PropName { char const* name; };
std::ostream& operator<<(std::ostream& out, PropName const& value)
{
    return out << "  - " << value.name << ": ";
}

为方便起见,此示例使用以输出运算符:

std::ostream& operator<<(std::ostream& out, DWRITE_COLOR_F const& value)
{
    return out << "{ " << value.r << ", " << value.g << ", " << value.b << ", " << value.a << " }";
}

std::ostream& operator<<(std::ostream& out, D2D_POINT_2F const& value)
{
    return out << '(' << value.x << ", " << value.y << ')';
}

std::ostream& operator<<(std::ostream& out, D2D_RECT_F const& value)
{
    return out << "{ "
        << value.left << ", "
        << value.top << ", "
        << value.right << ", "
        << value.bottom
        << " }";
}

std::ostream& operator<<(std::ostream& out, DWRITE_MATRIX const& value)
{
    return out << "{ "
        << value.m11 << ", "
        << value.m12 << ", "
        << value.m21 << ", "
        << value.m22 << ", "
        << value.dx << ", "
        << value.dy << " }";
}

std::ostream& operator<<(std::ostream& out, D2D1_EXTEND_MODE value)
{
    switch (value)
    {
    case D2D1_EXTEND_MODE_CLAMP: return out << "D2D1_EXTEND_MODE_CLAMP (0)";
    case D2D1_EXTEND_MODE_WRAP: return out << "D2D1_EXTEND_MODE_WRAP (1)";
    case D2D1_EXTEND_MODE_MIRROR: return out << "D2D1_EXTEND_MODE_MIRROR (2)";
    default: return out << (int)value;
    }
}

std::ostream& operator<<(std::ostream& out, DWRITE_PAINT_ATTRIBUTES value)
{
    switch (value)
    {
    case DWRITE_PAINT_ATTRIBUTES_USES_PALETTE:
        return out << "DWRITE_PAINT_ATTRIBUTES_USES_PALETTE";

    case DWRITE_PAINT_ATTRIBUTES_USES_TEXT_COLOR:
        return out << "DWRITE_PAINT_ATTRIBUTES_USES_TEXT_COLOR";

    case DWRITE_PAINT_ATTRIBUTES_USES_PALETTE | DWRITE_PAINT_ATTRIBUTES_USES_TEXT_COLOR:
        return out << "DWRITE_PAINT_ATTRIBUTES_USES_PALETTE | DWRITE_PAINT_ATTRIBUTES_USES_TEXT_COLOR";

    default:
        return out << (int)value;
    }
}

std::ostream& operator<<(std::ostream& out, DWRITE_COLOR_COMPOSITE_MODE value)
{
    switch (value)
    {
    case DWRITE_COLOR_COMPOSITE_CLEAR: return out << "DWRITE_COLOR_COMPOSITE_CLEAR";
    case DWRITE_COLOR_COMPOSITE_SRC: return out << "DWRITE_COLOR_COMPOSITE_SRC";
    case DWRITE_COLOR_COMPOSITE_DEST: return out << "DWRITE_COLOR_COMPOSITE_DEST";
    case DWRITE_COLOR_COMPOSITE_SRC_OVER: return out << "DWRITE_COLOR_COMPOSITE_SRC_OVER";
    case DWRITE_COLOR_COMPOSITE_DEST_OVER: return out << "DWRITE_COLOR_COMPOSITE_DEST_OVER";
    case DWRITE_COLOR_COMPOSITE_SRC_IN: return out << "DWRITE_COLOR_COMPOSITE_SRC_IN";
    case DWRITE_COLOR_COMPOSITE_DEST_IN: return out << "DWRITE_COLOR_COMPOSITE_DEST_IN";
    case DWRITE_COLOR_COMPOSITE_SRC_OUT: return out << "DWRITE_COLOR_COMPOSITE_SRC_OUT";
    case DWRITE_COLOR_COMPOSITE_DEST_OUT: return out << "DWRITE_COLOR_COMPOSITE_DEST_OUT";
    case DWRITE_COLOR_COMPOSITE_SRC_ATOP: return out << "DWRITE_COLOR_COMPOSITE_SRC_ATOP";
    case DWRITE_COLOR_COMPOSITE_DEST_ATOP: return out << "DWRITE_COLOR_COMPOSITE_DEST_ATOP";
    case DWRITE_COLOR_COMPOSITE_XOR: return out << "DWRITE_COLOR_COMPOSITE_XOR";
    case DWRITE_COLOR_COMPOSITE_PLUS: return out << "DWRITE_COLOR_COMPOSITE_PLUS";

    case DWRITE_COLOR_COMPOSITE_SCREEN: return out << "DWRITE_COLOR_COMPOSITE_SCREEN";
    case DWRITE_COLOR_COMPOSITE_OVERLAY: return out << "DWRITE_COLOR_COMPOSITE_OVERLAY";
    case DWRITE_COLOR_COMPOSITE_DARKEN: return out << "DWRITE_COLOR_COMPOSITE_DARKEN";
    case DWRITE_COLOR_COMPOSITE_LIGHTEN: return out << "DWRITE_COLOR_COMPOSITE_LIGHTEN";
    case DWRITE_COLOR_COMPOSITE_COLOR_DODGE: return out << "DWRITE_COLOR_COMPOSITE_COLOR_DODGE";
    case DWRITE_COLOR_COMPOSITE_COLOR_BURN: return out << "DWRITE_COLOR_COMPOSITE_COLOR_BURN";
    case DWRITE_COLOR_COMPOSITE_HARD_LIGHT: return out << "DWRITE_COLOR_COMPOSITE_HARD_LIGHT";
    case DWRITE_COLOR_COMPOSITE_SOFT_LIGHT: return out << "DWRITE_COLOR_COMPOSITE_SOFT_LIGHT";
    case DWRITE_COLOR_COMPOSITE_DIFFERENCE: return out << "DWRITE_COLOR_COMPOSITE_DIFFERENCE";
    case DWRITE_COLOR_COMPOSITE_EXCLUSION: return out << "DWRITE_COLOR_COMPOSITE_EXCLUSION";
    case DWRITE_COLOR_COMPOSITE_MULTIPLY: return out << "DWRITE_COLOR_COMPOSITE_MULTIPLY";

    case DWRITE_COLOR_COMPOSITE_HSL_HUE: return out << "DWRITE_COLOR_COMPOSITE_HSL_HUE";
    case DWRITE_COLOR_COMPOSITE_HSL_SATURATION: return out << "DWRITE_COLOR_COMPOSITE_HSL_SATURATION";
    case DWRITE_COLOR_COMPOSITE_HSL_COLOR: return out << "DWRITE_COLOR_COMPOSITE_HSL_COLOR";
    case DWRITE_COLOR_COMPOSITE_HSL_LUMINOSITY: return out << "DWRITE_COLOR_COMPOSITE_HSL_LUMINOSITY";
    default: return out << (int)value;
    }
}

颜色字体支持