当前位置: 首页 > article >正文

游戏引擎学习第172天

总结今天的计划

这次的项目我们没有使用任何游戏引擎或者第三方库,而是完全自己动手编写。这种方式可能没有经济效益,但我认为每个人都应该有一次亲身经历,了解开发一款游戏时所涉及的所有内容。这样能让开发者更加灵活,能够做很多其他人无法做的事情。通过这种方式,我们可以完全掌控每一个细节,不需要依赖别人提供的工具和解决方案,这真的很酷。而且,老实说,如果你是那种喜欢编程的人,自己亲自实现这些东西是很有趣的,能够避免层层的抽象和复杂的中间层。

今天,我们将进行一些调试。昨天,我们刚完成了一个部分,结束时没时间开始调试,因为那样会拖得很长,所以我们把调试推迟到今天。我们之前已经对资产处理系统和资源加载系统做了一些改进,增加了一些功能,允许它保存字体的元数据,包括字形的位图表和其他相关内容。我们已经完成了这些工作,但没有机会进行调试,也没有确认它是否正常工作。可能有一些遗漏或者错误,因为涉及到资产处理的两端和游戏端。今天我们要做的就是重新审查这些代码,确保它们正常工作,逐步调试,找到任何问题并解决。这样,我们就能让所有字体相关的功能基本完成。

接下来,我们可能会继续清理代码,添加一些我们想要的功能,让字体系统更加完善。这样,字体功能就完成了,接下来我们就可以开始处理其他内容,比如调试覆盖层,这也是我们一开始决定先做字体的原因,虽然目前游戏中还不需要文本显示,但以后可能会有这个需求。

存在一个问题,导致字体没有加载

目前并没有出现崩溃的错误,但有一个问题存在,导致字体没有正确加载,从而使得游戏无法正常显示字体。问题出在我们的资产打包过程或加载过程中,其中某个环节出现了严重错误。系统可能误认为没有字体数据,或者出现了其他类似的情况。

现在的任务是追踪并找出这个错误或者一系列错误发生的地方。我决定从资产处理器开始调试,因为我想先做一个快速的初步检查,确认我们的字体数据是否至少已经正确地存储到了文件中,以及字形数据是否已经被写入到大数据表中。通过这一步检查,我可以判断数据是否正常存储,并进一步确认问题的来源。

将字体写入一个单独的资源文件

为了让调试过程更简单,我决定将字体数据单独写入一个文件。这样,我就能更方便地检查字体的存储情况,并且暂时将其他资产排除在外。这样可以减少不相关的资源干扰,让调试更加专注。

在实现过程中,我添加了一个字体资产文件写入功能。通过这个功能,我将字体数据写入一个测试文件,比如叫做“test_fonts”。然后我运行了测试资产构建器,发现字体数据成功写入文件,文件大小也从之前的7MB缩小到不到1MB,而缺失的6MB数据已经出现在了新的文件中。

接下来,我进入测试资产构建器的代码,并逐步调试字体写入过程。我希望确认每个步骤都按照预期工作,而不是仅仅假设它们正常运行。这样可以确保不会遗漏任何潜在问题,特别是在已经发现至少有一个问题的情况下,检查每个步骤的执行情况就显得更加重要。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

遍历资源打包程序

在代码执行过程中,我们首先确定了 CodePointCount 的值为 127,符合预期,因为 ASCII 码最高到 126。接着,我们为 bitmap IDhorizontal advance 分配了空间,但这些空间在初始状态下并未清零,因此可能包含随机数据。在调试模式下,由于 CRT(C Runtime Library)debug malloc 机制,这些未初始化的内存会被填充特定的调试值(如 CDCDCDCD 之类的标记值)。
在这里插入图片描述

在这里插入图片描述

接下来,我们加载 arial TTF 字体文件,即便该字体已经存在于 Windows 字体目录,我们仍然强制加载,以防止缺失。我们指定了字体高度 128,这将在后续优化时调整为可变大小。

然后,我们调用加载函数读取该字体,并成功返回字体对象,证明加载过程未出错。随后,我们将其存入全局字体设备上下文,并提取 text metrics(文本度量信息)。从提取的度量信息来看,高度值正确,ascent(上升距离)descent(下降距离) 的值也符合预期,表明文本度量信息提取正确。
在这里插入图片描述

接着,我们存储了 CodePointCount 并计算 line advance(行间距),其值等于 字体高度 + 外部行距(external leading),略高于字体高度,这一结果符合预期。我们随后为 bitmap IDhorizontal advance 分配了足够的数组空间,以便后续存储相关数据。
在这里插入图片描述

然后,我们正式添加字体资产,并在 资产文件 中进行记录。字体的 ID 设为 1,因为它是当前文件中的第一个资源。这些资产包含 HHA(资源头部信息),标记该资源类型为 字体(而非位图或音频)。数据块指向正确的字体资源文件,存储的数据结构也符合预期。
在这里插入图片描述

在这里插入图片描述

在此之后,我们开始记录 字形(glyph) 信息。每个字形对应 字符的位图数据,我们遍历所有字符,并在 AddCharacterAsset 过程中将各个字符的位图信息加入字形资产表。此时,发现 AlignPercentageX 赋值不合理,虽然后续会被覆盖,但为了代码清晰,我们改为 先初始化为 0,并添加注释说明其后续会被正确赋值。
在这里插入图片描述

在这里插入图片描述

之后,我们确认了 资产源指向正确的字体,并且记录了对应的 CodePoint。在日志中可以看到,第一个记录的 CodePoint33,符合预期。接着,我们将返回的资产 ID 存入 BitmapIDs,并继续处理其余字符。
在这里插入图片描述

在调试过程中,我们查看 BitmapIDs 并统计存储的字符数量,发现其中包含一些无效值。这些无效值对应未被使用的字符,未来我们计划优化 glyph lookup(字形查找),以仅存储实际使用的字符,提高查找效率。
在这里插入图片描述

在这里插入图片描述

最后,我们将 HHA 资产信息写入文件,并检查写入的数据是否正确。我们重点验证了 字体部分字形部分 的存储结构,检查 bitmap table 以及其他关键数据,结果与预期一致,表明数据正确写入,无需额外修正。
在这里插入图片描述

在这里插入图片描述

我们没有正确设置水平间距。暂时将其硬编码为当前字符的宽度

在这里插入图片描述

在检查 horizontal advance(水平前进量) 时,发现其数值不太合理,可能存在问题。尝试随机抽查某个值,发现该数值看起来不太可信,这表明 horizontal advance 可能未正确设置。
在这里插入图片描述

进一步分析后发现,glyph bitmap(字形位图) 尚未完全加载,而 horizontal advance 的数据依赖于字形信息。因此,在字形数据未完全加载前,horizontal advance 不能正确写入,这导致测试数据无意义,原本用于检查 horizontal advance 的方法存在逻辑错误。

为了解决这个问题,决定在 horizontal advance 表未完全填充时,先使用一个合理的默认值填充整个表,以避免存储无效数据。具体做法是:

  1. 遍历 CodepointIndex,为每个字符的 horizontal advance 赋值。
  2. 选取 TextMetric(文本度量信息) 中的 max character width tmMaxCharWidth(最大字符宽度)作为临时值,以确保数值至少不会完全错误。
  3. 统一变量命名,使 result 变量名更具可读性,改为 Result,以确保命名一致。
    在这里插入图片描述

在这里插入图片描述

通过上述修改后,再次检查 horizontal advance,发现其数值变得更合理,符合预期。这表明问题已经修复,code pointhorizontal advance 现在都能正确写入。

最终,确保 horizontal advance 数据在 完整的字形信息加载后 再正确写入,避免在错误时间点写入无效数据。
在这里插入图片描述

我们使用了错误的资源类型来处理字体字形

在检查代码时,发现 两个相同的 font(字体) 被写入,这是不应该发生的,表明代码中存在一个 bug。深入分析后,发现 “font”“font glyph”(字体字形) 被错误地混淆,导致多次错误写入。
在这里插入图片描述

