pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
930 stars 154 forks source link

Support for Individual Text Styling in Strings #2211

Closed that1guy232 closed 1 year ago

that1guy232 commented 1 year ago

Hello,

Currently, the way text is rendered in pygame-ce allows us to apply a specific styling (bold, italic, underline, strikethrough) to an entire string using the respective properties on the font object. However, this functionality becomes limiting when there's a need to apply different styles to different words within the same string.

As it stands, we would need to render each word separately on a surface to apply distinct styles to them. For instance, consider a string where only one word needs to be bold, or italic. Having to render this word separately from the rest of the text can be inefficient, especially when dealing with long strings of text or multiple strings.

Here's the workaround that I've been using:

class TextRender:
    def __init__(self, font=None, color="black"):
        print(pygame.font.get_fonts())
        self.font = pygame.Font(None, 32)
        self.color = color

    def render_text(self, text, rect):

        words = text.split(" ")
        rendered_words = []
        rendred_surf = pygame.Surface(rect.size)

        aa = True
        for word in words:

            if word.startswith("**") and word.endswith("**"):
                self.font.bold = True
                word = word[2:-2]
                rendered_words.append(self.font.render(word, aa, self.color))
                self.font.bold = False
            elif word.startswith("*") and word.endswith("*"):
                self.font.italic = True
                word = word[1:-1]
                rendered_words.append(self.font.render(word, aa, self.color))
                self.font.italic = False
            elif word.startswith("_") and word.endswith("_"):
                self.font.underline = True
                word = word[1:-1]
                rendered_words.append(self.font.render(word, aa, self.color))
                self.font.underline = False
            elif word.startswith("-") and word.endswith("-"):
                self.font.strikethrough = True
                word = word[1:-1]
                rendered_words.append(self.font.render(word, aa, self.color))
                self.font.strikethrough = False
            else:
                rendered_words.append(self.font.render(word, aa, self.color))

            y = 0
            x = 0
            space_between_words = 5
            space_between_lines = 5
            max_line_width = rect.width
            current_line_width = 0

            for word in rendered_words:
                if word.get_width() > max_line_width - current_line_width:
                    y += word.get_height() + space_between_lines
                    x = 0
                    current_line_width = 0

                rendred_surf.blit(word, (x, y))
                x += word.get_width() + space_between_words
                current_line_width += word.get_width() + space_between_words

        return rendred_surf

Therefore, I would like to propose a feature addition that allows styling of individual words within a string. This would enable users to designate styles directly in the string, potentially using markup tags or specific characters, akin to markdown syntax. I believe this feature would enhance the flexibility of text rendering in pygame-ce, giving developers more control over their text presentation and reducing the complexity of the code needed to achieve varied text formatting within a single string.

Now, I'm not sure how difficult this would be to implement and maintain in the long run. So consider this as a suggestion or food for thought. I think it would be a cool feature, and it could make pygame-ce even more flexible.

MightyJosip commented 1 year ago

Good idea, but imo outside of pygame-ce scope.

Considering this part

For instance, consider a string where only one word needs to be bold, or italic. Having to render this word separately from the rest of the text can be inefficient

This is exactly what pygame-ce would do if we would support that. Render one part with one font settings with TTF_RenderUTF8_Solid for example. Then change the font settings with TTF_SetFontStyle, then render again with TTF_RenderUTF8_Solid. Basically unless sdl_ttf changes something itself, pygame-ce solution would be basically your code written in C. And something like markdown parsing is problem in itself, it would be a lot of work for "minor" improvement

Anyways, it would indeed be cool, but I don't think we can accomplish that

MyreMylar commented 1 year ago

Pygame GUI does most of what you are after, though the in string styling is done with classic HTML-like tags e.g.

<b>Hello</b><i>World</i>

Produces: Hello World

See: https://github.com/MyreMylar/pygame_gui https://pygame-gui.readthedocs.io/en/v_069/theme_reference/theme_text_box.html https://pygame-gui.readthedocs.io/en/v_069/pygame_gui.elements.html#module-pygame_gui.elements.ui_text_box

Disclaimer: I wrote most of this library.

MyreMylar commented 1 year ago

I think this is probably out of scope for Pygame-ce and is better passed over to one of the many GUI/Text companion libraries.

Otherwise Pygame-ce would gradually become a large GUI library attached to a small game dev component :)