获取绘制文本被截断的位置 GraphicsPath.DrawString

Get the position where drawn text is truncated by GraphicsPath.DrawString

我有几种绘制轮廓文本的方法。细节不重要,但足以说明问题:
(源代码来自

Private Sub FillTextSolid(g As Graphics, bounds As RectangleF, text As String, font As Font, fillColor As Color, sf As StringFormat)
    Using gp As GraphicsPath = New GraphicsPath(),
                                brush As New SolidBrush(fillColor)
        gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, sf)

        g.FillPath(brush, gp)
    End Using
End Sub

正确地将长字符串转换为边界内带有省略号的字符串。例如

Manic Miner is a platform video game originally written for the ZX Spectrum by Matthew Smith and released by Bug-Byte in 1983. It is the first game in the Miner Willy series and among the early titles in the platform game genre.

变成:

Manic Miner is a platform video game originally written for the ZX Spectrum by Matthew Smith and released by Bug-Byte in 1983. It is the first game in the Miner...

一切都很好。我需要的是一种代码方式,可以准确地查看显示了全文的哪一部分。然后,这将用于循环显示相同范围内的文本(如果您愿意,几乎是分页)以显示所有文本。

我查看了 MeasureString 但这似乎没有实现。有什么办法可以辨别吗?在伪代码中,类似于:

Dim textShown as string =  gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, sf).TextShown

谢谢

鉴于之前显示的 FillTextSolid() 方法:

Private Sub FillTextSolid(g As Graphics, bounds As RectangleF, text As String, font As Font, fillColor As Color)
    Using gp As GraphicsPath = New GraphicsPath(),
        brush As New SolidBrush(fillColor),
        format = New StringFormat(StringFormat.GenericTypographic)
        format.Trimming = StringTrimming.EllipsisWord
        gp.AddString(text, font.FontFamily, font.Style, font.Size, bounds, StringFormat.GenericTypographic)
        g.FillPath(brush, gp)
        Dim lastCharPosition = GetPathLastCharPosition(g, format, gp, bounds, text, font)
    End Using
End Sub

您可以使用当前 GraphicsPath、矩形边界、字体大小和用于在图形上下文中绘制文本的样式来计算最后绘制的字符的位置,因此,最后一句话,如有必要。

我在 FillTextSolid() 中添加了对 GetPathLastCharPosition() 方法的调用,该方法负责计算。将描述的对象传递给方法,因为它们是当前配置的(这些设置当然可以随时更改:请参阅底部的动画)。

Dim [Last Char Position] = GetPathLastCharPosition(
    [Graphics], 
    [StringFormat], 
    [GraphicsPath], 
    [RectangleF], 
    [String], 
    [Font]
)

要确定使用 GraphicsPath 对象打印的当前最后一个单词,您不能将字符串拆分为多个部分,例如,白色 space,因为每个字符都是渲染的一部分。

另请注意:要使度量按预期工作,您不能以磅为单位设置绘图字体大小,字体大小必须以像素表示。
您也可以使用 Point 单位,但是当指定 Points 时,GraphicsPath class 会在 EMs 中(正确地)生成一个 Font 度量 - 考虑到 Font Cell Ascent 和 Descent - 这与Font.Height.
您当然可以将度量从 Ems 转换为像素,但它只是无缘无故地增加了复杂性(至少在这个问题的上下文中)。

查看这些详细信息的描述以及如何计算 GraphicsPath EM:

GetPathLastCharPosition() 使用 Graphics.MeasureCharacterRanges to measure the bounding rectangle of each char in the Text string, in chunks of 32 chars per iteration. This is because StringFormat.SetMeasurableCharacterRanges only takes a maximum of 32 CharacterRange 个元素。

因此,我们将 Text 分成 32 个字符的块,获取每个字符的边界区域并验证该区域是否包含 GraphicsPath 中的最后一个点。
GraphicsPath 生成的最后一个点由 GraphicsPath.GetLastPoint().

返回
  • 此过程仅考虑从上到下和从左到右绘制的文本
    它可以适应处理从右到左的语言。

当然,你也可以忽略最后一点,只考虑Region bounds是否落在canvas.

的矩形框外

无论如何,当找到包含最后一个点的区域时,该方法将停止并returns最后一个字符在作为绘图一部分的字符串中的位置。

Private Function GetPathLastCharPosition(g As Graphics, format As StringFormat, path As GraphicsPath, bounds As RectangleF, text As String, font As Font) As Integer
    Dim textLength As Integer = text.Length
    Dim p = path.GetLastPoint()
    bounds.Height += font.Height

    For charPos As Integer = 0 To text.Length - 1 Step 32
        Dim count As Integer = Math.Min(textLength - charPos, 32)
        Dim charRanges = Enumerable.Range(charPos, count).Select(Function(c) New CharacterRange(c, 1)).ToArray()

        format.SetMeasurableCharacterRanges(charRanges)
        Dim regions As Region() = g.MeasureCharacterRanges(text, font, bounds, format)

        For r As Integer = 0 To regions.Length - 1
            If regions(r).IsVisible(p.X, p.Y) Then
                Return charRanges(r).First
            End If
        Next
    Next
    Return -1
End Function

它是这样工作的:

C#版本的方法:

private int GetPathLastCharPosition(Graphics g, StringFormat format, GraphicsPath path, RectangleF bounds, string text, Font font)
{
    int textLength = text.Length;
    var p = path.GetLastPoint();
    bounds.Height += font.Height;

    for (int charPos = 0; charPos < text.Length; charPos += 32) {
        int count = Math.Min(textLength - charPos, 32);
        var charRanges = Enumerable.Range(charPos, count).Select(c => new CharacterRange(c, 1)).ToArray();

        format.SetMeasurableCharacterRanges(charRanges);
        Region[] regions = g.MeasureCharacterRanges(text, font, bounds, format);

        for (int r = 0; r < regions.Length; r++) {
            if (regions[r].IsVisible(p.X, p.Y)) {
                return charRanges[r].First;
            }
        }
    }
    return -1;
}