ReactOS 图形系统分析(40):文本子系统 — text.c

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 设计动机

  1. 内核/用户分层 :与 win32k 其他子系统一致,text.c 把"系统调用入口

    (NtGdi*,处理 SEH 探测、池分配、MmCopyFromCaller/ToCaller 用户态内存

    拷贝)"与"内部实现(Gre*,只操作内核态对象,直接接受已验证指针)"分离。

    user32 从不直接调用 Gre*,而是通过系统调用进入 NtGdi*;win32k 内部

    (如 win32ss 其他模块)才直接调用 Gre*。

  2. 与 FreeType 引擎解耦 :text.c 完全不感知 FreeType 的存在。所有引擎

    相关操作都封装在 freetype.c 的 ft* 前缀函数中(ftGdiGetTextMetricsW

    ftGdiGetTextCharsetInfoftGdiGetRasterizerCaps),并通过 text.h 的

    函数原型把契约固定下来。这样未来替换字体引擎(例如支持位图光栅字体)

    时只需替换 freetype.c 一侧。

  3. 惰性实现(RealizeFontInit) :字体对象(LFONT/TEXTOBJ)在

    CreateFontIndirect 时并不立即与某个物理字体(FONTGDI)绑定------绑定推迟

    到第一次需要实际度量/渲染时(RealizeFontInitTextIntRealizeFont),

    期间完成字体替换、匹配打分、字体名解析等重活。这一"按需实现"模式使

    每次 TextOut/GetTextExtent 前的准备工作尽量最小化(见 2.4 节)。

  4. CORE-1091 的历史包袱IntTMWFixUp 注释明确说明------ReactOS 目前

    不支持光栅字体,ftGdiGetTextMetricsW 永远返回 TrueType 字体标志

    (TMPF_VECTOR | TMPF_TRUETYPE),这在编辑框中会造成布局问题。因此 text.c

    用一个"光栅字体名黑名单"来补偿(见 3.1 节)。

  5. 统一度量入口 :文本输出(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)

TEXTOBJLFONT同一个结构体 的两个别名------"逻辑字体对象"