修复此问题的方法:

  1. 确保在添加字符时,正确地将其标记为 “AssetType_FontGlyph” 而不是 “AssetType_Font”,避免错误分类。
  2. 在支持 discriminated union(区分联合体) 的编程语言中,这种错误会被编译器自动检测到,但当前语言不具备这样的特性,因此需要手动检查并修正。
  3. 重新运行代码,仔细检查每一步的执行情况,确保所有数据正确存储和写入。
    在这里插入图片描述

在继续调试过程中,检查了字形的写入情况,确认 设备上下文(device context) 被正确设置,字体数据也被正确存储。经过调整后,代码的行为变得更加符合预期,写入路径看起来合理,字体和字形的存储没有出现重复或错误。

在这里插入图片描述

在测试时,发现最终输出文件的 大小发生了变化

  • 原来的大小是 6MB,修复后变为 1MB
  • 可能的原因是之前错误地多次写入了冗余的字体表,而修复后避免了重复写入。
  • 也可能是因为移除了某些无用数据,使得最终生成的文件更加精简。
    在这里插入图片描述

最终,确保:

  • 正确区分 “font” 和 “font glyph”,避免重复存储。
  • 检查写入流程,确保数据完整且无冗余
  • 优化数据存储方式,使文件体积更加合理

整体来看,这次调试找到了多个关键性错误,并且成功修复,使代码更加稳定和高效。

仍然无法加载字体

再次检查代码执行情况,确认当前状态,推测 仍然无法加载,但仍需进行双重检查以确认问题是否仍然存在。
执行运行后,结果显示 确实未能成功加载,需要进一步排查具体原因,确保加载逻辑正确无误。

调试字体加载代码

开始检查实际的加载代码,分析能否正确读取数据。从文件加载数据是第一步,因此先 跳过字体的加载,直接检查从文件读取的内容。

进入 位图体(bitmap body) 相关的偏移处理代码,这部分逻辑主要是确定 字体字形(font glyph) 在文件中的具体位置。
在这里插入图片描述

当遇到 文件中的第一个字体字形 时,代码会计算 偏移量

  • 当前的 资产计数(asset count) 为 27,而代码原本 预期的索引 是 2,因此 实际的偏移量应该是 -24
  • 但检查结果发现 偏移量计算结果为正 24,明显方向反了。
    在这里插入图片描述

意识到这个错误后,重新分析偏移量的计算逻辑:

  • 目标地址应该在前,当前地址在后,计算时应该确保 从当前地址回溯到正确的索引位置
  • 但是代码计算时,把 资产索引(asset index)资产计数(asset count) 位置搞反,导致偏移量方向出错。

这个错误并不是数学计算本身的问题,而是 逻辑层面的理解错误。偏移量计算时,应该始终遵循 目标地址在前,当前地址在后 的原则,确保计算出的偏移量正确指向需要读取的数据位置。

经过再次确认,这一逻辑是正确的,需要修正代码中偏移计算的方向,以确保 正确读取字体数据
在这里插入图片描述

重新开始调试

重新检查逻辑后,发现之前的判断可能是错误的,偏移量计算的方式实际上是正确的。

  • 之前认为偏移量方向错误,但现在重新推理后发现,计算方式实际上符合语义逻辑。
  • 计算的目标是 从第一个资产索引(first asset index)推算出资产计数(asset count),得出的偏移量应为 正 24,这与预期一致。
  • 之前的怀疑是当前分析的失误,实际上 代码本身是正确的

总结来看,过去的代码实现并没有问题,是当前检查时的思维混乱导致错误判断。得出结论:代码在这部分逻辑上是正确的,无需修改
在这里插入图片描述

调试 LoadFont

接下来需要定位并修复 字体加载代码中的错误,重点关注 load fonts 相关逻辑。

  • 查找并进入 load fonts 代码,确认当前的实现是否存在问题。
  • 检查位图加载(bitmap load)和声音加载(sound load)相关部分,确保这些模块正确执行。
  • 重点关注 字体加载的逻辑是否正确执行,并检查 字符的水平进位(advance) 计算是否合理。
  • 逐步分析 字体数据的提取、存储和加载过程,找到可能导致错误的环节,并进行修正。
    在这里插入图片描述

我们还没有调用 LoadFont!

发现 load font 函数并没有被调用,导致字体无法加载,这是当前问题的根本原因。

  • 字体未被加载,但代码中仍然尝试获取字体(GetFont),这自然会导致字体无法正常使用。
    在这里插入图片描述

  • 需要 确保有代码触发字体加载,目前的实现似乎缺少这个关键步骤。

  • 修正方法

    1. 遍历 render group,确保所有需要字体的渲染组都正确加载字体。
    2. 使用 render root 进行字体管理,因为它应该是负责传递 generation id 并触发加载的核心部分。
    3. 调整代码逻辑,在需要字体的地方,主动调用 load font,确保字体数据被正确加载到内存中。
      下一步需要深入 render grouprender root 的实现,确保它们能够正确触发 字体加载

实现 PushFont

PushBitmap 调用中,代码执行了渲染相关的操作,而这里需要确保 正确加载字体,所以计划在 PushFont 逻辑中处理字体的加载。

主要调整点

  1. 在渲染组(render group)中关联字体

    • 需要让渲染组知道它正在使用哪种字体,以便后续正确渲染文本。
    • 直接从 render group 获取 font_id,然后尝试加载字体。
  2. 修改字体加载逻辑

    • 先尝试从已加载的字体中获取对应 font_id
    • 如果未找到,则触发 LoadFont 加载字体数据。
    • 断言(assert):确保字体不会在后台异步加载,而是同步完成,避免渲染时缺失字体数据。
  3. 调整 LoadFont 逻辑

    • 需要修改 LoadFont 使其返回正确的字体对象。
    • 确保 font_id 在所有相关调用中保持一致。
    • 可能还需要进行 类型转换,以便 LoadFont 返回正确的 font_id
  4. 最后统一字体加载调用

    • 确保所有地方都正确调用 LoadFont,不遗漏任何关键路径。
    • 改进 load font immediate,目前它是被调用的,但未真正支持,需要统一处理。

最终目标

  • load font 正常工作,使字体正确加载到渲染管线中。
  • push bitmap 时,能正确获取并使用 font_id,确保渲染正确执行。
  • 统一 load fontload font immediate 逻辑,减少重复代码,提高稳定性。

下一步:运行代码,检查是否能正确加载并使用字体。如果仍然出现问题,需要检查 render group 相关逻辑,确保 font_id 传递正确。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

字体加载成功!

目前,我们正在处理字体加载的进度,虽然字体已经加载,但由于我们尚未正确配置相关的表格(cunning tables),仍然有一些问题,尤其是关于字符间距和字形大小的部分。尽管如此,至少现在已经比之前接近了目标。
在这里插入图片描述

在这里插入图片描述

主要内容:

  1. 字形空间:目前字形的空间大小看起来还不完全正确。虽然我们已经加载了字体,但由于表格未配置完毕,字体空间依然可能存在问题。

  2. 检查字符间距和水平推进

    • 在计算 水平推进(horizontal advance) 时,代码只进行了简单的乘法操作,其中乘数为 CharScale,这个值等于 FontScale
    • 如果没有控制字符的影响(如特殊指令),则字体的 水平推进 值应该是直接使用当前的 水平推进值
      在这里插入图片描述
  3. 字体缩放(FontScalee)

    • 如果 FontScalee 设置为 1,则 CharScale 等同于 1,这意味着 水平推进 会直接等于字体表中定义的数值。
    • 目前看来,只要 FontScalee 保持为 1,水平推进就不会受到其他因素的干扰,应该直接按预期工作。
      在这里插入图片描述

查找正确的字距对值

目前的目标是从测试构建器中提取当前的实际信息,然后将其设置为我们认为正确的值。接下来,我们将检查这些值,确保我们所使用的内容是真实有效的,而不是随机的数值。

具体步骤:

  1. 提取字体数据

    • 之前在设置 水平推进 时,我们使用了一个随机的数值(例如 tmMaxCharWidth)。现在我们希望通过提取实际的字体数据来替代这个随机值,从而确保使用的字体数据真实有效。
  2. Windows 字体功能

    • 在 Windows 环境下,查找相关的字体功能有些困难。之前的帮助系统非常好,但现在变得基于 Web 后,变得很难使用,缺少了原本的目录和相关资源。
  3. 提取字体宽度与字符数据

    • 我们需要获取字体的宽度以及相关的 字符绘制表格。这些数据将帮助我们更精确地确定字体的排版和显示效果。

