如何通过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
相关产品推荐
相关产品推荐