(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
    lfWidthlfWeightlfItaliclfEscapementlfOrientation 等字段。
  • 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_ShareLockFontGDIOBJ_ReferenceObjectByHandle(hfont, GDIObjType_LFONT_TYPE)
  • LFONT_ShareUnlockFontGDIOBJ_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,供
    TextIntGetTextExtentPointIntExtTextOutW 直接使用(避免反复向
    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);
  • 内核内部接口(ftGdiGetTextMetricsWNtGdiGetTextMetricsW
    GreGetTextMetricsWIntGetCharDimensions)一律使用 TMW_INTERNAL 作为
    载体,为将来填充 Diff(Windows 的 TMDIFF 结构)预留空间;
  • NtGdiGetTextMetricsWcj <= 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      // 标记已实现

实现流程要点:

  1. 惰性CreateFontIndirect 只创建 TEXTOBJ,不选字体;直到
    TextOut/GetTextMetrics 等调用 RealizeFontInit 时才匹配字体。
  2. 两次匹配 :先查当前进程的私有字体表,再查全局系统字体表;
    FindBestFontFromList 按惩罚分(GetFontPenalty)选择最优字体。
  3. 请求参数拷贝RequestWeight/RequestItalic/... 在实现时从 LOGFONTW
    拷入 FONTGDI,供 FillTM 与光栅化使用;OriginalWeight/OriginalItalic
    保留字体本身属性,用于加粗/斜体模拟判定(EMUBOLD_NEEDED)。
  4. 初始化后置位 TEXTOBJECT_INIT,下次 RealizeFontInit 直接短路返回。
  5. 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)。
  • 实现流程
    1. NtGdiGetDCObject(hDC, GDI_OBJECT_TYPE_FONT) 取当前字体句柄;
    2. GreGetObject(hCurrentFont, sizeof(LOGFONTW), &lf) 取 LOGFONTW;
    3. _wcsicmp 逐一比对 lf.lfFaceName 与 8 个光栅字体名:
      CourierFixedSysHelvMS Sans SerifMS SerifSystem
      TerminalTms Rmn
    4. 命中则 ptm->TextMetric.tmPitchAndFamily &= ~(TMPF_TRUETYPE|TMPF_VECTOR)
  • 使用方式GreGetTextMetricsWNtGdiGetTextMetricsW
    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 节。

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 = 0
    • GetTextExtentPointW = 1
  • 实现流程
    1. cwc == 0 短路:psize->cx = 0; psize->cy = 0; return TRUE;
      (空字符串直接返回零尺寸,不锁 DC);
    2. DC_LockDc(hDC),失败 → EngSetLastError(ERROR_INVALID_HANDLE),
      return FALSE;
    3. RealizeFontInit(pdcattr->hlfntNew) 实现当前字体;
    4. 成功 → TextIntGetTextExtentPoint(pdc, TextObj, lpwsz, cwc, 0, NULL, NULL, psize, flOpts)------MaxExtent=0、Fit=NULL、Dx=NULL(非 Ex 版本
      不需要逐字符宽度);随后 TEXTOBJ_UnlockText(TextObj)
    5. 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(按字形索引)
  • 实现流程
    1. 参数校验:(!String && Count) || !pSizeEngSetLastError( ERROR_INVALID_PARAMETER),return FALSE;
    2. Count == 0 短路:if (Fit) *Fit = 0; return TRUE;(注意:源码中
      此分支 Fit = 0 的写法不合法------实际是 if ( Fit ) *Fit = 0;,即把
      用户缓冲区清零后成功返回);
    3. DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE;
    4. RealizeFontInit(pdcattr->hlfntNew)
    5. 成功 → TextIntGetTextExtentPoint(pdc, TextObj, String, Count, MaxExtent, (PINT)Fit, (PINT)Dx, pSize, fl)(PULONG 强转为 PINT,
      因为内部接口按 INT 处理);
    6. TEXTOBJ_UnlockTextDC_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 行):
    1. 栈上建 TMW_INTERNAL tmwi
    2. ftGdiGetTextMetricsW(hdc, &tmwi) 失败 → return FALSE;
    3. IntTMWFixUp(hdc, &tmwi)(光栅字体名补偿,见 3.1);
    4. *lptm = tmwi.TextMetric; 只拷贝 TEXTMETRICW 部分;
    5. 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 属性缓存。
  • 实现流程
    1. DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE, return 0;
    2. cscp = ftGdiGetTextCharsetInfo(Dc, NULL, 0)(只查字符集,不取签名);
    3. 缓存:pdcattr->iCS_CP = cscp;
    4. pdcattr->ulDirty_ &= ~DIRTY_CHARSET;(清除"字符集脏"标志,此后
      GetTextCharsetInfo 的快捷路径可命中缓存);
    5. 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)。
  • 实现流程
    1. 前置:praststat && cjBytes 不成立 → return FALSE(不设错误码);
    2. cjBytes >= sizeof(RASTERIZER_STATUS) 则截断为
      sizeof(RASTERIZER_STATUS)(避免越界写);
    3. ftGdiGetRasterizerCaps(&rsSafe) 填局部 RASTERIZER_STATUS------见
      freetype.c:3630:nSize = sizeof(RASTERIZER_STATUS)
      wFlags = TT_AVAILABLE | TT_ENABLED(TrueType 光栅化器可用且启用)、
      nLanguageID = gusLanguageID
    4. _SEH2_TRYProbeForWrite(praststat, sizeof(RASTERIZER_STATUS), 1)
      探测用户缓冲,RtlCopyMemory(praststat, &rsSafe, cjBytes) 按截断后
      的字节数拷贝;
    5. 异常 → SetLastNtError(Status) return FALSE;
    6. 成功 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)。
  • 实现流程
    1. DC_LockDc(hdc),失败 → ERROR_INVALID_HANDLE, return DEFAULT_CHARSET;
    2. lpSig == NULLpfsSafe = NULL(只查字符集);
    3. Ret = HIWORD(ftGdiGetTextCharsetInfo(Dc, pfsSafe, dwFlags))
      ------取高字为字符集(低字为 ACP);
    4. lpSig 非空:
      • Ret == DEFAULT_CHARSETRtlZeroMemory(pfsSafe, sizeof(FONTSIGNATURE))
        (字符集未知时签名清零);
      • _SEH2_TRYProbeForWrite(lpSig, sizeof(FONTSIGNATURE), 1) +
        RtlCopyMemory 拷贝签名到用户缓冲;
      • 异常 → SetLastNtError(Status) return DEFAULT_CHARSET;
    5. 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 的系统调用入口------带完整的用户态内存
    安全处理(探测、池拷贝、回写)。
  • 实现流程
    1. (LONG)Count < 0 → ERROR_INVALID_PARAMETER return FALSE
      (Count 是 ULONG,强转 LONG 判负以拒绝超大值);
    2. Count == 0 短路:Size = {0,0}MmCopyToCaller(UnsafeSize, &Size, sizeof(SIZE)),失败 → SetLastNtError return FALSE;成功 return TRUE;
    3. 分配字符串池:String = ExAllocatePoolWithTag(PagedPool, Count * sizeof(WCHAR), GDITAG_TEXT);失败 → ERROR_NOT_ENOUGH_MEMORY;
    4. UnsafeDx 非空:再分配 Dx = ExAllocatePoolWithTag(PagedPool, Count * sizeof(INT), GDITAG_TEXT)(失败则释放 String 并返回内存错误);
    5. MmCopyFromCaller(String, UnsafeString, Count * sizeof(WCHAR))
      把用户字符串拷入内核池(失败 → 释放两块池并 SetLastNtError);
    6. DC_LockDc(hDC)(失败 → 释放池,ERROR_INVALID_HANDLE);
    7. RealizeFontInit(pdcattr->hlfntNew)TextIntGetTextExtentPoint(dc, TextObj, String, Count, MaxExtent, NULL == UnsafeFit ? NULL : &Fit, Dx, &Size, fl)TEXTOBJ_UnlockTextDC_UnlockDc
    8. 释放 String 池;失败 → 释放 Dx 池 return FALSE;
    9. 回写:UnsafeFitMmCopyToCaller(UnsafeFit, &Fit, sizeof(INT))
      UnsafeDxMmCopyToCaller(UnsafeDx, Dx, Count * sizeof(INT))
      释放 Dx 池;最后 MmCopyToCaller(UnsafeSize, &Size, sizeof(SIZE))
    10. 任一回写失败 → 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",供文本两端对齐
    时在每个断字处均分空白。
  • 实现流程
    1. DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE return FALSE;
    2. pdcattr->lBreakExtra = BreakExtra;
    3. pdcattr->cBreak = BreakCount;
    4. DC_UnlockDc(pDc) return TRUE。
  • 注意事项
    • 纯状态写入,不触发任何重算;真正消费这两个字段的是
      IntExtTextOutW/IntGetTextDisposition 的断行/对齐逻辑
      (text.h STRGDI 中的 fxBreakExtra/fxExtra 概念与此呼应);
    • 注意与 TextOutTA_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")。
  • 实现流程
    1. DC_LockDc(hDC),失败 → ERROR_INVALID_HANDLE return FALSE;
    2. hFont = pdcattr->hlfntNew; 立即 DC_UnlockDc(Dc)(短持锁:只取
      字体句柄,后续在字体对象锁下工作);
    3. TextObj = RealizeFontInit(hFont); ------ 注意此处用 ASSERT(TextObj != NULL),假定实现必然成功;
    4. fLen = wcslen(TextObj->TextFace) + 1;(含终止符的字符数);
    5. FaceName 非空(需要拷贝):
      • Count = min(Count, fLen);(请求长度截断到实际名字长度+1);
      • MmCopyToCaller(FaceName, TextObj->TextFace, Count * sizeof(WCHAR))
        失败 → TEXTOBJ_UnlockText + SetLastNtError return 0;
      • Count > 0 && Count < fLenFaceName[Count - 1] = '\0';
        ------只拷贝了部分名字时手工加终止符;
      • ret = Count;
    6. 否则(只查询长度):ret = fLen;
    7. 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 的可变
    前缀------兼容不同版本的调用方)。
  • 实现流程
    1. cj <= sizeof(TMW_INTERNAL) 才继续(拒绝超大 cj),否则 return FALSE;
    2. ftGdiGetTextMetricsW(hDC, &Tmwi) 填充局部 TMW_INTERNAL(失败
      return FALSE);
    3. IntTMWFixUp(hDC, &Tmwi)(光栅字体名补偿);
    4. _SEH2_TRYProbeForWrite(pUnsafeTmwi, cj, 1) + RtlCopyMemory( pUnsafeTmwi, &Tmwi, cj) 按请求字节数拷贝;
    5. 异常 → SetLastNtError(_SEH2_GetExceptionCode()); _SEH2_YIELD(return FALSE);
    6. return TRUE。
  • 注意事项
    • 输出结构是 TMW_INTERNAL(用户态 user32 的 GetTextMetricsW 再取其中
      TextMetric 部分);
    • cj 允许小于 sizeof(TMW_INTERNAL)------老式调用方传 sizeof(TEXTMETRICW)
      也能工作(只拷贝 TEXTMETRICW 大小);
    • 与 3.5 相比多了 SEH 用户缓冲保护与可变长度支持。

