给定像素偏移量计算字符串中字符索引的最佳方法

发布于 2024-12-03 16:37:09 字数 882 浏览 1 评论 0原文

相关问题:根据像素偏移获取字符串索引

我知道这与那个问题很接近,但这不是问如何直接做到这一点,而是问如何最好地伪造它。


我正在为 Windows 窗体实现我自己的文本框(因为 RichTextBox 很糟糕),并且我正在尝试找到最佳方法,根据屏幕上绘制的字符串,计算鼠标悬停在哪个字符上。问题是字符可以是可变宽度的。

我想出了两种可能性:

  1. 在鼠标悬停的线上以二分搜索方式执行Graphics.MeasureCharacterRange 每次鼠标移动(如建议的)在顶部链接的问题中)

  2. 保留每行每个字符的偏移量列表。

(1) 性能会很差,

(2) 内存效率会很低,加上输入一个字符变成 O(n) 操作(因为你必须调整它后面的每个字符的偏移量)加上 不可能精确地做到这一点,因为 Graphics.MeasureCharacterRange 不精确(它为一个字符返回一个值,为另一个字符返回另一个值,以及一个完全不同的值[不等于之前添加的两个值]在一起] 将它们都放在一个字符串中。例如,W 的宽度为 16 像素,f 的宽度为 5 像素,但 Wf 的宽度为 20 像素。这些数字来自实际测试。 .)。

因此,我正在寻找一种更好的策略来做到这一点,最好是需要最小空间和 O(1) 计算复杂度的策略(尽管我很乐意牺牲一点内存效率来换取速度效率)。

Related question: Getting a string index based on a pixel offset

I know this is close to that question, but this isn't asking how to do it directly, it's asking how to fake it best.


I am implementing my own text box for Windows Forms (because RichTextBox sucks) and I am trying to find the best way to, given strings that have been drawn on the screen, calculate what character the mouse is over. The problem is that characters can be variable-width.

I have come up with two possibilities:

  1. Do Graphics.MeasureCharacterRange every time the mouse moves in a binary search fashion on the line that the mouse is over (as suggested in the question linked at the top)

  2. Keep a list of the offset of every character of every line.

(1) Will have bad performance, and

(2) will be memory-inefficient plus make typing a character become a O(n) operation (because you have to adjust the offset of every character after it) plus impossible to do precisely because Graphics.MeasureCharacterRange isn't precise (it returns one value for one character, another value for another character, and a totally different value [that does not equal the two previous values added together] for both of them together in one string. E.g. W will be 16 pixels wide and f will be 5 pixels wide, but Wf is 20 pixels wide. Those numbers are from an actual test.).

So I am looking for a better strategy to do this, preferably one that requires minimal space and O(1) computational complexity (though I will gladly trade off a little memory efficiency for speed efficiency).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

尘曦 2024-12-10 16:37:09

我认为你不必这样做 O(1)。 O(1) 假设每个附加字符都会影响所有先前的字符,但事实并非如此。最多我会看到每个单词的 O(1),这应该是非常快的。听起来你需要的是一种存储方式; 1 每个单词的位置,2 每个唯一单词,3 单词中每个字母的宽度。这将显着减少存储空间并提高查找速度。也许是这样的:

IEnumerable<TextLocation> TextLocations = ...;

internal class TextLocation
{
    public RectF BoundingBox { get; set; }  //this is relative to the textbox
    public TextWord TextWord { get; set; }
}

internal class TextWord
{
    public string Text { get; set; }
    public IEnumerable<LetterInfo> Letters { get; set; }
}

internal class LetterInfo
{
    public char Letter { get; set; }
    public float left { get; set; }  //these would be relative to the bounding box
    public float right { get; set; } //not to the textbox
}

那么你也许可以做类似的事情

var tl = TextLocations.FirstOrDefault(x => x.BoundingBox.Left < Mouse.X 
                                           && x.BoundingBox.Right > Mouse.X
                                           && x.BoundingBox.Top < Mouse.Y
                                           && x.BoundingBox.Bottom > Mouse.Y)

if (tl != null)
{
    //tl.TextWord.Text is the Word ("The", "Lazy", "Dog"...)

    var letter = tl.TextWord.Letters
                   .FirstOrDefault(x => Mouse.x - tl.BoundingBox.left > x.left
                                        Mouse.x - tl.BoundingBox.left < x.right);

    if (letter != null)
    {
        // you get the idea
    }                              
}

I don't think you have to do O(1). O(1) is assuming that every additional character has an affect on ALL previous characters, which it would not. At best I would see an O(1) for each word which should be crazy fast. It sounds like what you need is a way to store; 1 the location of each word, 2 each unique word, and 3 the width of each letter in the word. This would significantly reduce the storage and increase look up speed. Maybe something like:

IEnumerable<TextLocation> TextLocations = ...;

internal class TextLocation
{
    public RectF BoundingBox { get; set; }  //this is relative to the textbox
    public TextWord TextWord { get; set; }
}

internal class TextWord
{
    public string Text { get; set; }
    public IEnumerable<LetterInfo> Letters { get; set; }
}

internal class LetterInfo
{
    public char Letter { get; set; }
    public float left { get; set; }  //these would be relative to the bounding box
    public float right { get; set; } //not to the textbox
}

Then you might be able to do something like

var tl = TextLocations.FirstOrDefault(x => x.BoundingBox.Left < Mouse.X 
                                           && x.BoundingBox.Right > Mouse.X
                                           && x.BoundingBox.Top < Mouse.Y
                                           && x.BoundingBox.Bottom > Mouse.Y)

if (tl != null)
{
    //tl.TextWord.Text is the Word ("The", "Lazy", "Dog"...)

    var letter = tl.TextWord.Letters
                   .FirstOrDefault(x => Mouse.x - tl.BoundingBox.left > x.left
                                        Mouse.x - tl.BoundingBox.left < x.right);

    if (letter != null)
    {
        // you get the idea
    }                              
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文