Incredibly annoying GetTextWidth vs auto line split behavior

Started by Crimson Wizard, Thu 23/02/2017 16:59:23

Previous topic - Next topic

Crimson Wizard

I believe this is brought on forums every now and then, this is the earliest thread I found that gives a working solution, so I'd mention this for a reference here:
http://www.adventuregamestudio.co.uk/forums/index.php?topic=35731.0

So, what is this all about. Imagine you want to create an Overlay having very particular width, precisely to let your text fit in. What do you do?
Basic example:
Code: ags

function MakeOverlay(String s, int x, int y, FontType font, int color, int wait)
{
    int width = GetTextWidth(s, font);
    Overlay *o = Overlay.CreateTextual(x, y, width, font, color, s);
    Wait(wait);
}


That looks correct, right? Well, it should be. However, when you run the game, you find out that your text is split into more lines than you expected. Try calling this example with a short line, like "Welcome!". You may end up having "Welco" on one line and "me!" on another (depends on font).

Why the heck does it happen?

This is because when you call GetTextWidth - a correct width of text is calculated, however when AGS splits line for drawing (on overlay, on label, Display function, Say function, DrawStringWrapped function, etc), it does additional and undocumented changes to the width parameter:

1) First of all, it decreases width by (2 * padding) pixels, where padding is a specific value which depends on the type of GUI used for display (like TextWindow).
If no GUI is used for Display function (by default), then padding = 3.
Some functions do not do this, like DrawStringWrapped, because it is drawn raw on a DrawingSurface. But even then, there is another thing:

2) When calculating how many characters fit into single line, AGS does a small mistake in condition:
Code: cpp

// otherwise, see if we are too wide
else if (wgettextwidth_compensate(theline, fonnt) >= wii) {

Notice '>=' sign. It means that even if the text fits into the width precisely, it will still will be split at that point, resulting in at least 2 lines where 1 would be enough.

In other words, to work around this issue, you script should look like:
Code: ags

function MakeOverlay(String s, int x, int y, FontType font, int color, int wait)
{
    int width = GetTextWidth(s, font);
    width = width + 2*3 + 1; // 2*3 is double default padding, and 1 pixel is to counter wrong condition
    Overlay *o = Overlay.CreateTextual(x, y, width, font, color, s);
    Wait(wait);
}



I think something must be done here. I am not yet sure what. There is no problem in modifying calculations, because we know how to detect old games and process them differently to keep backwards compatibility. Fixing the condition in line-split function is easy, but I am not sure how to deal with the padding.


EDIT
What I mean about padding. We certainly cannot remove it out of equation, because then the text will be drawn over TextWindow borders, and such. But then there should be two things:
1) Firstly, it should be clearly documented that overlay's and display window's "width" - that is not just text width, but text width + padding.
2) Secondly, user must have means to get that padding somehow, to use it when arranging stuff on screen.


PS
While I am at this, there is another known issue that you should use "width + 1" in the call to GetTextHeight. Probably because of same line splitting mistake.

Monsieur OUXX

I have no idea what this is about but I'm always enthusiastic about engine inner workings clarifications.
 

Snarky

Had this issue with a hotspot tooltip module I made. I just kept increasing the width until it worked, but I never knew exactly why. Thanks!


Crimson Wizard

#4
Quote from: monkey0506 on Sat 17/06/2017 11:04:42
Magic numbers!

I did similar thing in my module's demo game :)

Code: ags

  int text_width = GetTextWidth(text, font);
  // counter default display padding and mistake in built-in line splitting condition,
  // trying to match AGS behavior on line splitting.
  text_width = text_width + DEFAULT_DISPLAY_PADDING + INTERNAL_LINE_SPLIT_MISTAKE;
  <...>
  int text_height = GetTextHeight(text, font, text_width - (DEFAULT_DISPLAY_PADDING + INTERNAL_LINE_SPLIT_MISTAKE) + 1);
  text_height = text_height + DEFAULT_DISPLAY_PADDING; // counter built-in overlay padding


Where
Code: ags

// Default padding that AGS subtracts from the width given to fit the text in,
// calculated as default padding 3 multiplied by 2 (both sides).
#define DEFAULT_DISPLAY_PADDING 6
// Number of pixels to counter some uncertain mistakes in the internal text
// splitting calculations of AGS.
#define INTERNAL_LINE_SPLIT_MISTAKE 2


PS. ...Hey, it looks like our magic numbers match?! At least there is 6 and 2 there...

monkey0506

Looks like we probably had similar results. Since I was specifically emulating speech, I also had to account for the "feature" whereby if a character is standing in the far left or far right quarter the screen's horizontal space, the available width for character speech is truncated by 1/5 of the width of the screen... 8-0 Chris Jones was kind enough to cue me in on that.

SMF spam blocked by CleanTalk