3.14 文件内未定义、但同属本子系统的相关函数

text.h 中还声明了多个与本文件配套但定义在别处的函数,这里一并交代:

  • GreExtTextOutW(freetype.c:7300):DC_LockDcIntExtTextOutW
    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)

的流程:

  1. PATH_LockPath(dc->dclevel.hPath);路径状态必须为 PATH_Open;
  2. count == 0 直接成功;ETO_GLYPH_INDEXGGO_GLYPH_INDEX 标志转换;
  3. 逐字符循环:
    • 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(字形单元增量)累加;
  4. 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/ExWNtGdiGetTextExtent/ExWIntGetCharDimensions

以及 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)

实现流程

  1. FontGDI = ObjToGDI(TextObj->Font, FONT)*Fit = 0(若 Fit 非空);
  2. TextObj->logfont.elfEnumLogfontEx.elfLogFont 取 lfHeight/lfWidth/
    lfWeight/lfItalic/lfEscapement/lfOrientation;
  3. 构造字形缓存键:Cache.Hashed.Face = FontGDI->SharedFace->Face
    lfHeightlfWidthEmu.Bold = EMUBOLD_NEEDED(OriginalWeight, lfWeight)
    Emu.Italic = (lfItalic && !OriginalItalic)
  4. 竖排检测(tategaki)IntNormalizeAngle(lfEscapement - lfOrientation)
    为 900 或 2700(即 90°/270°)时 bVerticalWriting = TRUE------此时
    cx/cy 互换(见第 9 步);
  5. 渲染模式:IntIsFontRenderingEnabled() 为真 → IntGetFontRenderMode(plf)
    (按渲染开关),否则强制 FT_RENDER_MODE_MONO(单色);
  6. 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)
  7. 逐字符循环(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_indexFT_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;
  8. 解锁 FreeType;ascender = FontGDI->tmAscent(基线以上,正)、
    descender = FontGDI->tmDescent(基线以下,正);
  9. 结果装配:
    • 竖排:Size->cx = ascender + descender;(宽=字高)
      Size->cy = (TotalWidth64 + 32) >> 6;(高=总宽)
    • 横排:Size->cx = (TotalWidth64 + 32) >> 6;
      Size->cy = ascender + descender;
  10. 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)

