For a recent project I've been working on a simple word processor, and because I need fine-grained control have had to implement a lot of the text shaping myself. Most of this is fairly straight-forward and described in detail places like here and here.
It's less obvious how to handle pressing down or up on the keyboard when dealing with non-monospaced text split across many lines. In monospaced text the algorithm is simple: move the text caret down one line and the same number of characters to the right that it was. But what about variable-width fonts? I've tried an algorithm like this (in pseudocode):
; Return text offset into next line after navigating down
function moveCaretDown():
Move text caret to start of next line
targetPixelOffset := previous pixel offset of caret in line above
textOffsetIntoLine := 0
pixelOffsetIntoLine := 0
prevDelta := Infinity
for each char in text of new line:
delta = abs(pixelOffsetIntoLine - targetPixelOffset)
; We are now further from the desired cursor offset than before, this must
; be closest slot to the caret's previous horizontal offset in this line.
if (delta > prevDelta):
return textOffsetIntoLine - 1
prevDelta = delta
pixelOffsetIntoLine += measureWidth(char)
currentOffset++
; Else return the offset of the last character in the line
return length of newline - 1
But I've found its behavior differs from text inputs in major web browsers and/or text editors (I can come up with some specific examples if needed). Is there some standard algorithm for this used by GUI toolkits or text shaping libraries? I was surprised I couldn't find a W3C standard on it, for example, considering this is behavior needed in every web browser.
* Inserting line breaks into a string at the correct places, handling ragged or fully-justified text, etc.
I don't think there's a standard other than to follow the Principle of Least Astonishment. Nowadays, that typically means seeing what the major applications do, since that will likely be familiar behavior to the user.
On the current line, you know the current horizontal offset. Let's call it x
. I'm talking about the pixel position, not the number of characters or glyphs since the beginning of that line.
On the destination line, there is a set of horizontal offsets the caret can be placed (e.g., between glyphs). So you want to pick the one of those that's as similar to your current x
as possible.
Furthermore, if the user moves the caret vertically several times in a row, you probably want to find the nearest to the original x
. The caret may wiggle horizontally a bit as the user moves up and down, but you don't want it to drift. Once the user does something that intentionally changes the horizontal offset (e.g., inserts a character, uses a horizontal arrow, clicks the mouse, etc.) that's the best time to update x
.
If you already have code to find the closest caret position to a mouse click, you might be able to re-use it as though the user had clicked the point exactly one line above or below the current x
.
I've also seen some editors (including monospace text editors) that treat the end of the line as a special case. So if you move up or down when you're at the end of a line, you move to the end of the preceding or succeeding line. That seems a nice way to handle ragged right text and short lines at the end of a paragraph.