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
}
}