查阅 GetKerningPairs 文档

在分析如何获取字符对(kerning pairs)时,发现了以下几个关键点:

主要发现:

  1. 获取字符对

    • getTurningPairs 函数可以提取当前设备上下文中字符对的信息。这是我们需要的函数,可以获取字体的字符对数据。
    • 该函数允许设置要获取的字符对数量,如果请求的字符对数量超过了字体所能提供的数量,它会返回错误。
  2. 如何获取字符对数量

    • 函数本身并不直接告诉我们有多少字符对,但可以通过传递 null 参数来获取所有可用的字符对数量。这样做时,返回值将包含所有字符对的数量。
  3. 文本度量与字符对

    • 字体的文本度量信息中并没有直接提供字符对的数量,唯一的办法是通过传递 null 参数来获取所有字符对的数量。

下一步:

  • 通过传递 null 来获取所有字符对的数量,然后我们就能更清楚地知道字体中包含多少字符对。这将帮助我们进一步处理字体的渲染和排版。

GetKerningPairs 函数是 Windows API 中用于获取字体中字符对(kerning pairs)的函数。字符对是指两个特定字符在排版时为了视觉上的平衡而调整的间距。这个函数帮助我们获得这些字符对的信息,以便在文本渲染时进行适当的调整。

GetKerningPairs 函数的作用和使用方法:

函数原型:
int GetKerningPairs(
  HDC hdc,            // 设备上下文句柄
  int nPairs,         // 返回的字符对的最大数量
  KERNINGPAIR *lpkrnpair // 存储字符对信息的数组
);
参数:
  1. hdc

    • 设备上下文(HDC)句柄,用于指定字体和其他图形设置。如果你在进行文字渲染时,使用当前的绘图设备上下文。
  2. nPairs

    • 要返回的字符对的最大数量。该值应为你希望检索的字符对数。通常,这是一个合理的限制,防止一次性返回太多数据。
  3. lpkrnpair

    • 指向 KERNINGPAIR 结构的指针,用于存储每个字符对的间距信息。该结构包含两个字符的 Unicode 值以及它们之间的间距调整量。
返回值:
  • 如果函数成功,它返回已获取的字符对的数量。
  • 如果返回值为 0,说明函数没有找到字符对,或者发生了错误。
KERNINGPAIR 结构:
typedef struct tagKERNINGPAIR {
  LONG  nFirst;   // 第一个字符的 Unicode 值
  LONG  nSecond;  // 第二个字符的 Unicode 值
  LONG  nKernAmount; // 字符对之间的调整量(通常是像素值)
} KERNINGPAIR;
  • nFirst:第一个字符的 Unicode 编码。
  • nSecond:第二个字符的 Unicode 编码。
  • nKernAmount:这两个字符之间的间距调整量,用于调整字符对之间的视觉效果。

使用示例:

HDC hdc = GetDC(hwnd); // 获取设备上下文(假设hwnd是窗口句柄)
int numPairs = GetKerningPairs(hdc, 0, NULL); // 获取字符对的数量

// 为字符对分配足够的内存
KERNINGPAIR* pairs = (KERNINGPAIR*)malloc(numPairs * sizeof(KERNINGPAIR));

// 获取所有的字符对信息
int result = GetKerningPairs(hdc, numPairs, pairs);

if (result > 0) {
    for (int i = 0; i < result; i++) {
        // 打印每一对字符的 Unicode 值和间距调整量
        printf("First: %d, Second: %d, Adjust: %ld\n", 
               pairs[i].nFirst, pairs[i].nSecond, pairs[i].nKernAmount);
    }
}

free(pairs); // 释放内存
ReleaseDC(hwnd, hdc); // 释放设备上下文

作用:

  • GetKerningPairs 主要用于获取字体的字符对和相应的间距调整量。这对于文本渲染非常重要,尤其是在需要精确控制字体间距和排版时(比如排版设计、图形应用程序等)。
  • 在文本渲染时,字符对的间距调整能提高文本的视觉平衡,使得两个字符之间的空白区域更加和谐,避免因为字体形状问题而导致文本看起来不自然。

计算字距对的数量

在这里插入图片描述

首先,为了获取字体的字符对(kerning pairs)数量,需要调用 GetKerningPairs 函数。为此,首先需要传入一些初始的假值。传入设备上下文(HDC),通常是已经选择的全局字体上下文。此时需要传递一个空值(NULL)作为字符对的返回值,这样可以通过 GetKerningPairs 函数来获取字符对的数量。

然后,调用函数后,返回的数量告诉我们该字体中有多少个字符对。接下来,为了存储这些字符对的信息,需要为字符对分配足够的内存空间。字符对的存储通常使用 KERNINGPAIR 结构来表示,每个字符对包含两个字符和它们之间的间距。

分配内存后,接下来可以再次调用 GetKerningPairs,并将返回的字符对信息存储到先前分配的内存中。这个过程让我们能够获取到所有的字符对,并可以进一步使用这些信息来调整字符间距,确保文本渲染时字符间隔的合理性。

最后,需要注意的是,确保使用正确的函数类型。如果函数返回错误,可能是由于使用了错误的数据类型(例如,使用了不适合的类型)。

获取字距对

在处理字距对(kerning pairs)时,首先需要分配足够的内存空间来存储这些字距对。根据需要的字距对数量,分配对应的内存。获取字距对时,可以使用 GetKerningPairs 函数来获取所需的字距对信息,并在处理完毕后释放这些内存。

具体流程如下:

  1. 分配内存: 根据从 GetKerningPairs 函数返回的字距对数量,分配适当大小的内存空间来存储这些字距对。
  2. 获取字距对: 使用 GetKerningPairs 函数获取所有的字距对数据,这些字距对存储在一个数组中。
  3. 遍历字距对: 遍历所有获取到的字距对。在处理每个字距对时,使用索引来访问当前字距对,并根据需要对每个字距对进行处理。

对于每一对字距对,处理方式取决于该字距对返回的信息。具体来说,每对字距对会包含两个字符和它们之间的调整值(kerning)。根据字距对返回的数据,调整字符之间的间距。

在处理完所有字距对后,可以释放之前分配的内存。这样,就完成了字距对的获取与处理工作。

tagKERNINGPAIRs 结构

在这里插入图片描述

首先,GetKerningPairs 函数返回的是字体中每一对字符的调整值(kerning)。这些调整值通常是负值,意味着相邻字符之间的距离通常会比正常情况下更紧凑。对于每一对字符,KERNNINGPAIR 结构体包含了两个字符的代码点以及它们之间的调整值。

然而,调整值是相对于默认的字符间距的。也就是说,字符间的初始间距是预设的,而 kerning(字符间距调整)是相对于这个默认值的。例如,如果两个字符在同一行中,它们的 kerning 值可能会使得字符之间的间距变得更紧凑(如果值是负的),或者更宽松(如果值是正的)。

接下来,在处理 kerning 的时候,需要对字符对的范围进行检查。如果字符对的两个字符都在字体的有效范围内,才会继续调整它们之间的间距。具体来说,如果这对字符的代码点都小于字体的字符集的总数,那么就认为这对字符的间距调整有效。

然后,通过从 kerning 数据中获取对应的调整值,可以决定是否需要调整这对字符之间的间距。如果调整值是负数,那么就将间距缩短;如果是正数,则加大间距。调整值的应用是基于默认的字符间距的基础上进行的。

为了实现这一目标,需要将 kerning 表初始化为合理的默认值。这个默认值代表了在没有特殊 kerning 信息的情况下,字符之间的默认间距。通过这种方式,字符之间的间距调整可以基于已有的默认值进行。

总结来说,整个过程涉及到获取字符对的调整值、检查字符对是否在有效范围内、根据调整值修改字符间距,以及确保所有的字符间距调整是相对于一个默认的间距值来进行的。
在这里插入图片描述

默认的推进值是什么?

