You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ImageSharp渲染两端对齐文本时如何实现单词基线对齐

问题背景

使用ImageSharp库渲染两端对齐文本时,将文本拆分为单词单独渲染,通过平均分配单词间距实现行首左对齐、行尾右对齐的效果,但出现垂直错位问题:不同单词包含的字符有差异(如带升部的M、带降部的j、仅含x高度字符的over),单词高度不一致,渲染后无法对齐baseline(基线)。

当前渲染效果:
当前渲染效果示例

原有实现代码:

public static IPathCollection RenderJustifiedText(
    string text,
    PointF startPos,
    float width,
    Font font
)
{
    var textOptions = new TextOptions(font);
    var wordsAndWidths = text.Split(' ')
        .Select(
            w =>
            new
            {
                Word = w,
                Width = TextMeasurer.MeasureBounds(w, textOptions).Width,
            }
        ).ToArray();
    var spaceRemaining = width - wordsAndWidths.Select(ww => ww.Width).Sum();
    var eachSpace = spaceRemaining / (wordsAndWidths.Length - 1);
    float xPos = startPos.X;
    List<IPathCollection> allPaths = new();
    foreach (var w in wordsAndWidths)
    {
        var textPath = new PathBuilder()
            .AddLine(
                new PointF(
                    xPos,
                    startPos.Y
                ),
                new PointF(
                    xPos + w.Width,
                    startPos.Y
                )
            ).Build();
        allPaths.Add(
            TextBuilder.GenerateGlyphs(
                w.Word,
                textPath,
                textOptions
            )
        );
        xPos += w.Width + eachSpace;
    }
    return new PathCollection(allPaths.SelectMany(pathCollection => pathCollection));
}
问题原因

原有代码存在三个核心问题导致基线不对齐:

  1. TextOptions未指定垂直对齐规则,默认采用顶部对齐模式,每个单词会以自身字形边界的顶部为基准对齐到传入的路径Y坐标,由于不同单词的字形高度、上下偏移不同,最终基线位置完全不统一。
  2. 直接使用行起始点startPos.Y作为路径Y坐标,startPos.Y是行内容的顶部位置,不是字体基线的位置,没有根据字体的升部参数计算正确的基线偏移。
  3. 未处理单行仅1个单词的边界场景,会触发除0运行时错误。
修复方案
  1. 初始化TextOptions时显式指定VerticalAlignment = VerticalAlignment.Baseline,让文本渲染时以基线为对齐基准。
  2. 通过字体的度量参数计算像素单位下的升部高度,从行顶位置偏移得到整行共用的固定基线Y坐标,所有单词都对齐到这条基线渲染。
  3. 增加单单词边界判断,避免除0错误。

修复后的完整代码:

public static IPathCollection RenderJustifiedText(
    string text,
    PointF startPos,
    float width,
    Font font
)
{
    var textOptions = new TextOptions(font)
    {
        // 配置文本垂直对齐到基线
        VerticalAlignment = VerticalAlignment.Baseline
    };

    // 计算像素单位下的字体度量值,得到行顶到基线的垂直偏移
    FontMetrics fontMetrics = font.FontMetrics;
    float pixelsPerFontUnit = font.Size / fontMetrics.UnitsPerEm;
    float lineBaselineY = startPos.Y + fontMetrics.Ascender * pixelsPerFontUnit;

    // 预计算每个单词的宽度
    var wordsAndWidths = text.Split(' ')
        .Select(w => new 
        {
            Word = w,
            Width = TextMeasurer.MeasureBounds(w, textOptions).Width
        })
        .ToArray();

    // 计算单词间距,处理单单词边界
    float wordSpacing = 0;
    if (wordsAndWidths.Length > 1)
    {
        float remainingSpace = width - wordsAndWidths.Sum(ww => ww.Width);
        wordSpacing = remainingSpace / (wordsAndWidths.Length - 1);
    }

    float currentX = startPos.X;
    List<IPathCollection> renderedGlyphs = new();
    foreach (var word in wordsAndWidths)
    {
        // 所有单词使用统一的基线Y坐标构建渲染路径
        IPath wordBaselinePath = new PathBuilder()
            .AddLine(
                new PointF(currentX, lineBaselineY),
                new PointF(currentX + word.Width, lineBaselineY)
            )
            .Build();

        renderedGlyphs.Add(TextBuilder.GenerateGlyphs(word.Word, wordBaselinePath, textOptions));
        currentX += word.Width + wordSpacing;
    }

    return new PathCollection(renderedGlyphs.SelectMany(paths => paths));
}

内容的提问来源于stack exchange,提问作者Andrew Shepherd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 16:12:16