FreeType中如何区分字形内外边框并获取点集分界索引
问题根因
直接遍历FT_Outline点数组连续绘制线段出现跨轮廓连线错误,核心是FreeType的字形轮廓点并非按「外轮廓全部前置、内轮廓全部后置」的线性顺序存储。单个字形的所有闭合路径(外边框、所有内孔洞边框)以独立轮廓段为单位拆分存储,段与段之间不存在连续连线关系,将所有点拼接为单条多段线绘制时,必然会把上一段的终点和下一段的起点错误连接,就是你观察到的字符"d"内外框乱连的现象。
内外轮廓拆分方案
不要尝试通过FT_Outline_GetInsideBorder查找点分界索引——该接口仅返回描边场景下的边界朝向规则,不提供点数组的分段位置信息。正确拆分逻辑依赖FT_Outline自带的轮廓分段字段,配合轮廓方向判断实现:
- 第一步:拆分所有独立闭合轮廓段
FT_Outline结构体的n_contours字段存储当前字形包含的独立闭合轮廓总数,contours是长度为n_contours的整型数组,每个元素的值为对应轮廓段最后一个点在points数组中的下标。
以字符"d"为例,其通常包含2个独立轮廓(外框、内孔),对应n_contours=2,contours数组值形如[m, n]:- 第0段轮廓的点范围为
points[0]至points[m] - 第1段轮廓的点范围为
points[m+1]至points[n]
每段为独立闭合路径,渲染时段与段之间禁止连线,即可消除跨轮廓的错误线段。
- 第0段轮廓的点范围为
- 第二步:区分轮廓段的内外属性
拆分出独立段后,通过轮廓环绕方向判断内外属性:FreeType默认Y轴向下的坐标系下,外轮廓为顺时针走向,内轮廓(孔洞)为逆时针走向;若你的渲染坐标系Y轴朝上,方向对应反转即可。
可直接调用FreeType内置接口FT_Outline_Get_Orientation判断单段轮廓方向,无需自行计算叉乘遍历点:- 将单段轮廓的点、标签信息抽取出来,组装为仅包含1个轮廓的临时
FT_Outline结构体 - 调用
FT_Outline_Get_Orientation(&temp_outline),返回FT_ORIENTATION_TRUETYPE为顺时针(外轮廓),返回FT_ORIENTATION_POSTSCRIPT为逆时针(内轮廓)
- 将单段轮廓的点、标签信息抽取出来,组装为仅包含1个轮廓的临时
- 关于
FT_Outline_GetInsideBorder的适用场景
该接口仅在配合FT_Stroker生成带宽度的描边轮廓时生效:描边操作会将原始单条轮廓扩展为内侧、外侧两条平行路径,此时可通过该接口判断两组路径的内外属性,读取原始字形轮廓时无需使用该接口。
实现参考代码
// 前置条件:已加载完成字形,FT_Outline* outline = &face->glyph->outline; std::vector<std::vector<FT_Vector>> outer_contours; // 存储所有外轮廓点集 std::vector<std::vector<FT_Vector>> inner_contours; // 存储所有内轮廓点集 int contour_start = 0; for (int cidx = 0; cidx < outline->n_contours; cidx++) { int contour_end = outline->contours[cidx]; // 抽取当前轮廓的所有点 std::vector<FT_Vector> cur_points; cur_points.reserve(contour_end - contour_start + 1); for (int pidx = contour_start; pidx <= contour_end; pidx++) { cur_points.push_back(outline->points[pidx]); } // 构造临时outline用于方向判断 FT_Outline temp_outline{}; temp_outline.n_contours = 1; temp_outline.n_points = cur_points.size(); temp_outline.points = cur_points.data(); // 补全临时outline必要字段,避免方向判断出错 std::vector<FT_Byte> temp_tags(outline->tags + contour_start, outline->tags + contour_end + 1); int temp_contour_pt_end = (int)cur_points.size() - 1; temp_outline.tags = temp_tags.data(); temp_outline.contours = &temp_contour_pt_end; // 判断方向,分内外存储 int orient = FT_Outline_Get_Orientation(&temp_outline); if (orient == FT_ORIENTATION_TRUETYPE) { // 若你的渲染坐标系Y轴向上,此处需将内外存储逻辑反转 outer_contours.push_back(std::move(cur_points)); } else { inner_contours.push_back(std::move(cur_points)); } contour_start = contour_end + 1; } // 渲染逻辑:分别遍历outer_contours、inner_contours,每个点集单独绘制闭合线段,禁止跨点集连线
多层嵌套轮廓(例如带孔洞的字内部还有实心结构)同样适用该规则:奇数层顺时针为实心外沿,偶数层逆时针为孔洞,FreeType生成的轮廓方向严格匹配非零填充规则。
内容的提问来源于stack exchange,提问作者AuGong
相关产品推荐
相关产品推荐