在处理字距对时,首先需要理解三个关键的间距值:

  1. Spacing A (前间距):表示字符开始绘制之前的距离。
  2. Spacing B (字形宽度):表示已经绘制的字符部分的宽度。
  3. Spacing C (后间距):表示字符绘制完后,字符右边的空白距离。

这些值加起来,就是每个字符的总前进量(即字符绘制后,当前绘制位置的偏移)。因此,我们要将这三个间距值加在一起,以确保准确计算字符之间的距离。

步骤:

  1. 计算字形的总宽度: 对于每个字符,先获取它的前间距、字形宽度和后间距,累加这三个值,得到总的前进量。
  2. 字形间距的调整: 如果存在额外的空白间距,需要根据这个间距调整字形的位置。这意味着需要根据字符的对齐点(alignment point)来回退该空白的间距。
  3. 存储字距信息: 将每个字符的字距数据存储在结构体中,然后将它们按顺序排列在一个数组中进行管理。
  4. 分配内存和释放: 在处理过程中,需要为这些字距数据分配内存,并在操作结束后释放内存。

代码实现:

  • 使用 GetKerningPairs 等函数获取字符的字距对。
  • 通过遍历所有字距对,计算出每个字符的前进量。
  • 将计算的结果存储在合适的数据结构中(例如,结构体数组)。
  • 每次处理完之后,确保正确释放资源,以避免内存泄漏。

通过这些步骤,可以更准确地管理字符之间的间距和对齐,确保文本渲染的正确性和效率。

GetCharABCWidths 是 Windows API 中的一个函数,它用于获取指定字符在指定字体中的宽度信息。具体来说,它提供了字符的前间距(A)、字符的宽度(B)以及后间距(C)信息,这些信息有助于文本布局和字形渲染。

函数原型:

BOOL GetCharABCWidths(
  HDC     hdc,           // 设备上下文句柄
  UINT    iFirst,        // 要查询的第一个字符
  UINT    iLast,         // 要查询的最后一个字符
  LPABC   lpabc          // 字符的宽度信息结构数组
);

参数说明:

  1. hdc: 设备上下文(Device Context)句柄,指定绘制文本的设备或图形环境。它是一个由 BeginPaintCreateDC 等函数创建的上下文对象。
  2. iFirst: 要查询的第一个字符的字符代码点。它指定了字符范围的起始位置。
  3. iLast: 要查询的最后一个字符的字符代码点。它指定了字符范围的结束位置。此值可以与 iFirst 相同,表示只查询一个字符的宽度。
  4. lpabc: 指向 ABC 结构体数组的指针,用于接收每个字符的宽度信息。ABC 结构体定义如下:
    typedef struct tagABC {
        LONG  abcA;    // 前间距(字符开始前的距离)
        LONG  abcB;    // 字形的宽度(字符实际的宽度)
        LONG  abcC;    // 后间距(字符结束后的距离)
    } ABC;
    

返回值:

  • 如果函数成功执行,则返回 非零 值。
  • 如果函数调用失败,则返回 ,可以使用 GetLastError() 获取错误信息。

函数作用:

GetCharABCWidths 用于获取指定字符范围内的每个字符的 ABC 宽度信息。这个信息非常重要,因为它包含了三个关键的间距参数:

  • A (前间距):字符开始前的空白区域。
  • B (字符宽度):字符本身的宽度。
  • C (后间距):字符结束后的空白区域。

这些信息通常用于文字渲染和排版,确保每个字符在显示时的位置和间距正确。例如,在进行字符排列和换行时,字符的宽度是决定文本布局的关键因素。

示例代码:

HDC hdc = GetDC(hWnd);  // 获取设备上下文
ABC abc[256];           // 创建一个 ABC 结构数组,用于存储字符的宽度信息

if (GetCharABCWidths(hdc, 'A', 'Z', abc)) {
    for (int i = 0; i < 26; i++) {
        printf("Character: %c, A: %ld, B: %ld, C: %ld\n", 'A' + i, abc[i].abcA, abc[i].abcB, abc[i].abcC);
    }
} else {
    printf("Failed to get character widths\n");
}

ReleaseDC(hWnd, hdc);  // 释放设备上下文

使用场景:

  • 文本排版:根据字符宽度计算文本的显示位置和换行位置。
  • 字符间距调整:对字符的显示间距进行微调,特别是对于不同字体或不同语言的文本渲染。
  • 文字渲染:确保字符渲染时按照正确的尺寸和间距显示。

通过 GetCharABCWidths 函数,开发者能够获取精确的字符宽度信息,并基于这些信息进行准确的文本渲染和排版。

询问 ABCs

在处理字符字形宽度时,遇到了一些问题。首先,对于这些字距信息(ABC宽度),不确定它们是否与其他类型的数据结构(如W类型数据结构)兼容,这可能会导致内存分配的问题。如果这些字距信息需要以ABC结构的形式存储,就需要确保内存分配正确,以避免分配过少的内存。

为了确认是否需要分配与W类型结构相关的内存,需要仔细检查当前的数据结构,特别是这些字距信息的存储方式。假如这些字距信息涉及到不同的数据类型或结构体,可能需要调整内存分配策略。这样做的目的是确保不会因分配不足的内存而导致潜在的内存问题或程序崩溃。

接下来,将需要仔细审查数据结构的定义以及它们的内存需求,确保所有数据的正确存储和操作。

检查系统头文件,查看哪些函数和结构提供宽字符替代方法

在处理字距对(kerning pairs)时,遇到了一些混乱,特别是在选择不同版本的 GetKerningPairs 函数时。Windows 提供了多个版本的该函数(例如,普通版本和带有W后缀的版本),它们的参数和返回值几乎相同,但又被分开定义。这个分开定义的做法显得有些不必要,因为这两种版本实际上都接受相同的参数,并且执行相似的操作。这让人不解,为什么需要两个几乎一样的函数。

在查看这些函数时,发现了两个版本的 GetKerningPairs,分别是常规版本和带W的版本(通常用于宽字符)。然而,这两个版本并没有明显的功能差异,且都需要相同的参数。因此,质疑为什么需要这两个版本是合理的,可能是为了兼容不同的字符编码或一些特定的使用场景。

另外,在实际操作中,开发人员可能会建议使用更为专业的开发工具(如 Visual Studio)来处理这些问题,因为它们提供了更直观和强大的调试功能,可以帮助更高效地分析和解决此类问题。
在这里插入图片描述

调试 LoadFont 崩溃问题

free出问题
在这里插入图片描述

在处理字距调整时,遇到了一个内存分配问题。原本以为为水平推进(horizontal advance)分配的内存空间是足够的,但实际上程序似乎正在尝试写入超过分配的内存范围,这可能导致了崩溃。具体来说,分配的内存是基于代码点的数量和 ABC(字形调整数据)结构来计算的,但看起来实际操作中似乎需要更多的内存空间,导致了程序超出了预期的内存范围。

这个问题可能是由于未能正确分配足够的内存,导致函数尝试读取超出分配区域的内存。文档中提到,需要传入一个包含至少与字符范围相同数量结构体的数组,以存储每个字符的宽度。在这里,代码试图按照字符范围分配相应的空间,但没有分配足够的空间来存储所有的字符宽度数据。因此,内存越界可能是因为数组的大小设置不当,导致函数读取或写入了错误的内存位置。

为了解决这个问题,需要确保分配的内存空间足够,并且每个字符的宽度信息能够被正确存储。需要检查代码中内存分配的大小,确保它至少包含字符范围内的所有结构体。

关于调试时找不到crt源码

启用调试符号(PDB 文件)并定位 exe_common.inl 文件和符号表的流程可以分为以下几个步骤。这个过程将帮助你调试时能够看到更详细的源代码、函数名称和行号等信息。

1. 启用调试符号 (PDB 文件)

调试符号文件(.pdb)包含了程序的调试信息,比如源代码行号、变量名称等。在调试时,加载 .pdb 文件可以帮助你查看详细的堆栈信息。