流程

  1. ptmwi == NULL → ERROR_INVALID_PARAMETER;RtlZeroMemory(ptmwi, ...)
  2. DC_LockDc(hDC) 失败 → ERROR_INVALID_HANDLE;
  3. RealizeFontInit(pdcattr->hlfntNew)(实现字体,失败 → STATUS_INVALID_HANDLE);
  4. IntLockFreeType()Error = IntRequestFontSize(FontGDI, plf->lfWidth, plf->lfHeight)(设置像素尺寸;注释明示 GetTextMetrics 忽略 lfEscapement
    与 XFORM);FT_Set_Transform(face, NULL, NULL)
  5. 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)
  6. !pOS2 && Error → "Can't find OS/2 table - not TT font?" →
    STATUS_INTERNAL_ERROR;!pHori && Error 同理(Error 非零说明不是
    WinFNT 字体,又无 OS/2/hhea 表,则无法填充);
  7. 成功 → FillTM(&ptmwi->TextMetric, FontGDI, pOS2, pHori, (Error ? NULL : &Win))/* FIXME: Fill Diff member */
  8. 解锁、TEXTOBJ_UnlockTextDC_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/2 xAvgCharWidth(XScale 换算),为 0 时强制为 1;
    • tmMaxCharWidthFace->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_VECTORFT_IS_SFNT → |= TMPF_TRUETYPE
      ------这正是 IntTMWFixUp 需要"纠错"的两个位(见 3.1)。

