textratypist icon indicating copy to clipboard operation
textratypist copied to clipboard

Adding custom images to a font results in a false scale/position

Open Trurl101 opened this issue 11 months ago • 7 comments

I have a hard time to display images correctly that I added to a font. If I use a TextureRegion and add it via "font.addImage", bigger images with a size greater than the font will not be scaled correctly and the position is slightly off.

If I add images via "font.addAtlas" the images will be scaled correctly but the position is way off. Probably the image position is based on the original width of the image not the scaled width.

Examples: The image is just a white square (512x512). For addImage I added the image as the ";" and changed text "Did YOU[+white]" to "Did YOU;"

I used the latest commit: "d78d65b60d", but testing with 1.1.0 has similar results. Perhaps I am doing something wrong?

final String text = "Did YOU[+white]know orange is my favorite color? Did YOU know orange is my favorite color?";
final TypingLabel label = new TypingLabel("[%50]" + text, style);
label.setSize(900, 400);
label.setPosition(100, 100);
label.setWrap(true);
label.setDebug(true);
stage.addActor(label);

final TypingLabel label2 = new TypingLabel(text, style);
label2.setSize(900, 400);
label2.setPosition(100, 500);
label2.setWrap(true);
label2.setDebug(true);
stage.addActor(label2);

final TypingLabel label3 = new TypingLabel("[%150]" + text, style);
label3.setSize(900, 400);
label3.setPosition(100, 900);
label3.setWrap(true);
label3.setDebug(true);
stage.addActor(label3);

Adding with addAtlas: Bildschirmfoto 2025-01-06 um 19 21 11

Adding with addImage: Bildschirmfoto 2025-01-06 um 19 21 47

Trurl101 avatar Jan 06 '25 18:01 Trurl101

I don't think I've used addImage() on its own recently (or maybe at all), since I implemented addAtlas() not much time later. I'll take a look now, but I'm pretty surprised the latest commit acts like 1.1.0, since I changed a lot relating to inline images between those two...

So, addAtlas() is mostly meant for the use case of adding many emoji or icons, all approximately square and all approximately the same size as other atlas images, to a Font of any size. The inline images are supposed to scale uniformly so they take up one line height and less or more width to match. The inline image scaling seems to work, but y position is off when font scaling changes, and x position is always off by a lot. I'm guessing that the 512 pixel width of the square may be getting used to position the scaled square, even though it is no longer 512 pixels wide.

Meanwhile, addImage respects the size of the square relative to the text, in some way, but its x-position is off when scaling changes.

The interaction of the two different scales for inline images and for the font has routinely been one of the hardest things to get right. I'll keep trying...

tommyettinger avatar Jan 07 '25 00:01 tommyettinger

Making some progress. I have at least figured out that the existing check for if an inline image is in the range used by addAtlas doesn't find inline images registered to "normal" chars, such as ASCII or Cyrillic chars.

java_RObFsv8nCm

This registers the white square to an atlas and, with addImage(), to \uE777, which is in the private use area where emoji get placed. This does look like it makes it act as an emoji.

Also of note is that the 150% sized version places an inline image outside the bounds of the TypingLabel, as if its actual position isn't known correctly. These are small clues so far, but now I have some idea where to look for each of the two problems.

tommyettinger avatar Jan 07 '25 04:01 tommyettinger

The latest commit may resolve this, but I need to check how well it works on the most common usage, emoji. For the controller button images and the large white square, it works very well.

java_Qb4FchRIwj

tommyettinger avatar Jan 07 '25 06:01 tommyettinger

With addImage the graphic has the correct position, but wrong scale.

Works now perfect with addAtlas!

Trurl101 avatar Jan 07 '25 09:01 Trurl101

Another related little problem, but it is hard to give a exact code example because it relates to font-size and label width. I hope the following example works.

If I just change the color of a text that includes an image it can break the layout under specific circumstances: The image must be bigger than the line height, so I made the text very small in this example, because the emoji images are small.

String text = "Did you know orange is my favorite color? Did[+OK hand, medium-light skin tone] YOU know orange is my favorite color? Did YOU know orange is my favorite color?";

final Font font = KnownFonts.getDejaVuSans();
KnownFonts.addEmoji(font);

final TextraLabel label = new TextraLabel("[#ffff00ff][%50]" + text, font);
label.setSize(300, 300);
label.setPosition(100, 900);
label.setWrap(true);
label.setDebug(true);
stage.addAction(
  Actions.sequence(
    Actions.delay(2.0f),
    Actions.run(() -> {
      label.setText("[#ff0000ff][%50]" + text);
    })
  )
);
Bildschirmfoto 2025-01-07 um 14 00 04

After two seconds the color is changed via the action (the color does not need to change, setting the exakt same text again produces the same result), surprisingly the layout changes too: Bildschirmfoto 2025-01-07 um 13 59 43

This is hard to replicate because it seems only to happen when the line breaks are at specific position and the text change is after at least one frame. A call to regenerateLayout fixes the layout: label.getFont().regenerateLayout(label.layout);

Trurl101 avatar Jan 07 '25 13:01 Trurl101

Wow, that's an odd one! It probably will end up being a difference between markup(), which applies to the first creation or fill of a Layout, and regenerateLayout(), which is supposed to adapt the text for different bounds and wrap accordingly. I think color will be a good way of noticing the difference, but underline [_] or strikethrough [~] should also make regenerateLayout() do the same thing without changing the intended width. Then again, those underline and strikethrough styles are ignored for emoji now, because I don't know why they would be used with images (and Discord also ignores underline and strikethrough for emoji).

tommyettinger avatar Jan 07 '25 22:01 tommyettinger

This last comment is somewhat addressed now, just not the root cause yet. Calling setText() behaves differently if wrap is enabled or not, so now if you call setWrap(true) and wrap had been false before, it will call setText() with the storedText right then. That seems like an appropriate thing to do. However, it means the case where the text wraps in 3 lines never happens, and the 4th line always appears with one word. It's at least the same throughout the lifetime of the TextraLabel.

tommyettinger avatar Jan 09 '25 03:01 tommyettinger