I’ve done similar work with text wrapping, although not with live updating, but I think my approach will be useful.
You need to decompose your TextLine object into a associated object that looks like this:
TextLine → TextLineRendering
TextLineRendering: property TextLineRenderingUnit(), property totalWidthByContext as Dictionary
TextLineRenderingUnit: property TextLineRenderingStyleRun(), property widthByContext as Dictionary, property spaceAfter as Boolean
TextLineRenderingStyleRun: property text as string, widthByContext as Dictionary, font, bold, size, etc.
TextLineRendering an object that represents the data and code necessary to render its associated TextLine to the output context. It also contains the code that does the rendering.
totalWidthByContext is a Dictionary that associates a rendering context (i.e. a particular Canvas, Picture, etc) with how wide the TextLineRendering is on that context. Storing the information like this gives you the flexibility of having multiple views of your text with different settings (think: zoom) and still maintain performance. Essentially it is the sum of all the widths of the child TextLineRenderingUnit’s widths PLUS whatever space you are inserting in between them during rendering, invisible characters shown at the beginning and ending of each line, indents, etc.
TextLineRenderingUnit represents the smallest bit of text the rendering system will handle at one time without wrapping. Because you’re writing a code editor, I’m assuming you don’t want to use hyphenation – the structure changes if you do want to hyphenate. The Unit will represent text like “Next” and “(-1)” which should never be broken across a line, depending on your style rules. Note the lack of any spaces before or after the text. The rendering system will take care of spacing. A widthByContext Dictionary performs the same function as the corresponding object in the parent.
The Unit object contains one or more TextLineRenderingStyleRun objects which represent the individual styled characters to render. Each StyleRun contains styling information (font, size, bold, etc) as well as a widthByContext Dictionary for the same use as the parent object. When the StyleRun is rendered for the first time for a particular context, the rendering width is stored in the dictionary for future use.
Depending on your style rules, a Unit object representing “(-1)” might contain StyleRun objects like “(”, “-1”, and “)”. Each StyleRun object is rendered with no space in between.
If the spaceAfter property of the Unit is true, a space is rendered. This lets you create style rules that allow breaking in the text “if SomeMethod(-1)” between the “SomeMethod” and the “(” if that is desirable.
You’d create the root level TextLineRendering object any time a TextLine is created or modified, and then save the cacheable values once they are known (during the first rendering process). This will make all subsequent renderings much faster. Remember to invalidate and recreate the TextLineStyleRun object any time its associated TextLine is modified.
I typed this up out of memory; I haven’t looked at my implementation of this system in a while, so there may be holes, but I think it’s a good start.