在 Visual Studio 中启用调试符号:
  1. 打开 Visual Studio 设置

    • 启动 Visual Studio 并打开你的项目。
    • 进入 Tools(工具)菜单,选择 Options(选项)。
  2. 启用符号服务器

    • Options 窗口中,依次选择 Debugging -> Symbols
    • 勾选 Microsoft Symbol Servers(微软符号服务器),确保调试器能够自动下载符号文件。
    • 你也可以添加自定义的符号服务器或本地符号文件路径。为了从 Microsoft 下载符号,你需要确保启用了 Microsoft 的符号服务器。
  3. 配置符号文件路径

    • 你可以选择添加本地路径来缓存符号文件,通常是将符号文件保存在本地的某个目录中。
    • 推荐路径:
      • C:\ProgramData\Microsoft Visual Studio\Symbols,这是 Visual Studio 默认下载并缓存符号文件的路径。
  4. 启用调试信息生成

    • 确保你的项目的生成配置已经启用符号生成。
      • 右键点击项目 -> Properties(属性)。
      • Configuration Properties -> C/C++ -> General 中,确保 Debug Information Format 设置为 Program Database (/Zi)
      • Configuration Properties -> Linker -> Debugging 中,确保 Generate Debug Info 设置为 Yes (/DEBUG)
  5. 编译项目并生成符号文件

    • 重新生成项目,确保 .pdb 文件被正确生成。
    • 这些符号文件通常会与生成的可执行文件或 DLL 保存在相同的目录下。

2. 查找 exe_common.inl 源代码文件

exe_common.inl 是一个与启动过程相关的文件,通常包含一些初始化代码,处理程序的启动和初始化。这个文件通常位于 CRT 源代码 中。

查找 exe_common.inl
  1. 在本地 Visual Studio 环境中搜索

    • exe_common.inl 是 C 运行时的一部分,通常包含在 Visual Studio 安装的 CRT 源代码中。你可以在 Visual Studio 中的 CRT 源代码目录中查找该文件:
      • C:\Program Files\Microsoft Visual Studio\<version>\VC\Tools\MSVC\<version>\crt\src\
      • 该文件可能包含在 vcstartup 目录中,它处理应用程序的启动部分。
  2. 检查 C:\Program Files 目录

    • 你也可以尝试直接在 Visual Studio 安装目录下搜索,特别是在 VC\Tools\MSVCcrt\src 等目录中查找文件。
  3. 如果找不到本地文件

    • 如果你无法找到 exe_common.inl 文件,通常它是作为 Windows SDK 或 Visual C++ 工具链的一部分提供的。你可以尝试通过微软的 GitHub 仓库访问该源代码(如果公开的话),或者使用符号服务器从 Microsoft 获取缺失的源文件。
    • GitHub 上有一些 C++ 工具链的源代码,你可以尝试访问:Microsoft C++ GitHub 仓库。
  4. 启用源服务器

    • 如果你无法找到源代码,可以启用源服务器来自动从 Microsoft 下载源文件。

3. 启用源服务器并下载符号

如果缺失源代码文件或符号信息,源服务器可以帮助你自动从 Microsoft 下载这些内容。

在 Visual Studio 中启用源服务器:
  1. 打开 Visual Studio 设置

    • 进入 Tools(工具) -> Options(选项)。
    • 选择 Debugging -> General,确保启用 Enable Source Server Support(启用源服务器支持)。
  2. 启用源服务器

    • 进入 Tools -> Options -> Debugging -> Symbols
    • 勾选 Microsoft Source Server,这样 Visual Studio 会从 Microsoft 的源服务器下载缺失的源代码文件。
  3. 配置源文件缓存目录

    • Source File Locations(源文件位置)设置中,可以添加自定义路径(本地或网络位置)以缓存下载的源文件。
    • 通常默认路径是 C:\Users\<YourUser>\AppData\Local\Microsoft\VisualStudio\Symbols
  4. 启动调试并下载源代码

    • 当你在调试时遇到缺失的源文件,Visual Studio 会自动连接源服务器并下载缺失的源代码。你可以查看调试器中的消息,确认源文件是否被成功下载。

4. 调试时检查符号和源代码

一旦你启用了符号和源服务器,调试器会显示更多详细信息:

  • 符号信息:在调试过程中,你应该能够看到更多的函数名、变量和行号。
  • 源代码:如果调试时遇到 exe_common.inl,且文件已被正确下载,你应该能够查看该源文件的内容。你可以直接在 Visual Studio 的调试窗口中查看源代码。

总结:

  1. 启用符号服务器:在 Visual Studio 中启用 Microsoft 符号服务器,确保调试符号文件(.pdb)能够下载并加载。
  2. 查找源代码文件exe_common.inl 是 C 运行时启动代码的一部分,你可以在 Visual Studio 安装目录的 CRT 源代码文件夹中查找。
  3. 启用源服务器支持:通过 Visual Studio 设置启用源服务器,以便从 Microsoft 下载缺失的源代码文件。
  4. 调试时查看符号和源代码:启用调试符号和源代码后,调试器将显示更多细节,帮助你定位问题。

通过以上步骤,你应该能够有效地启用调试符号并访问源代码,帮助你进行深入的调试。如果有任何具体问题,随时可以继续咨询!
在这里插入图片描述

exe_common.inl 的目录
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.40.33807\crt\src\vcruntime\exe_common.inl
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

exe_common.inl 是 Microsoft C++ 运行时(CRT)库的一部分,主要用于启动程序的初始化过程。它包含了与应用程序启动相关的一些共享代码,并通常在 vcstartup 目录中找到。它在启动时执行一些关键的初始化任务,如设置程序的运行环境、调用 C++ 程序入口点等。

exe_common.inl 的作用:

  1. 程序启动初始化
    exe_common.inl 是程序启动时加载的一部分,主要负责在应用程序的 main() 函数(或 wmain())执行之前执行一些必需的初始化步骤。它通常是启动代码的一部分,这些代码负责设置环境、初始化 CRT 等。

  2. 运行时环境设置
    它可能包含与程序执行环境相关的初始化代码,例如设置 C++ 标准库的状态、处理命令行参数等。

  3. 程序入口点连接
    在 C++ 程序中,程序的实际入口点通常是 main() 函数。但是,exe_common.inl 等文件负责将程序的入口点与 CRT 框架连接起来,以确保程序能够顺利启动并执行。

  4. 启动过程的一部分
    exe_common.inl 文件通常与其他启动文件(如 crt0startup 文件)配合工作。它们共同实现应用程序的启动过程,处理各种操作系统相关的任务,最终将控制权交给程序的主逻辑部分。

文件路径和作用:

  • 路径exe_common.inl 文件通常位于 CRT 源代码路径下,如:

    C:\Program Files\Microsoft Visual Studio\2022\VC\Tools\MSVC\<version>\crt\src\vcstartup\src\startup\exe_common.inl
    
  • 与启动文件的关系exe_common.inl 文件是启动文件的一部分,通常与 crt0 文件或其他与启动代码相关的文件一起工作。启动文件负责从操作系统接管应用程序的控制,执行一些环境设置,并在最后调用 main() 函数。

举个例子:

假设在 C++ 程序启动时,exe_common.inl 可能执行以下操作:

  1. 调用 CRT 初始化:在 main() 函数运行之前,它会执行与 C++ 运行时相关的初始化,如堆分配、标准流初始化等。
  2. 环境设置:处理命令行参数、环境变量等信息,以便程序能正确运行。
  3. 调试和错误检查:在某些情况下,它还可能处理调试信息和错误检查,以便确保程序在启动时没有问题。

在这里插入图片描述

如果用c/c++ 插件的launch 运行 可以通过symbolSearchPath 自动下载符号表
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

源码路径:C:\Program Files (x86)\Windows Kits\10\Source
只能在launch调试指定符号表
在这里插入图片描述

在这里插入图片描述

vscode 只能直接拷贝源码到指定的路径下面去

在这里插入图片描述

在这里插入图片描述

vs 直接打开C:\Program Files (x86)\Windows Kits\10\Source下面对应的文件就行

GetCharABCWidthsW 需要一个范围,而不是数量

在当前的代码逻辑中,发现 last char 实际上是直接使用的最后一个字符,而不是一个 计数值(count)。因此,在计算时需要对其进行调整,即 减去 1,以便正确获取所需的字符范围。

通常在编码实践中,范围的表示方式有两种:

  1. 包含最后一个元素(inclusive):[first, last]
  2. 不包含最后一个元素(exclusive):[first, last)

