SUGGESTION: DynamicSprite.CreateFromString/WORKAROUND: Aligned Textual Overlays

Started by monkey0506, Thu 11/10/2007 21:22:00

Previous topic - Next topic

monkey0506

Wouldn't it be nice if there were some way to render text as a sprite?

Workaround:

Code: ags
DynamicSprite* RenderTextAsSprite(int width, FontType font, int color, String text, Alignment align) {
  if ((width <= 0) || (font < 0) || (font >= Game.FontCount) || (text == null) || (text == "")) return null;
  DynamicSprite* sprite = DynamicSprite.CreateFromScreenShot(width, GetTextHeight(text, font, width));
  DrawingSurface* surface = sprite.GetDrawingSurface();
  surface.Clear();
  if (color < 0) color = 0;
  surface.DrawingColor = color;
  String buffer = "";
  int i = 0, w = 0, y = 0;
  while (i < text.Length) {
    if (GetTextWidth(buffer.AppendChar(text.Chars[i]), font) <= width) buffer = buffer.AppendChar(text.Chars[i]);
    else {
      while ((buffer.Contains(" ") != -1) && (buffer.Chars[buffer.Length - 1] != ' ')) {
        buffer = buffer.Truncate(buffer.Length - 1);
        i--;
        }
      w = GetTextWidth(buffer, font);
      if (align == eAlignLeft) surface.DrawString(0, y, font, buffer);
      else if (align == eAlignCentre) surface.DrawString((width - w) / 2, y, font, buffer);
      else if (align == eAlignRight) surface.DrawString(width - w, y, font, buffer);
      y += GetTextHeight(buffer, font, w + 1);
      i--;
      buffer = "";
      }
    i++;
    }
  w = GetTextWidth(buffer, font);
  if (align == eAlignLeft) surface.DrawString(0, y, font, buffer);
  else if (align == eAlignCentre) surface.DrawString((width - w) / 2, y, font, buffer);
  else if (align == eAlignRight) surface.DrawString(width - w, y, font, buffer);
  surface.Release();
  return sprite;
  }


Such a function would also provide a workaround for the outstanding request for aligned textual overlays (using a graphical overlay instead):

Code: ags
Overlay* OverlayCreateTextualAligned(int x, int y, int width, FontType font, int color, String text, Alignment align) {
  DynamicSprite* sprite = RenderTextAsSprite(width, font, color, text, align);
  if (sprite == null) return null;
  return Overlay.CreateGraphical(x, y, sprite.Graphic, true);
  }


BUG NOTE: Scripting the workaround for the text rendering function actually led me to discover what I believe would be considered bugs in AGS. For example, when using hyphens (-) and periods/full stops/dots (.) as separators instead of whitespace (to see how AGS handled width-based line-breaking), I often found that characters at the end of the line were lost. Even when using Character.Say I found similar results with characters disappearing. Since I considered this a bug, I've tried to make sure that the above workaround function does not lose such characters in the jet stream.

Pumaman

DrawingSurface.DrawMessageWrapped almost does this, except that it's based on a room message rather than a script string. If this method was adapted to take a String and an Alignment, would that resolve the situation for you?

monkey0506

Well basically that is what I've done I suppose. 8)

However, did you see this part?

Quote from: monkey_05_06BUG NOTE: Scripting the workaround for the text rendering function actually led me to discover what I believe would be considered bugs in AGS. For example, when using hyphens (-) and periods/full stops/dots (.) as separators instead of whitespace (to see how AGS handled width-based line-breaking), I often found that characters at the end of the line were lost. Even when using Character.Say I found similar results with characters disappearing. Since I considered this a bug, I've tried to make sure that the above workaround function does not lose such characters in the jet stream.

If it makes any difference in trying to replicate the problem I was using the String "This is a long message that should wrap to multiple lines.", width 150 pixels, default speech font.

On the note of the function however...couldn't DrawingSurface.DrawString just be made to take width and Alignment parameters? I understand that the basic idea behind leaving in the "..." part is backward compatibility...but all that would really need to be modified would be to encapsulate the String parameter and the formatting parameters with a String.Format(). Now that we can call String.Format to supply formatting Strings as parameters to functions, the ability of individual functions to format strings isn't as important (at least IMO).

Pumaman

If there are no spaces in your string, AGS isn't very good at wrapping it. I'll take a look and see if I can improve things.

monkey0506

I'd noticed. ;D Thanks for taking a look at this though Chris. You're the greatest.

naltimari

I was after the same thing (render text as sprite), but this code only works in AGS 3.0 (beta). Can you point me to some code that does this for AGS 2.72?

Actually, I ended up coding something myself:

Code: ags

DynamicSprite *ds;

function TextAsSprite(String s, int color)
{
	int o = GetGameOption(OPT_ANTIALIASFONTS);
	int w = GetTextWidth(s, Game.NormalFont);
	int h = GetTextHeight(s, Game.NormalFont, w);

	SetGameOption(OPT_ANTIALIASFONTS, 0);
	RawSaveScreen();

	RawSetColorRGB(255,0,255);
	RawDrawRectangle(0, 0, w, h);
	RawSetColor(color);
	RawPrint(0, 0, s);
	ds = DynamicSprite.CreateFromBackground(0, 0, 0, w, h);

	RawRestoreScreen();
	SetGameOption(OPT_ANTIALIASFONTS, o);

	return ds.Graphic;
}


Which I can use by calling, for example,

Code: ags

RawDrawImageTransparent(20, 20, TextAsSprite("Hello, World", 15), 50);


The above code works, but it has a couple of disadvantages:

  • Cant' use antialiased text (otherwise text edges have a 'pink' matte)
  • Function returns int, not DynamicSprite*, so you cant have access to properties of the sprite (height, width, etc)

Ashen

Quote
Function returns int, not DynamicSprite*, so you cant have access to properties of the sprite (height, width, etc)
What if you create a new DynamicSprite, based on the returned int value, e.g:
Code: ags

DynamicSprite *sprite = DynamicSprite.CreateFromExistingSprite(TextAsSprite("Hello, World", 15));
RawDrawImageTransparent(20, 20 - sprite.Height, sprite.Graphic, 50);


You could also declare TextAsSprite as a DynamicSprite rather than a function:
Code: ags

DynamicSprite *TextAsSprite(String s, int color)
{
	// Blah blah blah

	return ds;
}


However, I think you'd still need to use a 'dummy' DynamicSprite to access the properties - you couldn't directly use
TextAsSprite("Hello, World", 15).Graphic, for example - so you might as well stick with using function.
I know what you're thinking ... Don't think that.

monkey0506

The benefit to making it return a DynamicSprite* instead of returning an int would be that when you do:

Code: ags
DynamicSprite* ds = TextAsSprite(...);


Only one DynamicSprite is actually created in memory whereas:

Code: ags
DynamicSprite* ds = DynamicSprite.CreateFromExistingSprite(TextAsSprite(...));


Creates two sprites.

naltimari

QuoteThe benefit to making it return a DynamicSprite* instead of returning an int would be that...

Exactly. But AFAIK, AGS 2.72 functions can't return any pointers, right? From what I saw on your first post, AGS 3.0 changed that. :)

monkey0506

Nooooooo, AGS 2.72 functions can return pointers. Just replace function with DynamicSprite*.

SMF spam blocked by CleanTalk