5.3 ftGdiGetTextCharsetInfo(freetype.c:5148)

NtGdiGetCharSetNtGdiGetTextCharsetInfo 共用:

  1. RealizeFontInit(hFont)Face = FontGdi->SharedFace->Face
  2. FT_Get_Sfnt_Table(face, ft_sfnt_os2):把 OS/2 的
    ulCodePageRange1/2fsCsb[0/1]ulUnicodeRange1..4fsUsb[0..3]
    OS/2 version 0 时按首个字符 < 0x100 判定 FS_LATIN1 或 FS_SYMBOL;
  3. fsCsb[0] == 0(无代码页范围):遍历 face->num_charmaps
    FT_ENCODING_UNICODE/APPLE_ROMAN → FS_LATIN1、FT_ENCODING_MS_SYMBOL →
    FS_SYMBOL 兜底;
  4. 字符集推断:
    • 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) 命中即返回;
  5. 返回值 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_ALLOCATEDTO_ALL_PTRS_VALIDTO_VALIDTSIM_UNDERLINE1/2

TO_HIGHRESTEXTTO_BITMAPSTO_SYS_PARTITION 等;STRGDI 内含

STROBJ 头 + prfo(PFONTGDI)、pgpos(PGLYPHPOS)、fxExtent/fxExtra/

fxBreakExtradwCodePage、分区指针、基线调整等。

这是为 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 关键协同点

  1. 惰性实现共享 :text.c 的所有入口都先 RealizeFontInit,它与 font.c
    的字体枚举、freetype.c 的 TextIntRealizeFont 共同构成"逻辑字体 →
    物理字体"的唯一通道;
  2. 度量复用 :font.c 的 IntGetCharDimensions、IntExtTextOutW 的空格
    宽度补偿都调用 text.c 同族的 TextIntGetTextExtentPoint,避免出现
    三套度量口径;
  3. 锁序约定 :DC 锁(DC_LockDc)在外,字体对象锁(TEXTOBJ_LockText)
    在内,FreeType 全局锁(IntLockFreeType)最内------text.c 的函数严格按
    此序加锁/解锁,避免死锁;
  4. 用户态安全 :凡带 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)。

相关推荐
我不是疯子是傻子1 小时前
Qt CAN通信周期发送抖动?实测定时器精度校准与时间戳补偿方案
开发语言·数据库·qt
IT爱学堂2 小时前
尚硅谷 - 2025年3月Java+AI大模型应用开发
java·开发语言·人工智能
有点。2 小时前
C++认识数
开发语言·c++
Herbert_hwt2 小时前
C语言零基础入门:循环控制与数据类型详解
c语言·数据结构·算法
W_326003 小时前
Python文件进阶:一维数据与 CSV 文件读写
开发语言·python
Tyler_TXZ4 小时前
C++C语言之——二叉树
c语言·开发语言·数据结构·c++·二叉树
小羊先生car4 小时前
F429-HAL-RS485(回环/双机实验)(2026/8/16)
c语言·单片机·嵌入式硬件·软件构建
不会代码的小猴4 小时前
C++新增关键字
开发语言·c++·笔记
yaoxin5211234 小时前
497. Java 反射 - 使用反射读取注解
java·开发语言·python
2019一路前行4 小时前
Python 函数式编程
开发语言·python