在大多数情况下,采用 后者(即“one past last”)的方式能够更直观地表示范围,并避免 off-by-one 错误。然而,在当前的实现中,last char 采用的是 第一种方式,这意味着如果按照 count 计算范围,可能会导致额外读取字符,或者访问越界的内存区域。

因此,需要手动对 last char 进行修正,即 last char = last char - 1,以确保范围计算符合预期。这种设计选择可能是历史遗留问题,或者是某种 API 规范导致的。

最后,调整后的代码逻辑能够正确处理字符范围,避免出现不必要的溢出或错误访问情况。

在这里插入图片描述

现在我们的字体看起来更好

当前正在检查生成的字距表,并尝试确保其输出结果合理。初步来看,结果比之前更合理,但仍然存在一些问题,例如某些字符间出现了较大的间隙,需要进一步调整。

首先,需要修正 A、B、C 三个宽度值(ABC widths) 相关的问题。当前在绘制字符字形(glyph)时,字符的渲染起始位置可能并不正确。为了确保字形正确对齐,需要考虑 A 值(前间距,pre-spacing),确保它正确地应用到字符绘制位置,而不仅仅是使用 B(字符自身宽度)和 C(后间距,post-spacing)。

因此,需要在提取字形数据时,确保将 A 值正确地整合进整体布局计算中。这一步骤至关重要,因为 Windows API 在处理字体时,可能会在字符绘制前 额外添加一些默认间距,如果不正确处理,会导致整体排版出现错误,尤其是在计算字符的起始位置时。

下一步,需要修改提取 ABC widths 的逻辑,使其更加精确地考虑所有三个值(A、B、C),确保最终的字距表能够正确反映字符的间距和对齐方式。
在这里插入图片描述

使用 ABC 的 A 信息来修改字形的对齐

当前的目标是正确获取并应用 GetCharABCWidths 提供的 ABC width 值,以确保字符的正确对齐和间距计算。

首先,需要调用 GetCharABCWidths 获取特定代码点(code point)的 ABC width 值。由于只需获取单个字符的数据,因此不需要额外的内存分配,只需定义一个 ABC 结构体,并将其地址传递给 API,即可获得所需的 A、B、C 间距数据。

具体流程如下:

  1. 选择字体(SelectObject):确保当前设备上下文(Device Context, DC)正确加载了目标字体。
  2. 调用 GetCharABCWidths:传入字符代码点,并提供一个 ABC 结构体 的指针,API 将填充该结构体的 A(前间距)、B(字符宽度)、C(后间距) 三个值。
  3. 调整字符绘制逻辑:在计算字符绘制位置时,当前方法默认使用了简单的 B(字符宽度) 进行偏移,但正确的做法是考虑 A 值(前间距),以确保字形对齐正确。

具体改动:

  • 字符起始位置调整:在计算字符的对齐位置时,需要 额外加上 A 值,确保绘制时正确偏移。
  • 字符绘制结束后的对齐调整:在移动光标到下一个字符时,需要使用 A + B + C 计算新的偏移量,而不是仅依赖 B 或默认值。

这一调整将确保文本渲染时,字符的对齐方式更加精确,避免由于忽略 A 值导致的错位问题。
在这里插入图片描述

(黑板)字形对齐点计算图

当前目标是确保字体渲染时字符的对齐方式正确,特别是 A(前间距) 的处理方式,以避免字符错位。

问题描述

在计算字符的绘制位置时,Windows API 提供的 A、B、C 间距信息需要正确应用:

  1. A(前间距):指定字符绘制前应向后偏移的距离。
  2. B(字符本身宽度):字符本身占据的像素宽度。
  3. C(后间距):字符绘制后额外需要的间距。

如果 A 值 没有正确处理,字符的起始绘制位置可能会偏离预期,导致渲染错位。

解决方案

  1. 正确调整起始绘制位置

    • 设定一个基准对齐点 X,代表当前字符的目标绘制位置。
    • 在 X 位置绘制字符前,向左调整 A 个像素,即 X’ = X - A,确保字形绘制正确。
  2. 自动处理字符间距

    • Windows 要求在绘制字符时,使用 A 进行负向偏移,确保文本排版正常。
    • 采用 X’ = X - A,然后直接绘制字符,相当于自动调整了字符起始位置。

代码逻辑调整

  • 修改对齐基准点,从默认的 X 改为 X’ = X - A,确保字符绘制时不会发生错位。
  • 计算字符间距 时,使用完整的 A + B + C,保证字符连续排列时的正确性。

可能的优化

  • 当前方法简单直接,但可能有更健壮的方式,例如使用 Windows 提供的更高级 API 处理文本排版,减少手动计算误差。
  • 后续可以测试不同字体的渲染情况,确保所有字符的 A、B、C 值均被正确应用,不影响整体排版。

结论

正确处理 A 值 能够确保字符按照 Windows 规范进行渲染,避免对齐偏差。同时,通过自动调整 X’ = X - A,可以确保后续字符间距计算正确,使文本整体呈现效果符合预期。
在这里插入图片描述

我们的字体看起来更好了

现在,游戏的渲染效果看起来已经相当不错了。虽然目前的渲染效果还不算完美,但考虑到 Windows 的字体渲染系统本身存在很多问题,这种表现可能已经相对较好。

调整字体和对齐

  1. 使用不同的字体:在测试时,选择了 Arial 字体,并尝试调整其大小。通过尝试不同的字体设置,可以粗略估算当前的字体渲染效果,并进行微调,确保字体的显示效果接近理想状态。

  2. 字体大小调整:为了确保字体渲染效果接近预期,通过调整 Arial 128 的大小,调整了字体的显示比例,使其更符合要求。

  3. 计算字符间距:尝试调整和估算 ABC 的间距,观察是否能够接近正确的字符间距值。通过这种方式,能够粗略地确认当前的间距计算是否合理。

观察字体渲染效果

通过调整字体并观察其显示效果,调整到一个接近正确的显示比例。通过这种方式,能确保字符显示得更加精准,同时避免字体渲染中的显著偏差。

结论

虽然目前字体渲染还不完美,但通过细致的调整和估算,已经能够得到相对满意的结果。之后可以继续根据需要进一步调整字符间距和字体大小,确保最终效果达到最佳。

字体仍然不好看,但这是 Windows 的问题

通过观察当前的字体渲染效果,发现尽管从字体表中提取的曲线看起来并不完美,实际上,Windows 的字体渲染效果更差。这意味着,尽管我们并未完全正确提取字体的曲线,但由于 Windows 的字体渲染本身存在很多问题,差异并不明显,甚至可以忽略不计。

问题分析

  1. 字体曲线提取不完全准确:目前从表格中提取的曲线并未完全准确地反映字体的真实曲线。
  2. Windows 渲染系统问题:由于 Windows 字体渲染的质量较差,即便提取的曲线有误,也无法明显察觉出差异。因此,即使渲染效果并不完美,实际表现上反而显得不那么明显。

进一步的改进

  1. 调整曲线渲染:尽管当前的曲线渲染效果不好,但离正确支持字体的渲染比例已经相当接近。接下来需要进一步调整曲线提取和渲染算法,使得字体渲染效果更加精确。

  2. 字体选择问题:Windows 系统中的字体渲染本身质量较差,因此考虑到这一点,可能需要从互联网上寻找更合适的字体进行替换和使用,以提高渲染效果的质量。

未来的改进方向

由于当前的渲染效果依然无法达到理想的状态,接下来需要继续寻找合适的字体,并进行相关的调整,以便进一步提高字体的渲染质量。虽然 Windows 8 之后字体渲染有所改善,但由于早期版本的问题,可能需要依赖第三方字体或者尝试其他的渲染方法来优化最终的效果。

看一下 Segoe Condensed 字体的效果

尝试了一下新的调整,结果发现渲染效果完全不符合预期。输出的结果不仅无法达到预期效果,反而更加糟糕,简直令人失望。看着这种结果,完全没有想要继续进行下去的动力了。整个过程感觉非常沮丧,效果远远不如预期的好。

Windows 字体让字体设计师哭泣

