ReactOS 图形系统分析(40):文本子系统 --- text.c
0. 本文范围与阅读前提
本文分析 ReactOS 内核态 GDI(win32ss)的文本子系统 入口文件
text.c(file:///d:/reactos/win32ss/gdi/ntgdi/text.c)(约 618 行),以及与之
配套的头文件 text.h(file:///d:/reactos/win32ss/gdi/ntgdi/text.h) 中声明的
核心数据结构(TEXTOBJ/LFONT、STRGDI)与内部接口。
text.c 本身不包含 任何字形光栅化代码------它是一层"API → 内部实现"的转发
层:所有真正的工作(字形取位、度量计算、字符集判定、光栅化能力查询)都被
转发到 freetype.c(FreeType 引擎封装)与 font.c(字体注册/枚举)中的
ft*/TextInt*/Int* 内部函数。阅读本文前建议先了解:
- 《分析_16》:freetype.c 的
IntExtTextOutW与字形光栅化细节; - 《分析_33》:font.c 的字体加载与注册;
- 《分析_37》:path.c 的路径(PATH_ExtTextOut);
- 《分析_31》:win32k 图形系统整体入口表(text.c 的 8 个 NtGdi* 入口)。
本文档基于 ReactOS 主分支源码(2026 年 8 月快照)逐函数核实,所有函数签名、
行号、行为描述均对应真实代码,未做任何虚构。
1. 概述与设计动机
1.1 概述
text.c(file:///d:/reactos/win32ss/gdi/ntgdi/text.c) 位于
win32ss/gdi/ntgdi/,是 ntgdi 的文本输出与度量入口 模块。文件以"Gre*"
(内核态 GDI 导出,供 win32k 内部/服务端调用)和"NtGdi*"(系统调用入口,
供 user32 通过快速系统调用进入)两种前缀组织函数。
应用层 (user32.dll / 应用程序)
│ TextOut / GetTextExtentPoint32 / GetTextExtentExPoint
│ GetTextMetrics / GetTextFace / GetTextCharsetInfo
│ GetRasterizerCaps / SetTextJustification / GetCharSet
▼
user32 内部 (wine 兼容层) → NtUserCall* / NtGdi* 系统调用
▼
┌───────────────────────────── text.c ─────────────────────────────┐
│ GreTextOutW ──────────► GreExtTextOutW ──► IntExtTextOutW │
│ GreGetTextExtentW ─┐ │
│ GreGetTextExtentExW ┼─► TextIntGetTextExtentPoint (freetype.c) │
│ GreGetTextMetricsW ─┴─► ftGdiGetTextMetricsW (freetype.c) │
│ NtGdiGetTextExtent(Ex)W ─► TextIntGetTextExtentPoint │
│ NtGdiGetTextMetricsW ─► ftGdiGetTextMetricsW + IntTMWFixUp │
│ NtGdiGetTextFaceW ──► RealizeFontInit + TextFace │
│ NtGdiGetCharSet / NtGdiGetTextCharsetInfo ─► ftGdiGetTextCharsetInfo│
│ NtGdiGetRasterizerCaps ─► ftGdiGetRasterizerCaps │
│ NtGdiSetTextJustification ─► pdcattr->lBreakExtra / cBreak │
└────────────────────────────────┬──────────────────────────────────┘
▼
┌────────────────┴───────────────┐
│ freetype.c (FreeType 封装) │
│ 字形渲染 / 度量 / 字符集 │
│ font.c (字体注册与枚举) │
└────────────────────────────────┘
1.2 设计动机
-
内核/用户分层 :与 win32k 其他子系统一致,text.c 把"系统调用入口
(NtGdi*,处理 SEH 探测、池分配、MmCopyFromCaller/ToCaller 用户态内存
拷贝)"与"内部实现(Gre*,只操作内核态对象,直接接受已验证指针)"分离。
user32 从不直接调用 Gre*,而是通过系统调用进入 NtGdi*;win32k 内部
(如 win32ss 其他模块)才直接调用 Gre*。
-
与 FreeType 引擎解耦 :text.c 完全不感知 FreeType 的存在。所有引擎
相关操作都封装在 freetype.c 的
ft*前缀函数中(ftGdiGetTextMetricsW、ftGdiGetTextCharsetInfo、ftGdiGetRasterizerCaps),并通过 text.h 的函数原型把契约固定下来。这样未来替换字体引擎(例如支持位图光栅字体)
时只需替换 freetype.c 一侧。
-
惰性实现(RealizeFontInit) :字体对象(LFONT/TEXTOBJ)在
CreateFontIndirect时并不立即与某个物理字体(FONTGDI)绑定------绑定推迟到第一次需要实际度量/渲染时(
RealizeFontInit→TextIntRealizeFont),期间完成字体替换、匹配打分、字体名解析等重活。这一"按需实现"模式使
每次 TextOut/GetTextExtent 前的准备工作尽量最小化(见 2.4 节)。
-
CORE-1091 的历史包袱 :
IntTMWFixUp注释明确说明------ReactOS 目前不支持光栅字体,
ftGdiGetTextMetricsW永远返回 TrueType 字体标志(TMPF_VECTOR | TMPF_TRUETYPE),这在编辑框中会造成布局问题。因此 text.c
用一个"光栅字体名黑名单"来补偿(见 3.1 节)。
-
统一度量入口 :文本输出(IntExtTextOutW)与文本度量
(TextIntGetTextExtentPoint)共享同一套"字形查找/缓存/字距"逻辑(见 4、5
节),保证"画出来的宽度 == 量出来的宽度"。
1.3 文件内容总览
text.c 共包含 14 个函数(按源码出现顺序):
| # | 函数 | 前缀 | 角色 |
|---|---|---|---|
| 1 | IntTMWFixUp |
内部 | 光栅字体名补偿(CORE-1091 hack) |
| 2 | GreTextOutW |
Gre | TextOut 入口(薄转发) |
| 3 | GreGetTextExtentW |
Gre | 文本宽度(32/旧两版本) |
| 4 | GreGetTextExtentExW |
Gre | 扩展文本宽度(含 Fit/Dx) |
| 5 | GreGetTextMetricsW |
Gre | TEXTMETRICW 填充 |
| 6 | NtGdiGetCharSet |
Nt | 字符集查询(更新 DC 缓存) |
| 7 | NtGdiGetRasterizerCaps |
Nt | 光栅化能力查询 |
| 8 | NtGdiGetTextCharsetInfo |
Nt | 字符集 + 字体签名 |
| 9 | NtGdiGetTextExtentExW |
Nt | 扩展文本宽度(用户态安全拷贝) |
| 10 | NtGdiGetTextExtent |
Nt | 文本宽度(用户态入口) |
| 11 | NtGdiSetTextJustification |
Nt | 设置对齐增量 |
| 12 | NtGdiGetTextFaceW |
Nt | 字体字面名 |
| 13 | NtGdiGetTextMetricsW |
Nt | TEXTMETRICW(用户态安全拷贝) |
(text.c 文件末尾为注释 /* EOF */,全文件约 618 行。)
2. 核心数据结构
2.1 TEXTOBJ / LFONT(text.h)
TEXTOBJ 与 LFONT 是同一个结构体 的两个别名------"逻辑字体对象"
(logical font object),代表一次 CreateFontIndirect 的结果。它挂在 GDI
句柄表上,通过 HFONT 句柄引用:
c
/* GDI logical font object */
typedef struct _LFONT
{
/* Header for all gdi objects in the handle table.
Do not (re)move this. */
BASEOBJECT BaseObject; // 句柄表对象头(必须位于首位)
LFTYPE lft; // 字体类型标志
FLONG fl; // 状态标志(TEXTOBJECT_INIT 等)
FONTOBJ *Font; // 实现后的物理字体对象(FONTGDI 的 FONTOBJ 头)
WCHAR TextFace[LF_FACESIZE]; // 解析出的字体字面名(GetTextFace 返回值)
DWORD dwOffsetEndArray;
// Fixed:
ENUMLOGFONTEXDVW logfont; // 完整的 LOGFONT + 扩展设计向量
EX_PUSH_LOCK lock; // 对象锁(PushLock)
} TEXTOBJ, *PTEXTOBJ, LFONT, *PLFONT;
关键点:
BaseObject是 GDI 句柄表对象头,绝对不能移动或删除 (注释原话)。
LFONT 通过LFONT_AllocFontWithHandle()宏分配:
GDIOBJ_AllocObjWithHandle(GDI_OBJECT_TYPE_FONT, sizeof(TEXTOBJ))。logfont字段保存创建字体时的完整 LOGFONTW(含扩展设计向量 DV),
TextIntRealizeFont与度量代码通过
TextObj->logfont.elfEnumLogfontEx.elfLogFont反复读取lfHeight、
lfWidth、lfWeight、lfItalic、lfEscapement、lfOrientation等字段。Font字段在实现(realize)之前为 NULL ,实现后指向 FONTGDI 内嵌的
FONTOBJ 头(见 2.2)。TextFace[LF_FACESIZE]是NtGdiGetTextFaceW的数据源(见 3.12 节)。
锁定协议(text.h 内联函数):
c
FORCEINLINE PTEXTOBJ TEXTOBJ_LockText(HFONT hfont)
{
PLFONT plfnt = LFONT_ShareLockFont(hfont); // 句柄→对象引用计数+1
if (plfnt != 0)
{
KeEnterCriticalRegion(); // 禁止 APC 抢占
ExAcquirePushLockExclusive(&plfnt->lock); // 排他锁
}
return plfnt;
}
FORCEINLINE VOID TEXTOBJ_UnlockText(PLFONT plfnt)
{
ExReleasePushLockExclusive(&plfnt->lock);
KeLeaveCriticalRegion();
LFONT_ShareUnlockFont(plfnt); // 释放引用
}
LFONT_ShareLockFont→GDIOBJ_ReferenceObjectByHandle(hfont, GDIObjType_LFONT_TYPE);LFONT_ShareUnlockFont→GDIOBJ_vDereferenceObject;- 惯例:
RealizeFontInit返回的 TextObj 必须配对调用TEXTOBJ_UnlockText,
text.c 中每个函数都遵守这一协议。
2.2 FONTGDI(engobjects.h)
FONTGDI 定义在 engobjects.h(file:///d:/reactos/win32ss/gdi/eng/engobjects.h#L139),
是"物理字体实例"(一次实现后的字体,绑定到一个 FT_Face 和一组请求参数):
c
typedef struct _FONTGDI {
FONTOBJ FontObj; // 引擎可见的 FONTOBJ 头(含 flFontType 等)
ULONG iUnique;
FLONG flType;
DHPDEV dhpdev;
PSHARED_FACE SharedFace; // 共享 FT_Face(同名字体共享一份 FreeType 数据)
LONG lMaxNegA;
LONG lMaxNegC;
LONG lMinWidthD;
LPWSTR Filename;
BYTE RequestUnderline; // 请求的下划线(lfUnderline ? 0xFF : 0)
BYTE RequestStrikeOut; // 请求的删除线
BYTE RequestItalic; // 请求的斜体
LONG RequestWeight; // 请求的字重(lfWeight,FW_DONTCARE→FW_NORMAL)
BYTE OriginalItalic; // 字体自带斜体属性
LONG OriginalWeight; // 字体自带字重
BYTE CharSet; // 字体字符集(tmCharSet 数据源)
/* Precomputed font metrics (supplements FreeType metrics) */
LONG tmHeight; // 预计算度量:行高
LONG tmAscent; // 预计算度量:上行
LONG tmDescent; // 预计算度量:下行
LONG tmInternalLeading; // 预计算度量:内行距
LONG Magic; // FONTGDI_MAGIC (0x20110311) 校验魔数
LONG lfHeight; // 请求字号
LONG lfWidth; // 请求字宽
} FONTGDI, *PFONTGDI;
Magic == FONTGDI_MAGIC (0x20110311)用于断言对象有效
(ASSERT(FontGDI->Magic == FONTGDI_MAGIC),见 freetype.c:5127)。SharedFace指向共享面(SHARED_FACE),内部含Face(FT_Face)以及
EnglishUS / UserLanguage 两份本地化名称缓存。ObjToGDI(TextObj->Font, FONT)宏把 TEXTOBJ 的Font(FONTOBJ*)折算为
PFONTGDI(FONTOBJ 是 FONTGDI 的首字段)。tmAscent/tmDescent由实现流程预计算并写入 FONTGDI,供
TextIntGetTextExtentPoint、IntExtTextOutW直接使用(避免反复向
FreeType 查询)。
2.3 TEXTMETRICW / TMW_INTERNAL(ntgdityp.h)
TMW_INTERNAL 定义在 ntgdityp.h(file:///d:/reactos/win32ss/include/ntgdityp.h#L368):
c
typedef struct _TMW_INTERNAL
{
TEXTMETRICW TextMetric; // 标准的 TEXTMETRICW(用户可见部分)
TMDIFF Diff; // 差异记录(FIXME:当前未填充)
} TMW_INTERNAL, *PTMW_INTERNAL;
- 用户态
GetTextMetricsW只看到TextMetric(TEXTMETRICW); - 内核内部接口(
ftGdiGetTextMetricsW、NtGdiGetTextMetricsW、
GreGetTextMetricsW、IntGetCharDimensions)一律使用 TMW_INTERNAL 作为
载体,为将来填充Diff(Windows 的 TMDIFF 结构)预留空间; NtGdiGetTextMetricsW用cj <= sizeof(TMW_INTERNAL)限制拷贝字节数,
兼容旧式调用方只传 sizeof(TEXTMETRICW) 的情形。
TEXTMETRICW 字段与填充来源(详见 5.2 节 FillTM):
| 字段 | 含义 | 来源 |
|---|---|---|
| tmHeight | 字符高度 | tmAscent + tmDescent |
| tmAscent | 基线以上高度 | FONTGDI->tmAscent(预计算) |
| tmDescent | 基线以下深度 | FONTGDI->tmDescent |
| tmInternalLeading | 内行距(重音符区) | FONTGDI->tmInternalLeading |
| tmExternalLeading | 外行距(行间距) | MAX(0, LineGap − (WinAscent+WinDescent − (Ascender−Descender))) |
| tmAveCharWidth | 平均字符宽 | lfWidth ≠ 0 用之,否则 OS/2 xAvgCharWidth |
| tmMaxCharWidth | 最大字符宽 | FT max_advance_width |
| tmWeight | 字重 | OriginalWeight > RequestWeight 时取 Original |
| tmOverhang | 斜体突出 | 恒 0 |
| tmDigitizedAspectX/Y | 纵横比 | 恒 96 |
| tmFirstChar/tmLastChar | 首/末字符 | OS/2 usFirstCharIndex/usLastCharIndex(Symbol 字体特判) |
| tmDefaultChar | 缺省字符 | breakChar − 1 等 |
| tmBreakChar | 断字符 | 0x20 等 |
| tmItalic/tmUnderlined/tmStruckOut | 斜体/下划线/删除线 | FONTGDI Request* 标志 |
| tmPitchAndFamily | 间距+字族 | panose + TMPF_VECTOR/TMPF_TRUETYPE |
| tmCharSet | 字符集 | FONTGDI->CharSet |
2.4 字体的实现流程(RealizeFontInit → FONTGDI)
"实现(realize)"= 把逻辑字体 TEXTOBJ 绑定到一个物理字体 FONTGDI。
入口是 font.c(file:///d:/reactos/win32ss/gdi/ntgdi/font.c#L431) 的
RealizeFontInit,实际工作由 freetype.c 的 TextIntRealizeFont 完成。
RealizeFontInit(hFont) // font.c:431
│
├─ TEXTOBJ_LockText(hFont) // 句柄→引用+排他锁
├─ 若 TextObj->fl & TEXTOBJECT_INIT // 已实现过则直接返回
│ └─ 返回(不做任何事,惰性缓存命中)
└─ TextIntRealizeFont(hFont, pTextObj) // freetype.c:6050
│
├─ SubstituteFontRecurse(&SubLogFont) // 字体替换(Arial→Liberation 等)
├─ MatchPenalty = 0xFFFFFFFF
├─ FindBestFontFromList(..., &Win32Process->PrivateFontListHead)
│ // ① 进程私有字体表(AddFontMemResource/进程级 AddFont)
├─ FindBestFontFromList(..., &g_FontListHead)
│ // ② 系统字体表(打分制:CharSet 65000 / FixedPitch 15000 /
│ // OutputPrecision 19000 / PitchVariable 350 ...)
├─ 未找到 → STATUS_NOT_FOUND
├─ 填充 TextObj->TextFace:
│ ├─ MatchFontNames(SharedFace, 替换后名) ?
│ │ → 拷贝原始 lfFaceName 到 TextFace
│ └─ 否则 IntGetFontLocalizedName(..., TT_NAME_ID_FONT_FAMILY,
│ gusLanguageID)
│ // 用 FreeType name 表取本地化字体名
├─ TextObj->Font->iUniq = 1 // 允许缓存
├─ IntFontType(FontGdi) // 判定字体类型
├─ FontGdi->flType = TextObj->Font->flFontType
├─ RequestUnderline/StrikeOut/Italic/Weight ← LOGFONTW 请求
└─ TextObj->fl |= TEXTOBJECT_INIT // 标记已实现
实现流程要点:
- 惰性 :
CreateFontIndirect只创建 TEXTOBJ,不选字体;直到
TextOut/GetTextMetrics 等调用RealizeFontInit时才匹配字体。 - 两次匹配 :先查当前进程的私有字体表,再查全局系统字体表;
FindBestFontFromList按惩罚分(GetFontPenalty)选择最优字体。 - 请求参数拷贝 :
RequestWeight/RequestItalic/...在实现时从 LOGFONTW
拷入 FONTGDI,供 FillTM 与光栅化使用;OriginalWeight/OriginalItalic
保留字体本身属性,用于加粗/斜体模拟判定(EMUBOLD_NEEDED)。 - 初始化后置位
TEXTOBJECT_INIT,下次RealizeFontInit直接短路返回。 TextIntRealizeFont内部用IntLockFreeType/IntUnLockFreeType包裹对
g_FontListHead的遍历(FreeType 全局锁)。
3. 函数逐一展开
3.1 IntTMWFixUp(text.c:28)--- 光栅字体名补偿
c
VOID FASTCALL IntTMWFixUp(_In_ HDC hDC, _Inout_ PTMW_INTERNAL ptm)
- 作用 :修复
GetTextMetricsW对"光栅字体名"返回错误
TMPF_TRUETYPE/TMPF_VECTOR 标志的问题(CORE-1091)。 - 实现流程 :
NtGdiGetDCObject(hDC, GDI_OBJECT_TYPE_FONT)取当前字体句柄;GreGetObject(hCurrentFont, sizeof(LOGFONTW), &lf)取 LOGFONTW;- 用
_wcsicmp逐一比对lf.lfFaceName与 8 个光栅字体名:
Courier、FixedSys、Helv、MS Sans Serif、MS Serif、System、
Terminal、Tms Rmn; - 命中则
ptm->TextMetric.tmPitchAndFamily &= ~(TMPF_TRUETYPE|TMPF_VECTOR)。
- 使用方式 :
GreGetTextMetricsW与NtGdiGetTextMetricsW在
ftGdiGetTextMetricsW之后立即调用。 - 注意事项 :这是被注释明示的 "hack"------注释写道:由于 ReactOS 尚不支持
光栅字体,ftGdiGetTextMetricsW永远返回 TrueType 字体,导致编辑框布局
异常;等光栅字体支持加入后此函数应删除。它依赖当前 DC 选中的字体
(通过NtGdiGetDCObject),因此必须在 DC 有效且已选字体后调用。
3.2 GreTextOutW(text.c:59)--- TextOut 入口(薄转发)
c
BOOL FASTCALL GreTextOutW(_In_ HDC hdc, _In_ INT nXStart, _In_ INT nYStart,
_In_reads_(cchString) PCWCH lpString, _In_ INT cchString)
- 作用 :实现
TextOutW的内核侧(无 Ext 扩展参数的简化版)。 - 实现流程 :函数体仅一行------
return GreExtTextOutW(hdc, nXStart, nYStart, 0, NULL, lpString, cchString, NULL, 0);
即:fuOptions=0、无裁剪矩形(lprc=NULL)、无 Dx 数组、dwCodePage=0。 - 使用方式 :由
NtGdiTextOut(系统调用)或 win32k 内部直接调用。 - 注意事项 :
- 它不检查字符串有效性,也不做用户态拷贝------这是 Gre 层惯例 :假设
调用方已提供安全内核指针; GreExtTextOutW实现在 freetype.c:7300(本文件只有声明);- 完整的 TextOut 调用链见第 4 节。
- 它不检查字符串有效性,也不做用户态拷贝------这是 Gre 层惯例 :假设
3.3 GreGetTextExtentW(text.c:77)--- 文本宽度(32/旧版)
c
BOOL FASTCALL GreGetTextExtentW(_In_ HDC hDC, _In_reads_(cwc) PCWCH lpwsz,
_In_ INT cwc, _Out_ PSIZE psize, _In_ UINT flOpts)
- 作用 :计算字符串的宽度/高度(
GetTextExtentPoint32W与
GetTextExtentPointW共用)。 - 参数 :
flOpts------ 按注释:GetTextExtentPoint32W = 0GetTextExtentPointW = 1
- 实现流程 :
cwc == 0短路:psize->cx = 0; psize->cy = 0; return TRUE;
(空字符串直接返回零尺寸,不锁 DC);DC_LockDc(hDC),失败 →EngSetLastError(ERROR_INVALID_HANDLE),
return FALSE;RealizeFontInit(pdcattr->hlfntNew)实现当前字体;- 成功 →
TextIntGetTextExtentPoint(pdc, TextObj, lpwsz, cwc, 0, NULL, NULL, psize, flOpts)------MaxExtent=0、Fit=NULL、Dx=NULL(非 Ex 版本
不需要逐字符宽度);随后TEXTOBJ_UnlockText(TextObj); DC_UnlockDc(pdc)返回结果。
- 注意事项 :度量精度由
TextIntGetTextExtentPoint决定(第 5 节),
与 flOpts 取 0 还是 1 在 FreeType 路径下目前等价(32 版使用高精度度量,
旧版取整,ReactOS 当前统一走 26.6 定点)。
3.4 GreGetTextExtentExW(text.c:133)--- 扩展文本宽度
c
BOOL FASTCALL GreGetTextExtentExW(_In_ HDC hDC, _In_ PCWCH String,
_In_ ULONG Count, _In_ ULONG MaxExtent, _Out_opt_ PULONG Fit,
_Out_writes_to_opt_(Count, *Fit) PULONG Dx, _Out_ PSIZE pSize, _In_ FLONG fl)
- 作用 :实现
GetTextExtentExPointW(含"在 MaxExtent 内能放几个字符"
与"逐字符累计宽度")。 - 参数 :
fl------ 按注释:GetTextExtentExPointW = 0(以及其它使用该入口的调用)GetTextExtentExPointI = 1(按字形索引)
- 实现流程 :
- 参数校验:
(!String && Count) || !pSize→EngSetLastError( ERROR_INVALID_PARAMETER),return FALSE; Count == 0短路:if (Fit) *Fit = 0; return TRUE;(注意:源码中
此分支Fit = 0的写法不合法------实际是if ( Fit ) *Fit = 0;,即把
用户缓冲区清零后成功返回);DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE;RealizeFontInit(pdcattr->hlfntNew);- 成功 →
TextIntGetTextExtentPoint(pdc, TextObj, String, Count, MaxExtent, (PINT)Fit, (PINT)Dx, pSize, fl)(PULONG 强转为 PINT,
因为内部接口按 INT 处理); TEXTOBJ_UnlockText、DC_UnlockDc返回。
- 参数校验:
- 注意事项 :与 NtGdiGetTextExtentExW 的区别:本函数不做用户态内存
拷贝 ,String/Fit/Dx/pSize 均为内核指针(Gre 层约定);它是
NtGdiGetTextExtentExW内部路径的直接内核版本。
3.5 GreGetTextMetricsW(text.c:191)--- 度量结构填充(内核版)
c
BOOL WINAPI GreGetTextMetricsW(_In_ HDC hdc, _Out_ LPTEXTMETRICW lptm)
- 作用 :把当前字体的 TEXTMETRICW 填入
lptm。 - 实现流程 (函数体极短,仅 5 行):
- 栈上建
TMW_INTERNAL tmwi; ftGdiGetTextMetricsW(hdc, &tmwi)失败 → return FALSE;IntTMWFixUp(hdc, &tmwi)(光栅字体名补偿,见 3.1);*lptm = tmwi.TextMetric;只拷贝 TEXTMETRICW 部分;- return TRUE。
- 栈上建
- 注意事项 :
- 输出参数只有 TEXTMETRICW,不含 TMDIFF;
ftGdiGetTextMetricsW的完整分析见 5.2 节;- 此函数未加 SEH------调用方必须保证 lptm 可写(Gre 层契约)。
3.6 NtGdiGetCharSet(text.c:204)--- 字符集查询
c
DWORD APIENTRY NtGdiGetCharSet(_In_ HDC hDC)
- 作用 :返回当前 DC 字体的字符集(如 ANSI_CHARSET、DEFAULT_CHARSET),
并更新 DC 属性缓存。 - 实现流程 :
DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE, return 0;cscp = ftGdiGetTextCharsetInfo(Dc, NULL, 0)(只查字符集,不取签名);- 缓存:
pdcattr->iCS_CP = cscp; pdcattr->ulDirty_ &= ~DIRTY_CHARSET;(清除"字符集脏"标志,此后
GetTextCharsetInfo的快捷路径可命中缓存);DC_UnlockDc(Dc)返回 cscp。
- 注意事项 :
- 注释 "If here, update everything!" 说明该入口无条件重算;
- 返回值是 MAKELONG(ACP, CharSet) 打包值(见 ftGdiGetTextCharsetInfo),
用户态GetCharSet取低字; - 此函数属于 3.8 的快捷缓存维护者:先清 DIRTY_CHARSET,后续查询直接
使用pdcattr->iCS_CP。
3.7 NtGdiGetRasterizerCaps(text.c:226)--- 光栅化能力查询
c
BOOL APIENTRY NtGdiGetRasterizerCaps(_Out_ LPRASTERIZER_STATUS praststat,
_In_ ULONG cjBytes)
- 作用 :实现
GetRasterizerCaps------报告系统光栅化器能力
(TrueType 可用/启用、语言 ID)。 - 实现流程 :
- 前置:
praststat && cjBytes不成立 → return FALSE(不设错误码); - 若
cjBytes >= sizeof(RASTERIZER_STATUS)则截断为
sizeof(RASTERIZER_STATUS)(避免越界写); ftGdiGetRasterizerCaps(&rsSafe)填局部 RASTERIZER_STATUS------见
freetype.c:3630:nSize = sizeof(RASTERIZER_STATUS)、
wFlags = TT_AVAILABLE | TT_ENABLED(TrueType 光栅化器可用且启用)、
nLanguageID = gusLanguageID;_SEH2_TRY:ProbeForWrite(praststat, sizeof(RASTERIZER_STATUS), 1)
探测用户缓冲,RtlCopyMemory(praststat, &rsSafe, cjBytes)按截断后
的字节数拷贝;- 异常 →
SetLastNtError(Status)return FALSE; - 成功 return TRUE。
- 前置:
- 注意事项 :
- 先栈上生成
rsSafe再统一拷贝,避免在 SEH 内直接写用户内存的
中间态问题; cjBytes允许小于 sizeof(RASTERIZER_STATUS),此时只拷贝前 cjBytes
字节(兼容旧结构);ftGdiGetRasterizerCaps恒返回 TrueType 可用------因为 ReactOS 当前
唯一光栅化器就是 FreeType。
- 先栈上生成
3.8 NtGdiGetTextCharsetInfo(text.c:265)--- 字符集 + 字体签名
c
INT APIENTRY NtGdiGetTextCharsetInfo(_In_ HDC hdc, _Out_opt_ LPFONTSIGNATURE lpSig,
_In_ DWORD dwFlags)
- 作用 :实现
GetTextCharsetInfo------返回字体字符集,可选返回
FONTSIGNATURE(代码页范围 fsCsb + Unicode 范围 fsUsb)。 - 实现流程 :
DC_LockDc(hdc),失败 → ERROR_INVALID_HANDLE, return DEFAULT_CHARSET;lpSig == NULL→pfsSafe = NULL(只查字符集);Ret = HIWORD(ftGdiGetTextCharsetInfo(Dc, pfsSafe, dwFlags))
------取高字为字符集(低字为 ACP);- 若
lpSig非空:Ret == DEFAULT_CHARSET→RtlZeroMemory(pfsSafe, sizeof(FONTSIGNATURE))
(字符集未知时签名清零);_SEH2_TRY:ProbeForWrite(lpSig, sizeof(FONTSIGNATURE), 1)+
RtlCopyMemory拷贝签名到用户缓冲;- 异常 →
SetLastNtError(Status)return DEFAULT_CHARSET;
DC_UnlockDc(Dc)return Ret。
- 注意事项 :
- 与
NtGdiGetCharSet不同,此函数不 维护pdcattr->iCS_CP缓存,
每次直接查询 FreeType OS/2 表(见 5.3 节); - 返回值是字符集(INT),而非打包值;
ftGdiGetTextCharsetInfo的打包
值在此被 HIWORD 解包; dwFlags透传给 ft 层(当前实现未使用)。
- 与
3.9 NtGdiGetTextExtentExW(text.c:324)--- 扩展文本宽度(系统调用)
c
W32KAPI BOOL APIENTRY NtGdiGetTextExtentExW(_In_ HDC hDC,
_In_reads_opt_(Count) PCWCH UnsafeString, _In_ ULONG Count,
_In_ ULONG MaxExtent, _Out_opt_ PULONG UnsafeFit,
_Out_writes_to_opt_(Count, *UnsafeFit) PULONG UnsafeDx,
_Out_ PSIZE UnsafeSize, _In_ FLONG fl)
- 作用 :
GetTextExtentExPointW的系统调用入口------带完整的用户态内存
安全处理(探测、池拷贝、回写)。 - 实现流程 :
(LONG)Count < 0→ ERROR_INVALID_PARAMETER return FALSE
(Count 是 ULONG,强转 LONG 判负以拒绝超大值);Count == 0短路:Size = {0,0},MmCopyToCaller(UnsafeSize, &Size, sizeof(SIZE)),失败 → SetLastNtError return FALSE;成功 return TRUE;- 分配字符串池:
String = ExAllocatePoolWithTag(PagedPool, Count * sizeof(WCHAR), GDITAG_TEXT);失败 → ERROR_NOT_ENOUGH_MEMORY; - 若
UnsafeDx非空:再分配Dx = ExAllocatePoolWithTag(PagedPool, Count * sizeof(INT), GDITAG_TEXT)(失败则释放 String 并返回内存错误); MmCopyFromCaller(String, UnsafeString, Count * sizeof(WCHAR))
把用户字符串拷入内核池(失败 → 释放两块池并 SetLastNtError);DC_LockDc(hDC)(失败 → 释放池,ERROR_INVALID_HANDLE);RealizeFontInit(pdcattr->hlfntNew)→TextIntGetTextExtentPoint(dc, TextObj, String, Count, MaxExtent, NULL == UnsafeFit ? NULL : &Fit, Dx, &Size, fl);TEXTOBJ_UnlockText、DC_UnlockDc;- 释放 String 池;失败 → 释放 Dx 池 return FALSE;
- 回写:
UnsafeFit→MmCopyToCaller(UnsafeFit, &Fit, sizeof(INT));
UnsafeDx→MmCopyToCaller(UnsafeDx, Dx, Count * sizeof(INT));
释放 Dx 池;最后MmCopyToCaller(UnsafeSize, &Size, sizeof(SIZE)); - 任一回写失败 → SetLastNtError return FALSE;全部成功 return TRUE。
- 注意事项 :
- 注释
/* FIXME: Handle fl */:fl(GTEF_INDICES 等)当前未处理,
透传给 TextIntGetTextExtentPoint 作为字体链查找的索引模式标志; - 采用"先全部拷入内核池、计算、再统一回写"的模式,最大限度缩短持锁
时间并避免在持 DC 锁期间访问用户内存; Fit用局部 INT 接收再回写,Dx数组在内核池中计算;- 池标签 GDITAG_TEXT 便于内存泄漏诊断。
- 注释
3.10 NtGdiGetTextExtent(text.c:489)--- 文本宽度(系统调用)
c
BOOL APIENTRY NtGdiGetTextExtent(_In_ HDC hdc, _In_reads_(cwc) PCWCH lpwsz,
_In_ INT cwc, _Out_ PSIZE psize, _In_ UINT flOpts)
- 作用 :
GetTextExtentPoint32W/GetTextExtentPointW的系统调用入口。 - 实现流程 :函数体仅一行------
return NtGdiGetTextExtentExW(hdc, lpwsz, cwc, 0, NULL, NULL, psize, flOpts);
即把 Ex 版的 MaxExtent=0、Fit=NULL、Dx=NULL,复用到 Ex 的完整实现。 - 注意事项 :
- flOpts 语义同 3.3(0=GetTextExtentPoint32W,1=GetTextExtentPointW);
- 因为复用了 Ex 路径,天然获得用户态内存保护与空串处理;
cwc为 INT(可负),负数会触发 NtGdiGetTextExtentExW 中的
(LONG)Count < 0检查返回 ERROR_INVALID_PARAMETER。
3.11 NtGdiSetTextJustification(text.c:501)--- 设置对齐增量
c
BOOL APIENTRY NtGdiSetTextJustification(_In_ HDC hDC, _In_ INT BreakExtra,
_In_ INT BreakCount)
- 作用 :实现
SetTextJustification------为下一次 TextOut 指定"断字
额外空白总量 BreakExtra"与"断字字符个数 BreakCount",供文本两端对齐
时在每个断字处均分空白。 - 实现流程 :
DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE return FALSE;pdcattr->lBreakExtra = BreakExtra;pdcattr->cBreak = BreakCount;DC_UnlockDc(pDc)return TRUE。
- 注意事项 :
- 纯状态写入,不触发任何重算;真正消费这两个字段的是
IntExtTextOutW/IntGetTextDisposition的断行/对齐逻辑
(text.h STRGDI 中的fxBreakExtra/fxExtra概念与此呼应); - 注意与
TextOut的TA_CENTER/TA_RIGHT不同:那是对齐模式,
这是显式的空白分配。
- 纯状态写入,不触发任何重算;真正消费这两个字段的是
3.12 NtGdiGetTextFaceW(text.c:529)--- 字体字面名
c
W32KAPI INT APIENTRY NtGdiGetTextFaceW(_In_ HDC hDC, _In_ INT Count,
_Out_writes_to_opt_(Count, return) PWSTR FaceName, _In_ BOOL bAliasName)
- 作用 :实现
GetTextFaceW------返回当前 DC 字体的字面名(如
"Liberation Serif")。 - 实现流程 :
DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE return FALSE;hFont = pdcattr->hlfntNew;立即DC_UnlockDc(Dc)(短持锁:只取
字体句柄,后续在字体对象锁下工作);TextObj = RealizeFontInit(hFont);------ 注意此处用ASSERT(TextObj != NULL),假定实现必然成功;fLen = wcslen(TextObj->TextFace) + 1;(含终止符的字符数);- 若
FaceName非空(需要拷贝):Count = min(Count, fLen);(请求长度截断到实际名字长度+1);MmCopyToCaller(FaceName, TextObj->TextFace, Count * sizeof(WCHAR)),
失败 →TEXTOBJ_UnlockText+ SetLastNtError return 0;- 若
Count > 0 && Count < fLen:FaceName[Count - 1] = '\0';
------只拷贝了部分名字时手工加终止符; ret = Count;
- 否则(只查询长度):
ret = fLen; TEXTOBJ_UnlockText(TextObj)return ret。
- 注意事项 :
- 返回值语义:成功时返回包括终止符在内的字符数 ;若缓冲区过小返回
0 或截断(Windows 行为:缓冲区太小返回 0 并设 ERROR_INSUFFICIENT_BUFFER,
此处是截断策略); bAliasName有/* FIXME: Handle bAliasName */注释,当前未处理;TextFace的来源见 2.4 节 TextIntRealizeFont:优先保留请求名,否则用
FreeType name 表本地化名。
- 返回值语义:成功时返回包括终止符在内的字符数 ;若缓冲区过小返回
3.13 NtGdiGetTextMetricsW(text.c:588)--- 度量结构(系统调用)
c
W32KAPI BOOL APIENTRY NtGdiGetTextMetricsW(_In_ HDC hDC,
_Out_ PTMW_INTERNAL pUnsafeTmwi, _In_ ULONG cj)
- 作用 :
GetTextMetricsW的系统调用入口(输出 TMW_INTERNAL 的可变
前缀------兼容不同版本的调用方)。 - 实现流程 :
cj <= sizeof(TMW_INTERNAL)才继续(拒绝超大 cj),否则 return FALSE;ftGdiGetTextMetricsW(hDC, &Tmwi)填充局部 TMW_INTERNAL(失败
return FALSE);IntTMWFixUp(hDC, &Tmwi)(光栅字体名补偿);_SEH2_TRY:ProbeForWrite(pUnsafeTmwi, cj, 1)+RtlCopyMemory( pUnsafeTmwi, &Tmwi, cj)按请求字节数拷贝;- 异常 →
SetLastNtError(_SEH2_GetExceptionCode()); _SEH2_YIELD(return FALSE); - return TRUE。
- 注意事项 :
- 输出结构是 TMW_INTERNAL(用户态 user32 的 GetTextMetricsW 再取其中
TextMetric 部分); cj允许小于 sizeof(TMW_INTERNAL)------老式调用方传 sizeof(TEXTMETRICW)
也能工作(只拷贝 TEXTMETRICW 大小);- 与 3.5 相比多了 SEH 用户缓冲保护与可变长度支持。
- 输出结构是 TMW_INTERNAL(用户态 user32 的 GetTextMetricsW 再取其中
3.14 文件内未定义、但同属本子系统的相关函数
text.h 中还声明了多个与本文件配套但定义在别处的函数,这里一并交代:
GreExtTextOutW(freetype.c:7300):DC_LockDc→IntExtTextOutW→
DC_UnlockDc,见第 4 节;IntGetCharDimensions(font.c:337):内部工具------用 52 个字母(a-zA-Z)
度量求平均字符宽度:(sz.cx / 26 + 1) / 2,同时通过
ftGdiGetTextMetricsW输出 TEXTMETRICW 与行高;返回"平均字符宽度",
供非文本控件(如静态框)估算尺寸;GreGetCharWidthW/GreGetCharABCWidthsW/GreGetGlyphIndicesW
(text.h 声明):字符宽度与字形索引服务,实现在 freetype.c 的 ft 层。
4. TextOut 完整调用链
4.1 总览
用户调用 TextOut/ExtTextOut 时,在 ReactOS 中的完整路径:
user32 TextOutW
└─ NtGdiExtTextOutW / NtGdiTextOut (系统调用)
└─ GreExtTextOutW (freetype.c:7300)
├─ DC_LockDc(hDC) // 锁 DC,失败→ERROR_INVALID_HANDLE
└─ IntExtTextOutW (freetype.c:6729) // 真正的工作
├─ 参数校验 (Count > 0xFFFF / Count>0 && String==NULL)
├─ PATH_IsPathOpen(dc->dclevel) ? ──► PATH_ExtTextOut (path.c:2415)
│ // 路径分支:字形轮廓记入路径
├─ DC_vPrepareDCsForBlit(dc) // 准备目标表面
├─ 无表面 (内存 DC 未选位图) → TRUE // 空转
├─ 起点:TA_UPDATECP ? ptlCurrent : (XStart,YStart)
├─ IntLPtoDP + ptlDCOrig → RealX/YStart64 (26.6 定点)
├─ 图形模式:GM_ADVANCED ? DC_pmxWorldToDevice : 单位阵
├─ 背景:ETO_OPAQUE 填充 (IntEngFillBox/IntEngFillPolygon)
├─ RealizeFontInit → FontGDI (FONTGDI_MAGIC 校验)
├─ 字形缓存键 Cache 构建 (Face/lfHeight/lfWidth/Emu/RenderMode)
├─ TextIntUpdateSize(TextObj, FontGDI, FALSE) // 设字号+charmap
├─ FontLink_Chain_Init // 字体链接链
├─ 变换:lfEscapement(IntEscapeMatrix)
│ × 世界变换(IntMatrixFromMx+FT_Matrix_Multiply)
│ → FT_Set_Transform
├─ 垂直对齐调整 (TA_TOP/TA_BASELINE/TA_BOTTOM 对起点修正)
├─ 需宽度?(ETO_OPAQUE | TA_CENTER|TA_RIGHT)
│ └─ IntGetTextDisposition → 对齐平移/背景矩形
├─ 主渲染循环 (逐字符, 见 4.3)
├─ TA_UPDATECP → ptlCurrent.x 更新 (CORE-19721)
├─ 下划线/删除线绘制 (lfUnderline/lfStrikeOut)
└─ LockWindowUpdate 边界累加 (DC_ACCUM_APP|DC_ACCUM_WMGR)
4.2 路径分支:PATH_ExtTextOut
当 DC 处于路径打开 状态(BeginPath 之后、EndPath 之前)时,
IntExtTextOutW 不在屏幕上渲染,而是把每个字符的字形轮廓追加到路径:
c
if (PATH_IsPathOpen(dc->dclevel))
return PATH_ExtTextOut(dc, XStart, YStart, fuOptions, lprc, String, Count, Dx);
PATH_ExtTextOut(file:///d:/reactos/win32ss/gdi/ntgdi/path.c#L2415)(path.c)
的流程:
PATH_LockPath(dc->dclevel.hPath);路径状态必须为 PATH_Open;count == 0直接成功;ETO_GLYPH_INDEX→GGO_GLYPH_INDEX标志转换;- 逐字符循环:
ftGdiGetGlyphOutline(dc, str[idx], ggo_flags(GGO_NATIVE), &gm, 0, NULL, &identity, TRUE)两遍调用 :第一遍取所需字节数(dwSize);
若返回 GDI_ERROR(位图字体)则continue(产生空路径,与 Windows
行为一致);- dwSize > 0 时分配 TAG_PATH 池,第二遍取轮廓数据;
PATH_add_outline(dc, pPath, x+offset.x, y+offset.y, outline, dwSize)
把 GGO_NATIVE 格式轮廓并入路径;- 笔偏移:有 Dx → 按 dx(ETO_PDY 时取 dxidx*2/dxidx* 2+1)累加;
无 Dx → 按gm.gmCellIncX/gmCellIncY(字形单元增量)累加;
PATH_UnlockPath返回。
这样 EndPath 之后可以用 StrokePath/FillPath 等对文本轮廓做画笔描边
或填充------这是"文本转路径"功能的实现基础。
4.3 渲染分支:IntExtTextOutW 主循环
渲染循环逐字符执行(freetype.c:6998 起):
for (i = 0; i < Count; ++i)
{
ch0 = *String++;
if (IS_HIGH_SURROGATE(ch0)) // 代理对 → 合成 UTF-32 码点
{ ++i; ch1 = *String++;
if (IS_LOW_SURROGATE(ch1)) ch0 = Utf32FromSurrogatePair(ch0, ch1); }
glyph_index = FontLink_Chain_FindGlyph(&Chain, &Cache, &face, ch0,
fuOptions & ETO_GLYPH_INDEX);
Cache.Hashed.GlyphIndex = glyph_index;
realglyph = IntGetRealGlyph(&Cache); // 字形缓存查找/光栅化
if (!realglyph) { bResult = FALSE; break; }
// 字距调整(无 Dx 时才自动应用 kerning)
if (use_kerning && previous && glyph_index && NULL == Dx)
{ FT_Get_Kerning(face, previous, glyph_index, 0, &delta);
X64 += delta.x; Y64 -= delta.y; }
glyphSize.cx = realglyph->bitmap.width;
glyphSize.cy = realglyph->bitmap.rows;
// CORE-11787 修复:空格字符位图宽为 0 时补空格宽度
if ((flTextAlign & TA_UPDATECP) && glyphSize.cx == 0 &&
(ch0 == L' ' || ch0 == 0xa0 /* nbsp */))
{ 临时解锁 FreeType 调 TextIntGetTextExtentPoint(dc,TextObj,L" ",1,...)
取空格宽 → glyphSize.cx;realglyph->left = 0; }
// 目标矩形(26.6 定点 → 像素,+32 舍入)
DestRect.left = ((X64 + 32) >> 6) + realglyph->left;
DestRect.right = DestRect.left + glyphSize.cx;
DestRect.top = ((Y64 + 32) >> 6) - realglyph->top;
DestRect.bottom = DestRect.top + glyphSize.cy;
if (glyphSize.cx && glyphSize.cy) // 有位图内容
{
hbmGlyph = EngCreateBitmap(glyphSize, realglyph->bitmap.pitch,
BMF_8BPP, BMF_TOPDOWN,
realglyph->bitmap.buffer);
psoGlyph = EngLockSurface((HSURF)hbmGlyph);
// 用字体位图作为掩码(mask),以文本画刷 eboText 刷到目标
if (lprc && (fuOptions & ETO_CLIPPED)) // 裁剪到 lprc 右/下边界
{ DestRect.right = min(DestRect.right, lprc->right);
DestRect.bottom = min(DestRect.bottom, lprc->bottom);
if (DestRect.right >= lprc->right) DoBreak = TRUE; }
if (!IntEngMaskBlt(psoDest, psoGlyph, (CLIPOBJ*)&dc->co,
&exloRGB2Dst.xlo, &exloDst2RGB.xlo,
&DestRect, (PPOINTL)&MaskRect,
&dc->eboText.BrushObject, &g_PointZero))
DPRINT1("Failed to MaskBlt a glyph!\n");
EngUnlockSurface(psoGlyph);
EngDeleteSurface((HSURF)hbmGlyph);
}
if (DoBreak) break;
// 移动笔位置:
// 无 Dx → 累加字形 advance(advance.x >> 10 即 /64 转像素)
// ETO_PDY → Dx[2i]/Dx[2i+1] 为 (x,y) 对(复杂文本书写)
// 普通 Dx → 逐字符显式 x 偏移(y 恒 0)
// 有变换时 Dx 向量先经 FT_Vector_Transform 折算
...
}
/* 循环结束后:*/
if ((flTextAlign & TA_UPDATECP) && String) // CORE-19721:空串不更新 CP
pdcattr->ptlCurrent.x = DestRect.right - dc->ptlDCOrig.x;
- 每字符都要经过
FontLink_Chain_FindGlyph+IntGetRealGlyph,后者先在
全局字形缓存(g_FontCacheListHead,按 Face/GlyphIndex/lfHeight/lfWidth/
AspectValue/matTransform 哈希匹配,见IntFindGlyphCache/IntGetHash)中
查找,未命中才调用 FreeType 光栅化并加入缓存; - 位图是"零拷贝"包装:
EngCreateBitmap直接指向realglyph->bitmap.buffer
(8bpp 灰度/掩码),随后IntEngMaskBlt以文本画刷着色,因此字形形状
与颜色分离; ETO_CLIPPED的矩形在循环前已通过IntLPtoDP转换到设备坐标并加上
ptlDCOrig。
4.4 渲染分支:起点、变换与背景细节
起点(26.6 定点):
c
if (pdcattr->flTextAlign & TA_UPDATECP) { Start = ptlCurrent; }
else { Start = {XStart, YStart}; }
IntLPtoDP(dc, &Start, 1); // 逻辑→设备
RealXStart64 = ((LONGLONG)Start.x + dc->ptlDCOrig.x) << 6;
RealYStart64 = ((LONGLONG)Start.y + dc->ptlDCOrig.y) << 6;
TA_UPDATECP模式忽略传入坐标,从ptlCurrent(上次 CP)继续;- 坐标经过"LP→DP + 表面原点偏移"后左移 6 位进入 26.6 定点,全程定点计算
保证亚像素精度。
世界变换:
c
if (pdcattr->iGraphicsMode == GM_ADVANCED)
pmxWorldToDevice = DC_pmxWorldToDevice(dc);
else
pmxWorldToDevice = (PMATRIX)&gmxWorldToDeviceDefault;
/* 字体旋转(lfEscapement)与世界变换复合 */
if (FT_IS_SCALABLE(face) && plf->lfEscapement != 0)
IntEscapeMatrix(&Cache.Hashed.matTransform, plf->lfEscapement);
IntMatrixFromMx(&mat, pmxWorldToDevice);
FT_Matrix_Multiply(&mat, &Cache.Hashed.matTransform);
FT_Set_Transform(face, &Cache.Hashed.matTransform, NULL);
bNoTransform = (mat.xy == 0) && (mat.yx == 0) &&
(mat.xx == (1 << 16)) && (mat.yy == (1 << 16));
- GM_COMPATIBLE 模式用单位矩阵,GM_ADVANCED 用 DC 的世界变换;
bNoTransform标志用于 Dx 向量/背景矩形是否需经 FT 变换的快速判定。
垂直对齐(VALIGN) :先算出变换后的 ascent/descent 向量,再按对齐模式
修正起点:
c
vecAscent64 = (0, FontGDI->tmAscent << 6); FT_Vector_Transform(...)
vecDescent64 = (0, -FontGDI->tmDescent << 6); FT_Vector_Transform(...)
TA_BASELINE → 不动
TA_BOTTOM → RealXStart64 -= vecDescent64.x; RealYStart64 += vecDescent64.y;
TA_TOP → RealXStart64 -= vecAscent64.x; RealYStart64 += vecAscent64.y;
背景处理 :ETO_OPAQUE(显式 lprc 或 jBkMode == OPAQUE 隐式)时,
先 IntEngFillBox(无旋转)或 IntEngFillPolygon(有旋转,四边形)用
eboBackground.BrushObject 填满文本矩形;矩形尺寸来自
IntGetTextDisposition 计算出的 DeltaX64/DeltaY64。随后进入主循环时
EXLATEOBJ_vInitialize 建立调色板转换(RGB↔目标表面),并检查
DIRTY_TEXT 更新文本画刷。
下划线/删除线 :lfUnderline/lfStrikeOut 在主循环后单独绘制------按
face->underline_position/underline_thickness(经 y_ppem/units_per_EM
换算)生成一条/两条线(删除线在 -(tmAscent<<6)/3 高度),无旋转时用
IntEngFillBox,有旋转时用 IntEngFillPolygon,画刷为 eboText。
边界累加(LockWindowUpdate) :当 dc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR)
且 Count > 0 时,用最终笔位置与起点差计算文本整体包围盒,调用
IntUpdateBoundsRect 累加到窗口更新区域------保证"文本越界刷新"正确。
5. 度量实现细节(freetype.c)
5.1 TextIntGetTextExtentPoint(freetype.c:5028)
GreGetTextExtentW/ExW、NtGdiGetTextExtent/ExW、IntGetCharDimensions
以及 IntExtTextOutW 中的空格宽补偿,最终都汇聚到这一个函数:
c
BOOL FASTCALL TextIntGetTextExtentPoint(_In_ PDC dc, _In_ PTEXTOBJ TextObj,
_In_reads_(Count) PCWCH String, _In_ INT Count, _In_ ULONG MaxExtent,
_Out_ PINT Fit, _Out_writes_to_opt_(Count, *Fit) PINT Dx,
_Out_ PSIZE Size, _In_ FLONG fl)
实现流程:
FontGDI = ObjToGDI(TextObj->Font, FONT);*Fit = 0(若 Fit 非空);- 从
TextObj->logfont.elfEnumLogfontEx.elfLogFont取 lfHeight/lfWidth/
lfWeight/lfItalic/lfEscapement/lfOrientation; - 构造字形缓存键:
Cache.Hashed.Face = FontGDI->SharedFace->Face、
lfHeight、lfWidth、Emu.Bold = EMUBOLD_NEEDED(OriginalWeight, lfWeight)、
Emu.Italic = (lfItalic && !OriginalItalic); - 竖排检测(tategaki) :
IntNormalizeAngle(lfEscapement - lfOrientation)
为 900 或 2700(即 90°/270°)时bVerticalWriting = TRUE------此时
cx/cy 互换(见第 9 步); - 渲染模式:
IntIsFontRenderingEnabled()为真 →IntGetFontRenderMode(plf)
(按渲染开关),否则强制FT_RENDER_MODE_MONO(单色); IntLockFreeType();TextIntUpdateSize(TextObj, FontGDI, FALSE)(确保
字号与 charmap 已设置);matTransform = identityMat;
FT_Set_Transform(face, NULL, NULL)------注释明示:
GetTextExtentPoint32 忽略 lfEscapement 与 XFORM ;
FontLink_Chain_Init(&Chain, TextObj, face);
use_kerning = FT_HAS_KERNING(face);- 逐字符循环(i = 0...Count-1):
- 代理对处理(同 4.3);
FontLink_Chain_FindGlyph(&Chain, &Cache, &face, ch0, (fl & GTEF_INDICES))
------GTEF_INDICES 表示 String 已是字形索引;realglyph = IntGetRealGlyph(&Cache),失败则 break;- 字距:
use_kerning && previous && glyph_index→FT_Get_Kerning(face, previous, glyph_index, 0, &delta),TotalWidth64 += delta.x; TotalWidth64 += realglyph->root.advance.x >> 10;
(advance 是 16.16 定点,>> 10变为 26.6 定点,保持 64 位累加以防
溢出);- Fit:
((TotalWidth64 + 32) >> 6) <= MaxExtent时*Fit = i + 1
("在 MaxExtent 内能容纳到第几个字符",+32 是四舍五入); - Dx:
Dx[i] = (TotalWidth64 + 32) >> 6;(逐字符累计像素宽); previous = glyph_index;
- 解锁 FreeType;
ascender = FontGDI->tmAscent(基线以上,正)、
descender = FontGDI->tmDescent(基线以下,正); - 结果装配:
- 竖排:
Size->cx = ascender + descender;(宽=字高)
Size->cy = (TotalWidth64 + 32) >> 6;(高=总宽) - 横排:
Size->cx = (TotalWidth64 + 32) >> 6;
Size->cy = ascender + descender;
- 竖排:
- return TRUE。
要点:
- 度量精度 = 逐字形 advance 累加(26.6 定点)+ 字距调整,与渲染循环
(4.3)中"无 Dx 时 X64 += advance.x >> 10 + kerning"完全一致 ,保证
度量与渲染结果吻合; FontGDI->tmAscent/tmDescent在字体实现/更新尺寸时预计算,度量函数
无需再向 FreeType 查询面度量;- 与渲染循环的差异:度量路径把变换重置为单位阵(忽略 lfEscapement),
渲染路径则应用完整变换------这与 Windows 的 GetTextExtentPoint32 语义一致。
5.2 ftGdiGetTextMetricsW(freetype.c:5335)与 FillTM
c
BOOL FASTCALL ftGdiGetTextMetricsW(HDC hDC, PTMW_INTERNAL ptmwi)
流程:
ptmwi == NULL→ ERROR_INVALID_PARAMETER;RtlZeroMemory(ptmwi, ...);DC_LockDc(hDC)失败 → ERROR_INVALID_HANDLE;RealizeFontInit(pdcattr->hlfntNew)(实现字体,失败 → STATUS_INVALID_HANDLE);IntLockFreeType();Error = IntRequestFontSize(FontGDI, plf->lfWidth, plf->lfHeight)(设置像素尺寸;注释明示 GetTextMetrics 忽略 lfEscapement
与 XFORM);FT_Set_Transform(face, NULL, NULL);FT_Get_WinFNT_Header(face, &Win)(位图/WinFNT 字体才成功,Error≠0 表示
非 FNT 字体);pOS2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2);
pHori = FT_Get_Sfnt_Table(face, ft_sfnt_hhea);!pOS2 && Error→ "Can't find OS/2 table - not TT font?" →
STATUS_INTERNAL_ERROR;!pHori && Error同理(Error 非零说明不是
WinFNT 字体,又无 OS/2/hhea 表,则无法填充);- 成功 →
FillTM(&ptmwi->TextMetric, FontGDI, pOS2, pHori, (Error ? NULL : &Win));/* FIXME: Fill Diff member */; - 解锁、
TEXTOBJ_UnlockText、DC_UnlockDc;失败路径SetLastNtError。
FillTM(freetype.c:2716) 是 TEXTMETRICW 填充的核心(见 2.3 字段表),
补充几点实现细节:
- 光栅(WinFNT)路径 :直接映射
pixel_height/ascent/internal_leading/ avg_width/max_width/...,tmDescent = tmHeight − tmAscent;tmWeight/
tmItalic/tmUnderlined/tmStruckOut 取自 FONTGDI 的 Request* 字段;
tmCharSet = FontGDI->CharSet; - TrueType 路径 :
- WinAscent/WinDescent 之和为 0 时退回 hhea 的 Ascender/Descender;
tmExternalLeading = MAX(0, LineGap − ((WinAscent+WinDescent) − (Ascender−Descender)))(严格按 MSDN 公式,经 YScale 换算);tmAveCharWidth:请求了 lfWidth 就直接用 lfWidth,否则用
OS/2xAvgCharWidth(XScale 换算),为 0 时强制为 1;tmMaxCharWidth用Face->max_advance_width;- Symbol 字体(有 symbol charmap 或 usFirstCharIndex 落在 0xF000-0xF0FF)
时 tmFirstChar=0、tmLastChar=0xF0FF(Baltik 代码页 1257 为 0xF8FD)、
tmBreakChar=0x20、tmDefaultChar=0x1F; - tmBreakChar/tmDefaultChar 的派生规则:
usFirstCharIndex <= 1→ break = first+2;> 0xFF→ break = 0x20;
否则 break = first;default = break − 1; tmPitchAndFamily:固定宽度(FT_IS_FIXED_WIDTH)→ 0;可变宽度 →
_TMPF_VARIABLE_PITCH,但 panose PAN_PROP_MONOSPACED 时置 0;
再按 panose 家族类型叠加 FF_SCRIPT/FF_DECORATIVE/FF_ROMAN/FF_SWISS/
FF_MODERN/FF_DONTCARE(无衬线→FF_SWISS,有衬线→FF_ROMAN,
固定→FF_MODERN,其余→FF_DONTCARE);- 末尾
FT_IS_SCALABLE → |= TMPF_VECTOR、FT_IS_SFNT → |= TMPF_TRUETYPE
------这正是 IntTMWFixUp 需要"纠错"的两个位(见 3.1)。
5.3 ftGdiGetTextCharsetInfo(freetype.c:5148)
供 NtGdiGetCharSet 与 NtGdiGetTextCharsetInfo 共用:
RealizeFontInit(hFont)→Face = FontGdi->SharedFace->Face;FT_Get_Sfnt_Table(face, ft_sfnt_os2):把 OS/2 的
ulCodePageRange1/2→fsCsb[0/1]、ulUnicodeRange1..4→fsUsb[0..3];
OS/2 version 0 时按首个字符 < 0x100 判定 FS_LATIN1 或 FS_SYMBOL;- 若
fsCsb[0] == 0(无代码页范围):遍历face->num_charmaps按
FT_ENCODING_UNICODE/APPLE_ROMAN → FS_LATIN1、FT_ENCODING_MS_SYMBOL →
FS_SYMBOL 兜底; - 字符集推断:
- 先
RtlGetDefaultCodePage(&usACP, &usOEM),IntTranslateCharsetInfo( &cp, &csi, TCI_SRCCODEPAGE),若系统 ACP 对应的 fsCsb 与字体 fsCsb
有交集(csi.fs.fsCsb[0] & fs.fsCsb[0])→ 返回系统字符集; - 否则逐位扫描
fs.fsCsb[0](MAXTCIINDEX 位),
IntTranslateCharsetInfo(&fs0, &csi, TCI_SRCFONTSIG)命中即返回;
- 先
- 返回值
MAKELONG(csi.ciACP, csi.ciCharset)------低字 ACP、高字字符集,
与 3.6/3.8 的取字逻辑对应。
6. 调用链(mermaid)
#mermaid-svg-EjfZAhzr8peS2PKP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EjfZAhzr8peS2PKP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EjfZAhzr8peS2PKP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EjfZAhzr8peS2PKP .error-icon{fill:#552222;}#mermaid-svg-EjfZAhzr8peS2PKP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EjfZAhzr8peS2PKP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EjfZAhzr8peS2PKP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EjfZAhzr8peS2PKP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EjfZAhzr8peS2PKP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EjfZAhzr8peS2PKP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EjfZAhzr8peS2PKP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EjfZAhzr8peS2PKP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EjfZAhzr8peS2PKP .marker.cross{stroke:#333333;}#mermaid-svg-EjfZAhzr8peS2PKP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EjfZAhzr8peS2PKP p{margin:0;}#mermaid-svg-EjfZAhzr8peS2PKP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EjfZAhzr8peS2PKP .cluster-label text{fill:#333;}#mermaid-svg-EjfZAhzr8peS2PKP .cluster-label span{color:#333;}#mermaid-svg-EjfZAhzr8peS2PKP .cluster-label span p{background-color:transparent;}#mermaid-svg-EjfZAhzr8peS2PKP .label text,#mermaid-svg-EjfZAhzr8peS2PKP span{fill:#333;color:#333;}#mermaid-svg-EjfZAhzr8peS2PKP .node rect,#mermaid-svg-EjfZAhzr8peS2PKP .node circle,#mermaid-svg-EjfZAhzr8peS2PKP .node ellipse,#mermaid-svg-EjfZAhzr8peS2PKP .node polygon,#mermaid-svg-EjfZAhzr8peS2PKP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EjfZAhzr8peS2PKP .rough-node .label text,#mermaid-svg-EjfZAhzr8peS2PKP .node .label text,#mermaid-svg-EjfZAhzr8peS2PKP .image-shape .label,#mermaid-svg-EjfZAhzr8peS2PKP .icon-shape .label{text-anchor:middle;}#mermaid-svg-EjfZAhzr8peS2PKP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EjfZAhzr8peS2PKP .rough-node .label,#mermaid-svg-EjfZAhzr8peS2PKP .node .label,#mermaid-svg-EjfZAhzr8peS2PKP .image-shape .label,#mermaid-svg-EjfZAhzr8peS2PKP .icon-shape .label{text-align:center;}#mermaid-svg-EjfZAhzr8peS2PKP .node.clickable{cursor:pointer;}#mermaid-svg-EjfZAhzr8peS2PKP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EjfZAhzr8peS2PKP .arrowheadPath{fill:#333333;}#mermaid-svg-EjfZAhzr8peS2PKP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EjfZAhzr8peS2PKP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EjfZAhzr8peS2PKP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EjfZAhzr8peS2PKP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EjfZAhzr8peS2PKP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EjfZAhzr8peS2PKP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EjfZAhzr8peS2PKP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EjfZAhzr8peS2PKP .cluster text{fill:#333;}#mermaid-svg-EjfZAhzr8peS2PKP .cluster span{color:#333;}#mermaid-svg-EjfZAhzr8peS2PKP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EjfZAhzr8peS2PKP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EjfZAhzr8peS2PKP rect.text{fill:none;stroke-width:0;}#mermaid-svg-EjfZAhzr8peS2PKP .icon-shape,#mermaid-svg-EjfZAhzr8peS2PKP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EjfZAhzr8peS2PKP .icon-shape p,#mermaid-svg-EjfZAhzr8peS2PKP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EjfZAhzr8peS2PKP .icon-shape .label rect,#mermaid-svg-EjfZAhzr8peS2PKP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EjfZAhzr8peS2PKP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EjfZAhzr8peS2PKP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EjfZAhzr8peS2PKP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} font.c
freetype.c
text.c
系统调用
用户态
路径打开
需要宽度
仅声明
user32 TextOutW
user32 GetTextExtentPoint32W
user32 GetTextMetricsW
user32 GetTextFaceW
user32 GetTextCharsetInfo
NtGdiExtTextOutW
NtGdiGetTextExtent / GetTextExtentExW
NtGdiGetTextMetricsW
NtGdiGetTextFaceW
NtGdiGetTextCharsetInfo
NtGdiGetCharSet
NtGdiGetRasterizerCaps
GreTextOutW
GreGetTextExtentW / GreGetTextExtentExW
GreGetTextMetricsW
IntTMWFixUp
GreExtTextOutW
IntExtTextOutW
PATH_ExtTextOut
TextIntGetTextExtentPoint
ftGdiGetTextMetricsW
FillTM
ftGdiGetTextCharsetInfo
ftGdiGetRasterizerCaps
IntGetTextDisposition
IntGetRealGlyph
FontLink_Chain_FindGlyph
RealizeFontInit
TextIntRealizeFont
IntGetCharDimensions
7. 与 freetype / font / eng-string 的关系
7.1 模块分工
| 模块 | 文件 | 职责 | 与 text.c 的接口 |
|---|---|---|---|
| 文本入口 | text.c | NtGdi*/Gre* 系统调用与转发、SEH、池管理、光栅字体名补偿 | 本文件 |
| FreeType 封装 | freetype.c | 字形光栅化/缓存(IntGetRealGlyph)、文本输出(IntExtTextOutW)、度量(TextIntGetTextExtentPoint、ftGdiGetTextMetricsW、FillTM)、字符集(ftGdiGetTextCharsetInfo)、能力(ftGdiGetRasterizerCaps)、字体实现(TextIntRealizeFont)、字体大小(TextIntUpdateSize/IntRequestFontSize) | text.c 只调用 ft*/TextInt* |
| 字体管理 | font.c | CreateFontIndirect(GreCreateFontIndirectW)、RealizeFontInit、字体枚举/注册(NtGdiEnumFonts、AddFontResource)、IntGetCharDimensions、IntGetFontLanguageInfo | text.c 调用 RealizeFontInit;font.c 复用 text.c 的 TextIntGetTextExtentPoint 做枚举度量 |
| 引擎层 | eng/ | SURFOBJ、FONTOBJ、EXLATEOBJ、EngCreateBitmap/EngLockSurface/EngDeleteSurface、EngSetLastError | IntExtTextOutW 用 Eng* 创建字形位图表面 |
| 路径 | path.c | PATH_ExtTextOut、PATH_add_outline | IntExtTextOutW 路径分支 |
| 内部文本对象 | text.h | TEXTOBJ/LFONT、STRGDI、EXSTROBJ 标志、锁定协议、ft*/TextInt* 原型 | 契约头 |
7.2 eng/string.c(STROBJ)的预留关系
text.h 顶部定义了 EXSTROBJ 标志 与 STRGDI 扩展 STROBJ 结构 :
TO_MEM_ALLOCATED、TO_ALL_PTRS_VALID、TO_VALID、TSIM_UNDERLINE1/2、
TO_HIGHRESTEXT、TO_BITMAPS、TO_SYS_PARTITION 等;STRGDI 内含
STROBJ 头 + prfo(PFONTGDI)、pgpos(PGLYPHPOS)、fxExtent/fxExtra/
fxBreakExtra、dwCodePage、分区指针、基线调整等。
这是为 EngTextOut / DrvTextOut(引擎文本输出)路径 预留的接口:经典
GDI 设计中,显示驱动通过 DrvTextOut(STROBJ*) 消费字形定位数组
(GLYPHPOS),fxExtra/fxBreakExtra 即"字符间额外空白/断字额外空白"
(对应 NtGdiSetTextJustification 的 lBreakExtra/cBreak)。ReactOS 当前
渲染路径(IntExtTextOutW)并未 走 STROBJ/DrvTextOut,而是直接在
win32k 内用 IntEngMaskBlt 完成绘制(见 freetype.c 顶部的 FIXME 注释:
"Call EngTextOut, which does the real work (calling DrvTextOut where
appropriate)"),因此 STRGDI 属于"预留/未完全启用"结构------但它是理解
text.h 与未来驱动化文本输出方向的钥匙。
7.3 关键协同点
- 惰性实现共享 :text.c 的所有入口都先
RealizeFontInit,它与 font.c
的字体枚举、freetype.c 的 TextIntRealizeFont 共同构成"逻辑字体 →
物理字体"的唯一通道; - 度量复用 :font.c 的
IntGetCharDimensions、IntExtTextOutW 的空格
宽度补偿都调用 text.c 同族的TextIntGetTextExtentPoint,避免出现
三套度量口径; - 锁序约定 :DC 锁(DC_LockDc)在外,字体对象锁(TEXTOBJ_LockText)
在内,FreeType 全局锁(IntLockFreeType)最内------text.c 的函数严格按
此序加锁/解锁,避免死锁; - 用户态安全 :凡带 Unsafe* 前缀参数的系统调用入口,都在持锁前完成
MmCopyFromCaller到内核池、在持锁后通过MmCopyToCaller回写,配合
ProbeForWrite与_SEH2_*异常处理。
8. 源码索引
8.1 text.c 函数索引(含行号,2026-08 快照)
| 行号 | 函数 | 类型 |
|---|---|---|
| 28 | IntTMWFixUp |
内部(光栅字体名补偿) |
| 59 | GreTextOutW |
Gre |
| 77 | GreGetTextExtentW |
Gre |
| 133 | GreGetTextExtentExW |
Gre |
| 191 | GreGetTextMetricsW |
Gre |
| 204 | NtGdiGetCharSet |
Nt |
| 226 | NtGdiGetRasterizerCaps |
Nt |
| 265 | NtGdiGetTextCharsetInfo |
Nt |
| 324 | NtGdiGetTextExtentExW |
Nt |
| 489 | NtGdiGetTextExtent |
Nt |
| 501 | NtGdiSetTextJustification |
Nt |
| 529 | NtGdiGetTextFaceW |
Nt |
| 588 | NtGdiGetTextMetricsW |
Nt |
8.2 关联文件索引
| 文件 | 关键内容 |
|---|---|
| text.c(file:///d:/reactos/win32ss/gdi/ntgdi/text.c) | 本文主体:13 个入口 + 1 个内部函数 |
| text.h(file:///d:/reactos/win32ss/gdi/ntgdi/text.h) | TEXTOBJ/LFONT、STRGDI、EXSTROBJ 标志、锁定协议、ft*/TextInt*/Gre* 声明 |
| freetype.c(file:///d:/reactos/win32ss/gdi/ntgdi/freetype.c) | IntExtTextOutW(6729)、TextIntGetTextExtentPoint(5028)、ftGdiGetTextMetricsW(5335)、FillTM(2716)、ftGdiGetTextCharsetInfo(5148)、ftGdiGetRasterizerCaps(3630)、TextIntRealizeFont(6050)、IntGetTextDisposition(6643)、TextIntUpdateSize(4269)、IntRequestFontSize(4016)、GreExtTextOutW(7300) |
| font.c(file:///d:/reactos/win32ss/gdi/ntgdi/font.c) | RealizeFontInit(431)、IntGetCharDimensions(337)、IntGetFontLanguageInfo(382)、GreCreateFontIndirectW |
| engobjects.h(file:///d:/reactos/win32ss/gdi/eng/engobjects.h) | FONTGDI 结构(139)、FONTGDI_MAGIC、SHARED_FACE |
| ntgdityp.h(file:///d:/reactos/win32ss/include/ntgdityp.h) | TMW_INTERNAL(368)、NTMW_INTERNAL、ENUMFONTDATAW |
| path.c(file:///d:/reactos/win32ss/gdi/ntgdi/path.c) | PATH_ExtTextOut(2415)、PATH_add_outline |
8.3 与系列分册的对应
| 分册 | 主题 | 与本文关系 |
|---|---|---|
| 《分析_16》 | freetype.c 字形渲染 | IntExtTextOutW 光栅化主体 |
| 《分析_31》 | win32k 图形系统入口表 | text.c 的 8 个 NtGdi* 入口来源 |
| 《分析_33》 | font.c 字体管理 | RealizeFontInit/TextIntRealizeFont |
| 《分析_37》 | path.c 路径 | PATH_ExtTextOut 路径分支 |
| 《分析_40》(本文) | 文本子系统 text.c | 本文件 |
关联模块: freetype.c(渲染/度量)、font.c(实现/枚举)、eng/string.c
(STROBJ/STRGDI 预留)、path.c(文本转路径)、engobjects.h(FONTGDI)。
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/text.c及关联模块(freetype.c / font.c / text.h / engobjects.h / ntgdityp.h / path.c)
逐函数核实分析(2026 年 8 月快照)。
关键修复编号:CORE-1091(光栅字体度量)、CORE-11787(空格宽度)、
CORE-19721(TA_UPDATECP 空串不更新 CP)。