1

我有几种绘制轮廓文本的方法。这方面的细节并不重要,但它可以说明问题:
Graphics DrawPath 的源代码在渲染文本时会产生意想不到的结果

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 是一款平台视频游戏,最初由 Matthew Smith 为 ZX Spectrum 编写,由 Bug-Byte 于 1983 年发行。它是 Miner Willy 系列的第一款游戏,也是平台游戏类型的早期游戏之一。

变成:

Manic Miner 是一款平台视频游戏,最初由 Matthew Smith 为 ZX Spectrum 编写,由 Bug-Byte 于 1983 年发行。它是 Miner 中的第一款游戏...

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

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

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

谢谢

4

1 回答 1

1

给定FillTextSolid()前面显示的方法:
Graphics DrawPath 在渲染文本时产生意外的结果

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 对象打印的当前最后一个单词,您不能将字符串拆分为由空格分隔的部分,因为每个字符都是渲染的一部分。

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

请参阅这些详细信息的描述以及如何计算 GraphicsPath EM:
使用 GraphicsPath 正确绘制文本

GetPathLastCharPosition()使用Graphics.MeasureCharacterRanges测量文本字符串中每个字符的边界矩形,每次迭代以 32 个字符为单位。这是因为StringFormat.SetMeasurableCharacterRanges最多只需要 32 个CharacterRange元素。

因此,我们以 32 个字符为单位获取文本,获取每个字符的边界区域,并验证该区域是否包含 GraphicsPath 中的最后一个点。
GraphicsPath 生成的最后一个点由GraphicsPath.GetLastPoint()返回。

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

当然,您也可以忽略最后一点,只考虑区域边界是否落在画布的边界矩形之外。

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

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

这是它的工作原理

GraphicsPath 最后绘制字符

该方法的 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;
}
于 2021-09-02T20:17:50.187 回答