在这段过程中,尽管已经做了许多努力,最终发现Windows的字体渲染仍然非常糟糕,甚至令人沮丧。目的是让字体在排布时看起来不那么丑陋,但即使只是这么简单的任务,Windows也没能做到,这种情况真的令人失望。虽然我们并不指责字体本身丑陋,但Windows渲染出来的字体排列确实是让人看了觉得不舒服,甚至有点夸张。

接下来,尽管做了调整,还是感觉没能正确提取Windows的字体渲染数据。由于Windows本身的字体渲染质量本来就很差,最终的效果也并没有得到预期的提升。整个过程变得有些讽刺和幽默,因为从Windows中提取字体的渲染信息已经是非常困难的事情了。

我们仍然需要看看字形边界框检测与系统字距信息的互动

首先,讨论了在加载字形位图时,如何找到字体字形的边界框。问题在于,字体字形的边界框会与Windows认为的绘制位置有所偏差。我们告诉Windows从x=0的地方开始绘制,但Windows已经包含了字符的宽度,即它会自动处理一些偏移,而我们自己之后需要重新找到字形的实际边界框。

但问题在于,字体字形本身可能包含了一些额外的空白区域,这些区域并没有被包含在字符的宽度(A宽度)内。例如,假设我们仍然使用相同的处理方法,告诉Windows从某个位置开始绘制字形,但字形实际开始的位置可能远离预期的绘制位置。如果我们手动计算字形的最左边界,可能会“剪切”掉这些额外的空白区域,因为我们只保留了字形的实际部分,而忽略了这些额外的空间。

考虑到这一点,可以尝试计算字形的实际位置与预期绘制位置之间的差距,看看是否需要做更复杂的调整来避免这一问题。接着,研究了TextOutW函数,这个函数用于输出文本,尝试了解它是如何处理这些字形的输出的。它依赖于上下文中的对齐方式和其他配置,因此需要理解这些配置是如何影响绘制位置的。

最后,推测TextOutW函数会根据所给的对齐方式将文本绘制到指定位置,但具体行为需要进一步确认。

基于边界框的对齐已经正确,实际上不需要使用 ABC

首先,考虑到当前的测试情况,决定进行更结构化的测试,可能会在第二天进行。对于文本绘制过程中的问题,当前并不完全清楚文本输出的实际行为,所以需要更多的测试以明确到底发生了什么。

在检查字体的字形位图时,得到了有关字符宽度(a b c width)的信息,发现字符宽度为10。这表明,Windows在进行文本绘制时,已经包含了字符宽度(a width),而不是像预期那样在字符外部加上额外的空白空间。

基于这个信息,可以考虑放弃当前的处理方式,直接使用该信息来调整绘制的位置。通过获取最小X坐标(minX),可以确定字符在水平轴上的对齐方式,并据此进行调整,从而确保字符正确显示。

在进一步的测试中,将字体更换为Arial,并观察文本输出的表现。结果显示,虽然大多数情况下输出效果很好,但在某些字符上(例如字母"a"),存在微小的裁剪现象。这是因为在绘制过程中,字形向后移动,导致字符的一部分被裁剪。插入点的位置与裁剪点对齐,表明文本输出时确实发生了裁剪。

因此,认为文本输出的裁剪问题可能是在调用TextOut函数时出现的,需要进一步检查文本输出过程,尤其是在裁剪问题出现的位置。

修改资源打包程序中的 TextOutW 调用,允许 Windows 将字形绘制到指定绘图点左侧

在进行字体绘制时,考虑到需要让Windows能够适当调整字体位置,决定允许它在一定程度上向后移动字体。这意味着可以为Windows提供一些配置,让其调整字体的显示位置,类似于设置一个“预设步骤”(pre-step)的参数。例如,可以尝试让Windows在绘制字符时向后移动128个字符位置,观察这种调整是否能解决问题。

在实现这一点时,需要确保计算minX时考虑到“预设步骤”,并在计算时进行适当的偏移调整。这样,最小X坐标(minX)会被修正,以便考虑到Windows可能已经应用的向后移动。

另外,需要扩展字体搜索的半径,以便在计算边界时确保包含了向后移动的偏移量。否则,在计算时就会遗漏一部分需要绘制的区域。这要求在绘制时,将计算出的边界与预设的偏移量结合。

在测试时,发现一些字体绘制正常,而有些则出现了错误,这表明当前的实现可能有部分问题。尽管如此,基本上看起来已经能正确绘制字体,只是需要进一步的调试来确保所有情况都能处理好。

总的来说,目前的任务是进一步完善字体的提取过程,尤其是要处理好字体的位移和绘制边界的调整。这些任务可能需要更多的调试和验证,待明天继续进行。
在这里插入图片描述

你能用其他文本样本,比如 “AVA WA Ta” 吗?

可以使用其他技术示例,比如使用 a wavi a w,甚至是使用 a tar,完全可以根据需要进行选择和尝试。这些技术的选择并没有特别的限制,可以根据实际情况灵活调整。例如,如果要使用 avi a wsj top 来进行某种特定的文本处理或展示,也完全是可行的。过程中需要注意的细节可能包括确保正确地进行调用和执行,避免遗漏或错误的操作。

了解 Arial 和 Helvetica 字体的一些新知识

发现了一个有趣的事情:在 Arial 字体中,字母 “W” 的倾斜角度与字母 “A” 的倾斜角度不同,之前并没有注意到这一点。更有意思的是,Arial 字体的设计实际上是基于 Helvetica 字体的,只不过它是一个非常不精确的 Helvetica 版本。尝试查看 Helvetica 字体时,确实会看到字体的倾斜角度差异,尤其是在字母的设计上,这与 Arial 字体的表现有所不同。原来 Helvetica 的设计中,字母的倾斜角度是统一的,这一点之前并没有意识到,今天才了解这个细节。

你怎么知道这么多关于字体的知识?

其实并没有学到很多关于字体的知识,但因为工作中涉及到图形处理,所以必须了解一些基础的字体知识,以免在处理时犯低级错误。工作中有一定的字体代码编写,所以了解一些关于字体排版的知识是非常有必要的,比如字母间距、字形的中心对齐、连字(ligature)等。这些是做字体相关工作的基础。虽然并不能像一些字体专家那样,仅凭眼睛就能识别出某个字体,比如看到某个字体就能说出它是 “Baskerville” 或 “Helvetica”,但知道这些基础知识已经足够在工作中避免一些常见的错误了。

你通常会为游戏购买字体许可吗?还是利用渲染为位图的漏洞,可以使用任何字体?

通常来说,关于游戏中的字体使用,可以通过一个漏洞来避开版权问题,这个漏洞意味着只要将字体渲染为位图格式,就可以使用任何字体,而不需要考虑其版权。不过,值得注意的是,实际上有很多非常好的字体资源,可以合法使用,不必依赖这个漏洞。这些合法的字体选择通常也能满足游戏开发中的需求,并且不涉及版权问题。

推荐使用 Google Fonts

Google 提供的字体库包含了大量的字体资源,估计有大约一千四百种字体。这些字体都经过了授权,可以用于商业用途,无需依赖任何版权漏洞。每个字体家族的目录都包含了相关的许可证,确保字体的使用符合法律规定。大多数这些字体都遵循开放字体许可证(Open Font License,OFL),这意味着只要遵守一些基本条件,就可以合法使用这些字体。具体来说,用户需要在使用字体时注明版权信息,并且不能单独出售字体,也不能修改字体的名称或者字体作者的名字,此外还不能将字体作者作为自己产品的推广者。

只要遵守这些简单的要求,就可以在游戏开发中合法使用这些字体,而无需依赖任何法律漏洞。因此,如果没有特别的需求,建议直接在这些已授权的字体库中寻找合适的字体,这样可以确保字体的使用完全合法,避免任何版权问题,甚至适用于欧美地区以外的国家。

你喜欢讨论字体吗?有位作家曾写道:“如果音乐是物理学的主观应用,那么字体光栅化几乎肯定是计算机科学的主观应用。”你同意吗?

