2
\$\begingroup\$

In order to practice my programming skills, I am trying to implement a Button class, to use in pygame. I know there are libraries like PGU, I do this to improve in programming.

I spent a bit of time creating a function to find a good font size for the text inside the button, depending on the length of the text and the dimensions of the button.

I came to this result, it's close enough to what I want. However, I am looking for advice to improve this function that looks wobbly:

def balanced_text_size_button(length, width, height):
    # sqrt(length) / length -> The higher the length is, the smaller this value become,
    # it decrease slower and slower as length increase (never reach 0).
    # pow(....., 1.6) -> The resulting value is multiplied,
    # the impact is more important on high value
    # (I don't think it makes much sense I found this empirically).
    length_factor = math.pow((math.sqrt(length) / length), 1.6)

    # If there is a lot of horizontal space for the text, the font will be big,
    # but if there is a lot of char to write it will be smaller.
    font_size = width * length_factor

    # This way the font is not too high
    if font_size > height:
        font_size = height * 0.9

    return int(font_size)

A preview of what it looks like: enter image description here

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Firstly I'd simplify your maths:

$$ \text{font_size} = \text{width} * (\frac{\sqrt{\text{length}}}{\text{length}}) ^ {1.6}\\ (\frac{\sqrt{x}}{x})^{1.6} = \frac{1}{x^{0.8}}\\ \text{font_size} = \frac{\text{width}}{\text{length}^{0.8}} $$

After this we're left with your if, personally I think using min would be better here, min(font_size, height * 0.9). That way we know the maximum is \$\text{height} * 0.9\$ rather than \$\text{height}\$, and makes the font_size not jump. This is up to you, but a jump in the font size is ugly in my opinion.

If we look at the affect of not using min and using min:

>>> balanced_text_size_button(1, 89, 100)
89
>>> balanced_text_size_button(1, 91, 100)
91
>>> balanced_text_size_button(1, 100, 100)
100
>>> balanced_text_size_button(1, 101, 100)
90
>>> balanced_text_size_button_min(1, 89, 100)
89
>>> balanced_text_size_button_min(1, 91, 100)
90
>>> balanced_text_size_button_min(1, 100, 100)
90
>>> balanced_text_size_button_min(1, 101, 100)
90

The above makes much more sense. And so I'd change your function to:

def balanced_text_size_button(length, width, height):
    return int(min(width / math.pow(length, 0.8), height * 0.9))

And if you want to keep your description I'd use comments. I wouldn't use a doc-string as the implementation details aren't needed by the end user. I'd either use a heavy comment like you did:

def balanced_text_size_button(length, width, height):
    # Decide the font size by getting a desired with and height font size.
    # For text to not go out of the box, the smallest of the two wanted sizes is taken.
    # The width size is found by dividing the width by an exponential of the text.
    # An exponent is used to have a reduced affect on larger values.
    # The exponent 0.8 seems to give the best results.
    # The height is decided by picking a font size of 90% of it.
    return int(min(width / math.pow(length, 0.8), height * 0.9))

Or as the code is easy to understand, you can just say why you're using a magic variable.

def balanced_text_size_button(length, width, height):
    # The exponent 0.8 seems to give the best results.
    return int(min(width / math.pow(length, 0.8), height * 0.9))
\$\endgroup\$
2
\$\begingroup\$

The function seems good and has helpful comments. One improvement I would suggest is to add a docstring which explains what the three arguments are and what the function returns (so that people can tell what it does without having to see the example picture).

I assume that length is the width of the text in pixels, this could be renamed to text_length to make it clear it isn't referring to the button itself.

Also, if the button has no text then length will be zero and so the first line will throw a ZeroDivisionError, so you might want to catch that and return some default value.

try:
    length_factor = math.pow((math.sqrt(length) / length), 1.6)
except ZeroDivisionError:
   length_factor = 1
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.