Skip to content

Instantly share code, notes, and snippets.

@MSGhero
Last active April 1, 2020 07:29
Show Gist options
  • Save MSGhero/d96928cd3370f76e44abb097c07ad80e to your computer and use it in GitHub Desktop.
Save MSGhero/d96928cd3370f76e44abb097c07ad80e to your computer and use it in GitHub Desktop.
Layout-aware type text that word wraps without bumping words down
// just contains excerpts and comments
// does not work on Flash. Fine on Windows and HTML5 at least
// involves diving into OpenFL's private API, so no guarantees this will work forever. But it works now!
// probably errors if you start fiddling with the textfield, like setting formats or appendText()
// SmartWordWrap.hx
class SmartWordWrap extends openfl.text.TextField { // gotta extend TextField to have access to internals
var layoutGroups:openfl.Vector<openfl._internal.text.TextLayoutGroup> = new Vector(); // this will cache character information
// ... etc code
// either override set_text or make a new function
function setFancyText(text:String):Void {
this.text = text;
// you want to set any textformats (bold, colors, whatever) HERE before continuing, or they will get erased or cause issues
__updateLayout(); // tells OpenFL to lay the text out into its final position. This is where the smart word wrap comes from since we will cache this info
var lg:TextLayoutGroup, newLG:TextLayoutGroup;
layoutGroups.length = 0; // clear the cache
// this breaks up the natural text layout groups (low-level text formats) into one per character
var j = 0, k = 0, delta = 0, offsetX = 0.0;
for (i in 0...__textEngine.layoutGroups.length) { // __textEngine.layoutGroups is the native array of text info, where the cache comes from
lg = __textEngine.layoutGroups[i];
delta = lg.endIndex - lg.startIndex;
j = 0; k = 0;
offsetX = lg.offsetX;
while (j < delta) {
// make a new layout group for each character, copying from the original layout group (aka cache text info)
newLG = new TextLayoutGroup(lg.format.clone(), lg.startIndex + j, lg.startIndex + j + 1); // clone the textformat so characters don't share
newLG.ascent = lg.ascent;
newLG.descent = lg.descent;
newLG.height = lg.height;
newLG.leading = lg.leading;
newLG.lineIndex = lg.lineIndex;
newLG.offsetX = offsetX;
newLG.offsetY = lg.offsetY;
newLG.positions = [];
newLG.width = lg.getAdvance(k);
offsetX += newLG.width; // so each character is aware of its correct position independent of surrounding characters
j++;
// this is for Unicode character on Neko and Mac and stuff. Not 100% sure it works
// skip buffer 0s, aka find the real character that contains width
while (k < lg.positions.length && lg.getAdvance(k + 1) == 0) {
newLG.positions.push(lg.positions[k]);
k++;
}
newLG.positions.push(lg.positions[k]);
k++;
layoutGroups.push(newLG); // gather all the layout groups into a new array that we cache for later
}
}
__textEngine.layoutGroups.length = 0; // delete all characters from the textfield's internal array of characters
// __dirty = true; // idk if this is needed. It's not in my code but I'm not sure why! If your text doesn't look empty at first, try this line
}
// here is your existing typing text code. You're probably doing textfield.appendText(), or even worse text += nextChar
// So, stop doing that. layoutGroups contains all the info for each character that the text renderer wants
// Instead of adding a character to the text string, we will add layout groups from the stored layoutGroups cache to the native __textEngine.layoutGroups
function addNextCharacter():Void {
if (__textEngine.layoutGroups.length < layoutGroups.length) { // if there are characters left to type
__textEngine.layoutGroups.push(layoutGroups[__textEngine.layoutGroups.length]); // push the next character from layoutGroups (cached character info) to __textEngine.layoutGroups (the real character info location)
__dirty = true; // VERY IMPORTANT!!! Tells OpenFL that the text needs to be re-rendered. It doesn't do this already because we bypassed the public API where this normally gets set
}
}
// also, feel free to fiddle with the characters in __textEngine.layoutGroups!
// set lg.format.color to change each character's color individually
// lg.offsetX and lg.offsetY are the positions of each character. You can move characters all over (wave effect, jitter, fly into place...)
// just know the overall TF has a crop on it that may cut off your flying letters
}
@MSGhero
Copy link
Author

MSGhero commented Apr 1, 2020

anim text

This is native text manipulation! No extra memory or CPU beyond storing the cache and fiddling with the layout groups.

@intoxopox
Copy link

This is swell!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment