Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pygame - blitting text with an escape character or newline [duplicate]

Hi I am looking to blit some text to the pygame screen. My text is to have/include a newline after the word recommened.

posX = (self.scrWidth * 1/8)
posY = (self.scrHeight * 1/8)
position = posX, posY
font = pygame.font.SysFont(self.font, self.fontSize)
if text == "INFO":
   text = """If you are learning to play, it is recommended
               you chose your own starting area."""
   label = font.render(text, True, self.fontColour)
   return label, position

The return give the surface and position for the blit.

I have tried the triple quote method to include white space, using \n. I do not know what I am doing wrong.

like image 618
Shadowhawk Avatar asked Sep 15 '15 15:09

Shadowhawk


2 Answers

pygame.font.Font/SysFont().render() does not support multiple line text.

This is stated in the documentation here.

The text can only be a single line: newline characters are not rendered.

One way you can work around this is to render a list of single line strings. One for each line, and blit them one below the other.

Example:

posX = (self.scrWidth * 1/8)
posY = (self.scrHeight * 1/8)
position = posX, posY
font = pygame.font.SysFont(self.font, self.fontSize)
if text == "INFO":
    text = ["If you are learning to play, it is recommended",
            "you chose your own starting area."]
    label = []
    for line in text: 
        label.append(font.render(text, True, self.fontColour))
    return label, position

Then to blit the images, you can use another for loop like this:

for line in range(len(label)):
    surface.blit(label(line),(position[0],position[1]+(line*fontsize)+(15*line)))

In the Y value for the blit, the value is:

position[1]+(line*fontsize)+(15*line)

what this is doing exactly is taking position[0], which is the posY variable from earlier, and using it as the top left most position of the blit.

Then (line*fontsize) is added to that. Because the for loop uses a range instead of the list items themselves, line will be 1,2,3, etc..., and thus can be added to put each consecutive line directly below the other.

Finally, (15*line) is added to that. This is how we get that margin between the spaces. The constant, in this case 15, represents how many pixels the margin is supposed to be. A higher number will give a bigger gap, and a lower number will lower the gap. The reason "line" is added to this is to compensate for moving the above line down said amount. If you wish, you may take away the "*line" to see how this will cause the lines to start overlapping.

like image 155
Flutterguy135 Avatar answered Oct 12 '22 03:10

Flutterguy135


Here is the anwser I found and used in my code. This produces a text surface that can be used normally.

class TextRectException:
    def __init__(self, message=None):
            self.message = message

    def __str__(self):
        return self.message


def multiLineSurface(string: str, font: pygame.font.Font, rect: pygame.rect.Rect, fontColour: tuple, BGColour: tuple, justification=0):
    """Returns a surface containing the passed text string, reformatted
    to fit within the given rect, word-wrapping as necessary. The text
    will be anti-aliased.

    Parameters
    ----------
    string - the text you wish to render. \n begins a new line.
    font - a Font object
    rect - a rect style giving the size of the surface requested.
    fontColour - a three-byte tuple of the rgb value of the
             text color. ex (0, 0, 0) = BLACK
    BGColour - a three-byte tuple of the rgb value of the surface.
    justification - 0 (default) left-justified
                1 horizontally centered
                2 right-justified

    Returns
    -------
    Success - a surface object with the text rendered onto it.
    Failure - raises a TextRectException if the text won't fit onto the surface.
    """

    finalLines = []
    requestedLines = string.splitlines()
    # Create a series of lines that will fit on the provided
    # rectangle.
    for requestedLine in requestedLines:
        if font.size(requestedLine)[0] > rect.width:
            words = requestedLine.split(' ')
            # if any of our words are too long to fit, return.
            for word in words:
                if font.size(word)[0] >= rect.width:
                    raise TextRectException("The word " + word + " is too long to fit in the rect passed.")
            # Start a new line
            accumulatedLine = ""
            for word in words:
                testLine = accumulatedLine + word + " "
                # Build the line while the words fit.
                if font.size(testLine)[0] < rect.width:
                    accumulatedLine = testLine
                else:
                    finalLines.append(accumulatedLine)
                    accumulatedLine = word + " "
            finalLines.append(accumulatedLine)
        else:
            finalLines.append(requestedLine)

    # Let's try to write the text out on the surface.
    surface = pygame.Surface(rect.size)
    surface.fill(BGColour)
    accumulatedHeight = 0
    for line in finalLines:
        if accumulatedHeight + font.size(line)[1] >= rect.height:
             raise TextRectException("Once word-wrapped, the text string was too tall to fit in the rect.")
        if line != "":
            tempSurface = font.render(line, 1, fontColour)
        if justification == 0:
            surface.blit(tempSurface, (0, accumulatedHeight))
        elif justification == 1:
            surface.blit(tempSurface, ((rect.width - tempSurface.get_width()) / 2, accumulatedHeight))
        elif justification == 2:
            surface.blit(tempSurface, (rect.width - tempSurface.get_width(), accumulatedHeight))
        else:
            raise TextRectException("Invalid justification argument: " + str(justification))
        accumulatedHeight += font.size(line)[1]
    return surface
like image 29
Shadowhawk Avatar answered Oct 12 '22 04:10

Shadowhawk