MFC生成PDF嵌入TrueType字体:如何获取Glyph Widths?
嘿,我正好处理过类似的PDF字体嵌入问题,咱们来一步步解决你的困惑:
核心问题拆解
你提到的Adobe公式是完全正确的:(advance * 1000) / unitsPerEm,这个公式是为了把字体设计空间的原始宽度值转换成PDF要求的「千分比宽度」(PDF字体默认以1000为基准单位)。现在的问题是怎么拿到正确的advance(字形前进宽度)和unitsPerEm,以及理解缩放的逻辑。
第一步:获取字体的unitsPerEm
unitsPerEm是TrueType字体的核心设计参数,代表字体设计空间的基准单位大小(比如大部分字体是1000)。你可以用WinAPI的GetOutlineTextMetrics直接获取:
// 示例代码:获取目标字体的unitsPerEm HDC hdc = GetDC(NULL); HFONT hOriginalFont = (HFONT)SelectObject(hdc, yourFontHandle); // 替换成你的字体句柄 // 先获取所需缓冲区大小 DWORD otmBufferSize = GetOutlineTextMetrics(hdc, 0, NULL); OUTLINETEXTMETRIC* pOtm = (OUTLINETEXTMETRIC*)malloc(otmBufferSize); if (GetOutlineTextMetrics(hdc, otmBufferSize, pOtm)) { UINT unitsPerEm = pOtm->otmEMSquare; // 这就是我们要的unitsPerEm // 可以把这个值保存下来供后续计算使用 } // 清理资源 SelectObject(hdc, hOriginalFont); ReleaseDC(NULL, hdc); free(pOtm);
第二步:获取字体设计空间的Advance Width
你之前用的GetCharWidthFloat返回的是当前DC字体字号下的设备无关宽度,不是字体原始设计空间的宽度。要拿到设计空间的advance,有两种可靠的方法:
方法一:用GetGlyphOutline(推荐,直接拿原始设计值)
GetGlyphOutline可以直接获取字体设计空间的字形度量,指定GGO_METRICS参数即可:
// 示例:获取字符'A'的设计空间advance宽度 GLYPHMETRICSFLOAT gmf = {0}; MAT2 identityMatrix = {0}; // 设置单位矩阵,不做任何缩放 identityMatrix.eM11.value = 1; identityMatrix.eM11.fract = 0; identityMatrix.eM22.value = 1; identityMatrix.eM22.fract = 0; HDC hdc = GetDC(NULL); SelectObject(hdc, yourFontHandle); DWORD result = GetGlyphOutline(hdc, 'A', GGO_METRICS, &gmf, 0, NULL, &identityMatrix); if (result != GDI_ERROR) { float designAdvance = gmf.gmfCellIncX; // 字体设计空间的前进宽度 float pdfWidth = (designAdvance * 1000.0f) / unitsPerEm; // 转换为PDF要求的千分比宽度 // 这个pdfWidth就是你要写入PDF字体对象的Glyph Width值 } ReleaseDC(NULL, hdc);
方法二:用GetCharWidthFloat(需先创建设计单位字体)
如果你更习惯用GetCharWidthFloat,可以先创建一个以字体设计单位为基准的字体(把字体高度设为-unitsPerEm,负数代表使用设计单位),再调用函数:
// 示例:创建设计单位字体并获取宽度 HFONT hDesignFont = CreateFont( -unitsPerEm, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH | FF_DONTCARE, yourFontName // 替换成你的字体名称 ); HDC hdc = GetDC(NULL); HFONT hOldFont = (HFONT)SelectObject(hdc, hDesignFont); float charWidth; GetCharWidthFloat(hdc, 'A', 'A', &charWidth); float pdfWidth = (charWidth * 1000.0f) / unitsPerEm; // 清理资源 SelectObject(hdc, hOldFont); DeleteObject(hDesignFont); ReleaseDC(NULL, hdc);
有没有直接返回PDF宽度的WinAPI?
很遗憾,没有直接返回PDF所需千分比宽度的WinAPI,但上面两种方法组合起来已经非常高效了,而且完全依赖系统原生API,稳定性有保障。
另外补充一点:如果你的PDF需要处理完整字符集,记得循环遍历所需的字符(或字形索引),用GetCharIndex把字符编码转换成字形索引(避免字符和字形不一一对应的情况),再批量获取宽度值。
内容的提问来源于stack exchange,提问作者Penachia

