Лучший способ вычислить индекс символа в строке с учетом смещения пикселя

Связанный вопрос: Получение индекса строки на основе смещения пикселя

Я знаю, что это близко к этому вопросу, но здесь не спрашивается, как это сделать напрямую, а спрашивается, как лучше всего это подделать.


Я реализую свое собственное текстовое поле для Windows Forms (потому что RichTextBox отстой), и я пытаюсь найти лучший способ, учитывая строки, которые были нарисованы на экране, вычислить, над каким символом находится мышь. Проблема в том, что символы могут быть переменной ширины.

Я придумал две возможности:

  1. Выполняйте Graphics.MeasureCharacterRange каждый раз, когда мышь перемещается в режиме бинарного поиска в строке, над которой находится мышь (как предлагается в вопросе, указанном вверху)

  2. Ведите список смещения каждого символа каждой строки.

(1) будет иметь плохую производительность и

(2) будет неэффективно использовать память, а ввод символа станет операцией O(n) (потому что вам нужно настроить смещение каждого символа после него) плюс невозможно сделать именно потому, что Graphics.MeasureCharacterRange не t точный (он возвращает одно значение для одного символа, другое значение для другого символа и совершенно другое значение [которое не равно сумме двух предыдущих значений] для них обоих вместе в одной строке. Например, W будет иметь ширину 16 пикселей. и f будет шириной 5 пикселей, но Wf шириной 20 пикселей.Эти цифры взяты из реального теста.).

Поэтому я ищу лучшую стратегию для этого, предпочтительно ту, которая требует минимального пространства и вычислительной сложности O (1) (хотя я с удовольствием поменяю небольшую эффективность памяти на эффективность скорости).


person Seth Carnegie    schedule 09.09.2011    source источник
comment
вам нужно сохранить текст в текстовом поле или вы можете использовать альтернативный контейнер, например div?   -  person Kris Ivanov    schedule 09.09.2011
comment
@ Крис, что такое div? Я использую winforms, а не HTML. Простите, если в winforms есть такая штука, я о ней никогда не слышал.   -  person Seth Carnegie    schedule 09.09.2011
comment
Это даже не похоже на вопрос HTML, это вопросы WinForms?   -  person Erik Philips    schedule 09.09.2011
comment
Злая преждевременная оптимизация. Мышь перемещается человеком.   -  person Hans Passant    schedule 09.09.2011
comment
ну, вы отредактировали вопрос, добавив тег winform и дополнительный текст для Windows Forms (потому что RichTextBox отстой), возможно, в следующий раз вы добавите это в первоначальный вопрос, и, пожалуйста, не делайте вид, что я неправильно понял вопрос   -  person Kris Ivanov    schedule 09.09.2011
comment
@Kris, да, я отредактировал это, чтобы сделать его более понятным. Но я не знал, что в HTML существуют такие вещи, как Graphics.MeasureCharacterRange. Или что вы можете использовать C# в HTML. И неправильное прочтение вопроса — это не страшно, я делаю это постоянно. Извиняюсь. Не нужно минусовать вопрос.   -  person Seth Carnegie    schedule 09.09.2011
comment
@ Ганс, я не хочу решать это и строить кучу вещей поверх этого, а потом менять их, потому что они медленные, а затем менять все, что построено поверх них. Это моя проблема с аргументом преждевременной оптимизации.   -  person Seth Carnegie    schedule 09.09.2011
comment
Что плохого в этом, так это предпринимать усилия, не зная, нужно ли это. Мера.   -  person Hans Passant    schedule 09.09.2011
comment
@ Ханс, я понимаю, что ты имеешь в виду, но может быть, это огромная трата времени для чего-то, что, вероятно, будет медленным, и мне достаточно искать другой способ.   -  person Seth Carnegie    schedule 09.09.2011


Ответы (1)


Я не думаю, что вам нужно делать 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
    }                              
}
person Erik Philips    schedule 09.09.2011
comment
В любом случае это будет O(n), потому что вам нужно пересчитывать положение каждого слова после символа, который вы вводите в этой строке. И я не могу хранить слова, разделенные пробелами, из-за подсветки синтаксиса. Спасибо хоть. - person Seth Carnegie; 09.09.2011
comment
Я написал алгоритм печати для определения страниц перед печатью с использованием Graphics.MeasureString, и я считаю, что проблема заключалась в том, чтобы убедиться, что используемая графика имеет DPI, равный или превышающий фактический сопоставимый. При использовании монитора я использовал 300 DPI, а при печати использовал 1800 DPI, но только для MeasureString, а не для фактической печати. - person Erik Philips; 09.09.2011
comment
Итак, вы говорите, что каждый тип символа в текстовом поле влияет на ширину КАЖДОГО отдельного символа до этого? Я знаю, что это не работает таким образом, не для любого шрифта. Единственное исключение было бы, если бы вы использовали странное выравнивание (по центру) или полное выравнивание (которое не поддерживается автоматически). - person Erik Philips; 09.09.2011
comment
Не до, а после и в той же строке. И не ширина, а смещение. Это само собой разумеющееся, что мое текстовое поле не выполняет перенос строк, и в этом случае это повлияет на каждый символ после введенного, не только в той же строке, но и на все строки после той, которая получила новый символ. - person Seth Carnegie; 09.09.2011
comment
Но, пересчитав слово, которое было отредактировано, вы знаете смещение, которое нужно применить ко всем словам в этой строке. Ничего не нужно пересчитывать, нужно только обновить TextLocation.BoundingBox.Left и TextLocation.BoundingBox.Right с новым смещением. - person Erik Philips; 09.09.2011
comment
я имею в виду, что вы должны просмотреть каждое слово после того, которое вы набрали, и добавить к ним это смещение. Это означает, что это O (n). Кроме того, как я уже сказал, я не могу разбить текст на слова. - person Seth Carnegie; 09.09.2011