这个观点提到,如果音乐是物理学的客观应用,那么字体的修复几乎肯定是计算机科学的主观应用。这个说法确实有一定的道理,但它的抽象性很强,难以简单地表示同意或不同意。音乐作为一种艺术形式,它的基础确实是物理学的原理,例如音波的传播和声波的振动。而字体修复则更多依赖于计算机科学中的算法、图形处理等技术,这些过程常常是主观的,因为它们涉及到设计师的选择和程序的执行方式。

可以说,音乐和字体的修复都涉及到将科学与艺术相结合,但在操作方式上有所不同。音乐更多地体现了物理规律,而字体修复则是对视觉元素的计算和再现,这其中充满了个人的判断和技术的干预。因此,这种比喻可以理解,但它的抽象性让人很难明确表示同意或不同意。

看起来大写字母与小写字母之间的字距不太需要考虑。很多字体可能不会处理这些情况

字体在处理大小写时应该考虑到正确的字母修正。如果不处理这一点,就显得非常马虎和不专业。尤其是当名字中包含大小写混合时,例如“Van de Vendor”或“De Boer”,在书写时需要确保每个部分的正确拼写和排版,避免因字体设计不当而导致名字的视觉效果不准确或不协调。名字的大小写应该得到妥善处理,确保看起来整洁、正式,因为这些细节对于个人或品牌的形象非常重要。

Times New Roman 会不会更好?

Times New Roman 是否会更好取决于字体的设计和字距调整(Kerning)。不同的字体在处理字母之间的间距时可能有所不同,而 Times New Roman 在字距调整上通常比一些其他字体更为标准和一致。然而,是否有更好的字距调整(Kerning)取决于具体的应用和字体的版本。有些字体在设计时已经优化了字母之间的间距,而有些可能需要更多的调整才能达到理想的效果。所以,不能简单地说 Times New Roman 就一定会更好,还需要根据实际情况来判断。

有哪些字体/排版系统可以很好地处理字距?Knuth 的 TeX 是唯一的选择吗?

在讨论字体排版和字距调整(Kerning)时,有多个层面需要考虑。首先,字体可能包含特定的字形连接(例如“ff”、“fi”),但操作系统如 Windows 可能不会自动使用这些字形连接。这是因为文本控制系统没有处理这些特殊字符的功能,不能自动将“ff”或“fi”转换成正确的字形连接。

这就引出了一个重要的问题:字距调整不仅仅是字体设计本身的事情,还涉及文本渲染系统是否能够正确处理这些特殊的字符组合。对于 Windows 系统来说,虽然底层的字距调整机制可能是不错的,但很多时候默认的字体在字距调整方面表现不佳,可能是因为这些字体在设计时没有优化字距调整或字形连接。

而像 TeX 这样的排版系统则在这方面做得很好,它能够自动处理诸如“ff”、“fi”等字形连接。如果在 TeX 中使用带有这些连接的字体,它会自动选择正确的字形连接,从而使得排版更加精美。这是因为 TeX 是一个完整的排版系统,它能够在渲染时分析和处理文本。

至于 Windows 系统的字距调整,虽然它本身有支持,但由于许多系统字体设计得并不完善,可能导致字距调整效果不如人意。因此,虽然 Windows 的字距调整并非完全糟糕,但它可能不是最好的,特别是与一些专门设计的排版系统(如 TeX)相比。

嗯……好观点。更多的游戏设计师应该有一个懂字体的人,因为我看到很多游戏中的任务文本和聊天文本很难阅读

在讨论游戏中的字体和排版问题时,提到游戏开发人员通常在排版方面的知识较为薄弱。许多游戏中的任务文本和聊天文本很难阅读,这个问题并非游戏开发者独有。实际上,网页浏览器的排版系统也常常做得不好,尽管它们的任务是展示大量文本。因此,游戏开发者并不是唯一需要改进排版的人,许多从事网页设计的人员在排版上也常常没有做到最好。

不过,了解字体和排版知识对于任何涉及文本展示的工作来说都非常重要,尤其是在游戏开发中,字体和排版直接影响用户体验。因此,尽管开发者并不以排版质量为强项,但如果每个人都能多学习一些关于字体的知识,情况可能会有所改善。

值得一提的是,有些游戏在排版方面表现非常出色,甚至在字体处理上能够做到非常精致,提供了令人印象深刻的排版效果。

关于抽象文本,我同意!所以我问了这个问题。根据你的经验,也许你能理解更多

对于这个抽象的文本,我同意其中的一些观点,正因为如此,我才提出了这个问题。根据我的经验,可能我能理解其中的一些含义,但这段话本身比较笼统,也提出了很多问题,让人不太容易立刻给出明确的看法。它虽然没有什么特别值得反驳的内容,但因为太过模糊,所以我不确定我是否完全同意他们想要表达的意思。

字母 “A” 的左下角被切掉了,但你通过像素搜索边界,为什么会这样?

在这个情况下,问题出在我关闭了额外的空间分配。这本来是我计划修复的部分,但还需要做一些额外的工作。这与先前的 X 轴情况有关。根据我的推测,Windows 系统在绘制时出现了裁剪,因为没有足够的空间。因此,我们需要将图像移动到位图的中间,这样它就能从左侧正常绘制并且不被裁剪。

你玩过《Facade》吗?或者遇到过设计师吗?

明天将专注于解决字母“A”被裁剪的问题。虽然我觉得已经大致知道是哪个区域出了问题,但我们需要深入弄清楚并解决这个问题。实际上,问题可能出在“free step”设置上,我必须修正这个部分,但之前在做时可能忽略了某些其他细节,这部分需要进一步排查。

虽然所有内容都基于“min x”,但目前不太清楚哪里出现了问题。可能需要检查其他地方,看看是否是“size cx”被用到其他地方,或者存在其他因素导致了这一问题。总之,我们将进一步研究并找出原因。

把一切都清空为黑色而不是白色

问题的根源在于之前清空了一切为白色,这其实不该这么做,应该将一切清空为黑色。发现问题后,重新绘制内容时,可能是因为这种清空行为导致了字母“A”出现了问题。经过修正后,现在字母“A”已经恢复正常。

接着,字母“V”看起来可能也被裁剪了。虽然不确定是否存在裁剪问题,但从当前的宽度值来看,它确实给出的尺寸有问题。为了解决这个问题,需要调整宽度,可能要增加宽度的计算,特别是需要考虑到更宽的搜索范围。

目前,计算的宽度返回值明显不准确,因此要在宽度的计算上进行调整。这将确保字母能够正确显示,而不是被裁剪。同时,也考虑使用更好的方法来处理宽度的计算,避免使用过于简陋的解决方案。

经过调整后,字母“V”已经没有被裁剪了,显示效果有所改善。虽然看起来依然不好,但这并不是代码的错,而是由于所使用的字体本身的问题。
在这里插入图片描述


http://www.kler.cn/a/594955.html

相关文章:

  • 深度解析历年蓝桥杯算法题,助力提升编程技能
  • Saga 模式实战 Demo
  • Compose 实践与探索十五 —— 自定义触摸
  • Prometheus Exporter系列-Postgres_Exporter一键部署
  • Java 大视界 -- Java 大数据分布式计算中的通信优化与网络拓扑设计(145)
  • Python 单例模式的 5 种实现方式:深入解析与最佳实践
  • 如何给商品一键换色?图生生AI,告别繁琐修图
  • 【Dify平台】Function Call 模式模式和ReAct模型有什么不同?
  • Compose 实践与探索十六 —— 与传统的 View 系统混用
  • Q2 电商订单数据分析优化
  • QT Quick(C++)跨平台应用程序项目实战教程 3 — 项目基本设置(窗体尺寸、中文标题、窗体图标、可执行程序图标)
  • uniapp整合SQLite(Android)
  • 集成学习(下):Stacking集成方法
  • MANISKILL3:GPU 并行机器人模拟和渲染,用于通用的具身AI
  • 贪心算法(10)(java)跳跃游戏
  • hive的基础函数>>集合函数, 条件函数, 类型转换函数
  • GEO:在AI时代抢占DeepSeekC位?
  • 深度学习项目--基于ResNet和DenseNet混合架构网络论文的复现(pytorch实现)
  • 【大模型理论篇】CogVLM:多模态预训练语言模型
  • python3.13.2安装详细步骤(附安装包)