You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过DirectWrite检测Noto Music字体字符的实际绘制区域?

获取Noto Music字体中特定字符的实际绘制区域

要准确获取单个字符(如U+1D046)的实际绘制区域,不能依赖IDWriteTextLayout::GetMetrics(它返回的是整个布局的整体度量,包含字体的字距、行高冗余空间),需要直接针对字符对应的字形(Glyph)进行度量,步骤如下:

步骤1:提取文本布局中的字形运行(Glyph Run)信息

通过IDWriteTextLayout::GetGlyphRun获取目标字符对应的字形数据,包括字形索引、字体面、字形位置等:

DWRITE_GLYPH_RUN glyphRun;
DWRITE_GLYPH_RUN_DESCRIPTION glyphRunDesc;
FLOAT baselineOriginX, baselineOriginY;
// 假设目标字符在文本的第0个位置(索引0)
HRESULT hr = lay->GetGlyphRun(
    0,  // 起始文本位置
    &glyphRun,
    &glyphRunDesc,
    &baselineOriginX,
    &baselineOriginY,
    nullptr  // 省略字形簇映射
);
if (SUCCEEDED(hr) && glyphRun.glyphCount > 0)
{
    // 处理第一个字形(对应U+1D046)
    UINT16 targetGlyphIndex = glyphRun.glyphIndices[0];
    CComPtr<IDWriteFontFace> fontFace = glyphRun.fontFace;
    // 继续后续步骤
}

步骤2:获取字形的设计度量或轮廓边界

方法A:使用设计度量快速获取边界

通过IDWriteFontFace::GetDesignGlyphMetrics获取字形的设计坐标(基于字体设计单位),再转换为屏幕像素:

DWRITE_GLYPH_METRICS glyphMetrics;
BOOL isSideways = FALSE;
hr = fontFace->GetDesignGlyphMetrics(
    &targetGlyphIndex,
    1,
    &glyphMetrics,
    isSideways
);
if (SUCCEEDED(hr))
{
    // 将设计单位转换为像素:设计单位 × 字号 / 字体设计单位
    FLOAT pixelsPerEm = (FLOAT)fontFace->GetMetrics()->designUnitsPerEm;
    FLOAT scale = glyphRun.fontSize / pixelsPerEm;

    // 计算实际绘制区域的矩形(基于基线原点)
    FLOAT left = baselineOriginX + glyphMetrics.leftSideBearing * scale;
    FLOAT top = baselineOriginY - glyphMetrics.topSideBearing * scale;
    FLOAT width = glyphMetrics.advanceWidth * scale;
    FLOAT height = (glyphMetrics.topSideBearing + glyphMetrics.bottomSideBearing + glyphMetrics.verticalOriginY) * scale;

    // 最终的实际绘制矩形:RECTF actualRect = { left, top, left + width, top + height };
}

方法B:通过字形轮廓精确计算边界

如果需要更精确的边界(比如排除字形内部的空白),可以获取字形的路径轮廓,再计算路径的包围盒:

IDWriteFontFace1* fontFace1 = nullptr;
hr = fontFace->QueryInterface(&fontFace1);
if (SUCCEEDED(hr))
{
    DWRITE_GLYPH_IMAGE_FORMATS imageFormat = fontFace1->GetGlyphImageFormat(targetGlyphIndex);
    if (imageFormat == DWRITE_GLYPH_IMAGE_FORMAT_OUTLINE)
    {
        IDWriteGeometrySink* geometrySink = nullptr;
        // 创建一个路径几何对象来接收轮廓
        CComPtr<IDWriteFactory> dwFactory;
        fa->QueryInterface(&dwFactory); // fa是原代码中的IDWriteFactory
        CComPtr<IDWritePathGeometry> pathGeometry;
        dwFactory->CreatePathGeometry(&pathGeometry);
        pathGeometry->Open(&geometrySink);

        // 获取字形轮廓
        hr = fontFace->GetGlyphOutline(
            targetGlyphIndex,
            glyphRun.fontSize,
            glyphRun.fontSize,
            nullptr, // 变换矩阵,这里用默认
            geometrySink
        );
        if (SUCCEEDED(hr))
        {
            geometrySink->Close();
            // 获取路径的包围盒
            D2D1_RECT_F boundingRect;
            pathGeometry->GetBounds(nullptr, &boundingRect);
            // 结合基线原点调整坐标
            boundingRect.left += baselineOriginX;
            boundingRect.right += baselineOriginX;
            boundingRect.top += baselineOriginY;
            boundingRect.bottom += baselineOriginY;
            // boundingRect即为实际绘制的精确边界
        }
        SafeRelease(&geometrySink);
    }
    SafeRelease(&fontFace1);
}

关键说明

  • Noto Music这类特殊符号字体的字符通常在字体设计中占据较小的设计区域,GetMetrics返回的高度包含了字体的 ascent/descent 预留空间,导致看起来字符很小,实际需要针对字形单独度量。
  • 转换设计单位到像素时,必须使用字体的designUnitsPerEm进行缩放,确保坐标准确。

内容的提问来源于stack exchange,提问作者Michael Chourdakis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 09:56:17