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
770 stars 120 forks source link

Add outline option for rendering fonts #2828

Open narilee2006 opened 2 months ago

narilee2006 commented 2 months ago

Hello! I tried doing https://github.com/pygame-community/pygame-ce/issues/2452. Let me know if there are any issues.

narilee2006 commented 2 months ago

Outline color isnā€™t supported

It actually is, you need to call it in the font.render() method.

Looking at my notes from before (havenā€™t run this yet), doesnā€™t this PR mean that people have to call font.render twice? Once for the text, then once for the outline. That seems inconvenient for users.

Yep, that's how SDL2 implemented it.

narilee2006 commented 2 months ago

Perhaps outline as a kwarg so the developer doesn't need to use two lines to create the font?

oddbookworm commented 2 months ago

Maybe perhaps

pygame.font.Font(filename=None, size=20, outline=0)

for the font constructor and

pygame.font.Font.render(text, antialias, color, bgcolor=None, wraplength=0, outline_color=None)

for the render call?

But I'm not sure what to do if outline_color defaults when outline was set to something nonzero

Starbuck5 commented 2 months ago

Perhaps outline as a kwarg so the developer doesn't need to use two lines to create the font?

Well then how do you do the color? Another kwarg? Kwargs are expensive to parse in functions that get called all the time, too.

I think my second proposal in the issue would work well (minus the render outline only thing, I think thatā€™s unnecessary now)

font.outline_width (attribute, get/set with TTF_ outline)
font.outline_color (attribute, defaults to black)

Render with an outline:
font.render
narilee2006 commented 2 months ago

You can do the outline color by specifying it in the render() method with no modifications to the existing code.

I'm personally against the outline_color parameter as existing draw methods have the width parameter to determine their border size; if the width parameter is greater than 0 it won't fill the shape and only draw the borders.

https://pyga.me/docs/ref/draw.html

narilee2006 commented 2 months ago

Well then how do you do the color? Another kwarg? Kwargs are expensive to parse in functions that get called all the time, too.

@Starbuck5, sorry I meant the outline kwarg in the constructor of the Font object.

MyreMylar commented 2 months ago

I agree with @Starbuck that this should be implemented as a styling attribute to Font - similar to the current styling attributes bold, italic & underline. The current .render() function is already overlong with parameters like the 'antialias' parameter that definitely should have been an attribute defaulting to the most common usage (true).

Proposed test program:

import pygame
from pygame import Color, Font, QUIT

pygame.init()
window_surf = pygame.display.set_mode((400, 200))

impact_font = Font(filename="fonts/impact.ttf", size=64)

impact_font.outline_width = 2  # defaults to 0
impact_font.outline_color = Color("red")  # defaults to black

text_with_outline = impact_font.render(
    text="Hello World", antialias=True, color=Color("white")
)

running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False

    window_surf.fill(Color("black"))

    window_surf.blit(text_with_outline, (50, 50))

    pygame.display.update()

font.render can just check if outline_width is greater than 0 or not - and if so draw the outline before or after the main font rendering code - whichever looks better.

As an aside maybe we could make color and antialias into arguments with default values. We almost certainly would have already converted 'antialias' if it was on the other side of 'color' in the argument list. At least that way you could do:

font.render("Hello World", color=Color("white"))

Which isn't a typing saving over:

font.render("Hello World", True, Color("white"))

But probably clearer to read.

Might also open up:

font.default_color = Color("red")

hw_text_surf = font.render("Hello World in red")
other_text_surf = font.render("Other text, also in red")
narilee2006 commented 2 months ago

Okay... I'll get to work on this.

robertpfeiffer commented 2 months ago

Overall, this is promising.