Broken Text Wrapping algorythm


  • Notification Spam Recipient

    I'm trying to wrap my head around some of the assumptions in this code:

    
    	//// Convert to FString for modification later
    	FString BodyString = Body->Text.ToString();
    
    	// Get number of lines of text and number of characters per line for proper formatting
    	SizePerCharacter = BodyTextSize / BodyString.Len();
    	float NumberOfLines = BodyString.Len() / MeshSize;
    	float CharactersPerLine = BodyString.Len() / NumberOfLines;
    	if (BodyTextSize > MeshSize * SizePerCharacter)
    	{
    		// Using calculated number of characters per line, iterate through the string checking where the maximum amount of characters would fit in one line, if a space is not present at this index, iterate backwards until a space is found and then insert the new line character.
    		for (int32 Index = CharactersPerLine; Index < BodyString.Len(); Index = Index + CharactersPerLine)
    		{
    			FString CurrentChar = BodyString.Mid(Index, 1);
    			while (!CurrentChar.Equals(" ") && Index > 0)
    			{
    				Index = Index - 1;
    				CurrentChar = BodyString.Mid(Index, 1);
    			}
    			BodyString.InsertAt(Index, TEXT("<br>"));
    		}
    
    		Body->SetText(FText::FromString(BodyString));
    	}
    

    Some Assumptions:

    • We're using fixed-width font here.
    • If the text happens to be really long, there will always be a space within CharactersPerLine to split on.

    Context: This is supposed to ensure that text doesn't exceed the (horizontal) bounds of the mesh it's supposed to be printed on.

    Problem: It tends to break easily. For instance, when displaying a too-long string that contains no spaces, it will endlessly apply the "<br>" string and never return. Guess how I found that out? :P

    Strangely enough, this code was written apparently due to the fact that UTextRenderComponent doesn't have this functionality built-in for raisins (though it does support multi-line rendering...).

    I found the code that UE4 is using for their Slate Widget things, but that's ~250 extra lines of code that could potentially be running every tick...

    I'm half considering just adding a check for zero and a check for an existing "<br>" and smashing in more at CharactersPerLine. Luckily we're not considerate of Unicode, yeah? :facepalm:

    Conundrum: Do it right and slow, or do it quick, dirty, and ugly?


  • Discourse touched me in a no-no place

    @tsaukpaetra said in Broken Text Wrapping algorythm:

    I found the code that UE4 is using for their Slate Widget things, but that's ~250 extra lines of code that could potentially be running every tick...

    […]

    Conundrum: Do it right and slow, or do it quick, dirty, and ugly?

    Why would you run this every timer tick? People can't read text that changes that fast. Line wrapping really doesn't change that frequently, and you can cache your results aggressively.

    But what you should actually do is make it cheap yet also support hyphenation points (which you insert in a precompute phase when building assets, of course, which will be comparatively cheap) so that you can guarantee that you won't ever have gigantic unbreakable messages. Then you can use a cheap algorithm that has bad failure modes because you are guaranteeing that you won't ever hit them.


  • Notification Spam Recipient

    @dkf said in Broken Text Wrapping algorythm:

    Why would you run this every timer tick?

    I personally wouldn't, but some delegates like to (:doing_it_wrong: ). I did say "potentially" after all.

    @dkf said in Broken Text Wrapping algorythm:

    you can cache your results aggressively.

    That's part of the ~250 lines mentioned if I'm reading it right, but the cache gets invalidated every time the text changes (which is reasonable), and in the event the text is getting updated every tick anyways it's unlikely to help.

    @dkf said in Broken Text Wrapping algorythm:

    But what you should actually do is make it cheap yet also support hyphenation points (which you insert in a precompute phase when building assets, of course, which will be comparatively cheap) so that you can guarantee that you won't ever have gigantic unbreakable messages. Then you can use a cheap algorithm that has bad failure modes because you are guaranteeing that you won't ever hit them.

    Oh good, that looks like what UE4's Slate interface thing is doing 👍 But, as mentioned, probably not good to do every tick.

    I'm going to try wiring up their TextLayout widget and see if I can get it outside of the UMG widgets thing. Then, I'll just use that and yell at anyone trying to update the text every frame....


  • Discourse touched me in a no-no place

    @tsaukpaetra said in Broken Text Wrapping algorythm:

    Then, I'll just use that and yell at anyone trying to update the text every frame....

    UIs that use super-frequent updating (like that Paperclips game) tend to not want very much text wrapping. Or careful thought about what they're doing anyway. And most people's hardware only updates their display a very limited number of times a second.



  • @tsaukpaetra OMFG.

    SizePerCharacter = BodyTextSize / BodyString.Len();
    

    ...what? Are we worried that the string might be UTF-16 or something? (obviously it's assuming that all characters are the same size, and the code that breaks it doesn't care about such things as surrogate pairs or multi-byte characters -- it attempts nothing so fancy)

    float CharactersPerLine = BodyString.Len() / NumberOfLines;
    

    ...but NumberOfLines is BodyString.Len() / MeshSize... so...

    float CharactersPerLine = BodyString.Len() / (BodyString.Len() / MeshSize);
    

    ...in other words, aside from any floating-point math errors, it's mathematically identical to MeshSize... :wtf:

    Just loop through it once, break at the last space before CharactersPerLine like it's doing, or break if you get to CharactersPerLine and haven't found any. Get rid of all that crap about trying to calculate stuff; the only thing you should care about is CharactersPerLine. If your code even cares about the number of lines, it should be determined by starting a counter at 1 and incrementing it by 1 every time it adds a line break.

    Also have it actually change the index when it inserts text. As it is, it's inserting <br> and then I presume it's counting <, b, r, > as 4 characters at the beginning of the next line.


  • Notification Spam Recipient

    @dkf said in Broken Text Wrapping algorythm:

    And most people's hardware only updates their display a very limited number of times a second.

    It's a VR title, we're stuck at 45 or 90 FPS.


  • Notification Spam Recipient

    @anotherusername said in Broken Text Wrapping algorythm:

    OMFG.

    I was actually going to comment on float CharactersPerLine being assigned to int32 Index = CharactersPerLine, but yeah. This is fantastically crazy in many ways...



  • @tsaukpaetra I can't figure out what it's trying to do. What is "MeshSize?"

    Also another question:

    You're doing HTML parsing every frame (shown by you inserting BR tags for line breaks), but you're worried about 250 lines of code?

    Kiboshing the HTML parsing and just making your string a raw string would save a shitload more work than 250 lines' worth of wrapping code.

    And yes as someone else pointed out, the infinite loop is due to it inserting characters then neglecting to advance the character index.


  • Notification Spam Recipient

    @blakeyrat said in Broken Text Wrapping algorythm:

    You're doing HTML parsing every frame (shown by you inserting BR tags for line breaks), but you're worried about 250 lines of code?

    No, it's not actually HTML. UE4 just uses that string to indicate a line break for some unholy reason.



  • @tsaukpaetra Ok; what's MeshSize? I don't get how one single number represents the size of the mesh.

    Does the UE4 API have a function call to get character metrics (aka the size of a character in pixels)? Because this seems like a horrible hack to work around a lack of that.

    If UE4 doesn't have a character metrics API call, then I'd say 250 lines is about right for a function to do this, if it has to be flexible enough to handle fixed-width and variable-width fonts.


  • Notification Spam Recipient

    @blakeyrat said in Broken Text Wrapping algorythm:

    @tsaukpaetra Ok; what's MeshSize? I don't get how one single number represents the size of the mesh.

    Sorry, MeshSize is... the ...? Oh, apparently it's the diameter of the representative circle the textbox's mesh fits it. Or something. It seems to be the width in whatever-units this fake text box is.

    @blakeyrat said in Broken Text Wrapping algorythm:

    Does the UE4 API have a function call to get character metrics (aka the size of a character in pixels)? Because this seems like a horrible hack to work around a lack of that.

    Yes, but it doesn't look like it's being used at all.

    @blakeyrat said in Broken Text Wrapping algorythm:

    If UE4 doesn't have a character metrics API call, then I'd say 250 lines is about right for a function to do this, if it has to be flexible enough to handle fixed-width and variable-width fonts.

    Sure, 250 lines I don't want to write myself. I'm looking to see if I can use the UMG code stuff here so I don't have to worry about any of these things, but that's usually used in the Widgets system, and I'm not sure if I can easily pull out everything I need piecewise if it doesn't like being created outside that environment...



  • @tsaukpaetra Well you really shouldn't have to. Surely UE4 has GUI libraries you can lean on here, right? I mean wrapping text is like "QuickDraw 1.0 in 1984"-level shit.


  • Notification Spam Recipient

    @blakeyrat said in Broken Text Wrapping algorythm:

    @tsaukpaetra Well you really shouldn't have to. Surely UE4 has GUI libraries you can lean on here, right? I mean wrapping text is like "QuickDraw 1.0 in 1984"-level shit.

    Right. But then politics happened.

    0_1514495007047_ec7cf42c-3d8b-4db1-9758-287615cb78cd-image.png
    0_1514495073756_9e1e7855-db27-4aa3-8dde-f34c7a77d118-image.png
    0_1514495138155_89bfde17-9abb-415e-8c40-230637a2b346-image.png

    So, I bodged in a sliding window (I think that's the right term?) and added more characters to the detection list. Good enough for now. :sadface: