Text rendering restructured

This commit is contained in:
Balthasar Reuter
2015-06-28 17:32:32 +02:00
parent 0fabadd9a5
commit 5f22af56ec

200
gui.py
View File

@@ -1,11 +1,6 @@
#!/usr/bin/env python
# Created by br@re-web.eu, 2015
# TODO:
# - base everything on surfaces to allow stacking
# - incorporate render_textrect
# - restructure mainloop
from __future__ import division
import pygame
@@ -17,108 +12,8 @@ except ImportError:
from events import Event
class TextRectException:
def __init__(self, message = None):
self.message = message
def __str__(self):
return self.message
def render_textrect(string, font, rect, text_color, background_color, justification=0, valign=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.
Source: http://www.pygame.org/pcr/text_rect/index.php
Takes the following arguments:
string - the text you wish to render. \n begins a new line.
font - a Font object
rect - a rectstyle giving the size of the surface requested.
text_color - a three-byte tuple of the rgb value of the
text color. ex (0, 0, 0) = BLACK
background_color - a three-byte tuple of the rgb value of the surface.
justification - 0 (default) left-justified
1 horizontally centered
2 right-justified
valign - 0 (default) top aligned
1 vertically centered
2 bottom aligned
Returns the following values:
Success - a surface object with the text rendered onto it.
Failure - raises a TextRectException if the text won't fit onto the surface.
"""
final_lines = []
requested_lines = string.splitlines()
# Create a series of lines that will fit on the provided
# rectangle.
accumulated_height = 0
for requested_line in requested_lines:
if font.size(requested_line)[0] > rect.width:
words = requested_line.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
accumulated_line = ""
for word in words:
test_line = accumulated_line + word + " "
# Build the line while the words fit.
if font.size(test_line)[0] < rect.width:
accumulated_line = test_line
else:
accumulated_height += font.size(test_line)[1]
final_lines.append(accumulated_line)
accumulated_line = word + " "
accumulated_height += font.size(accumulated_line)[1]
final_lines.append(accumulated_line)
else:
accumulated_height += font.size(requested_line)[1]
final_lines.append(requested_line)
# Check height of the text and align vertically
if accumulated_height >= rect.height:
raise TextRectException, "Once word-wrapped, the text string was too tall to fit in the rect."
if valign == 0:
voffset = 0
elif valign == 1:
voffset = int((rect.height - accumulated_height) / 2)
elif valign == 2:
voffset = rect.height - accumulated_height
else:
raise TextRectException, "Invalid valign argument: " + str(valign)
# Let's try to write the text out on the surface.
surface = pygame.Surface(rect.size)
surface.fill(background_color)
accumulated_height = 0
for line in final_lines:
if line != "":
tempsurface = font.render(line, 1, text_color)
if justification == 0:
surface.blit(tempsurface, (0, voffset + accumulated_height))
elif justification == 1:
surface.blit(tempsurface, ((rect.width - tempsurface.get_width()) / 2, voffset + accumulated_height))
elif justification == 2:
surface.blit(tempsurface, (rect.width - tempsurface.get_width(), voffset + accumulated_height))
else:
raise TextRectException, "Invalid justification argument: " + str(justification)
accumulated_height += font.size(line)[1]
return surface
class GuiException(Exception):
"""Custom exception class to handle GUI class errors"""
class GUI_PyGame:
"""A GUI class using PyGame"""
@@ -166,7 +61,7 @@ class GUI_PyGame:
# Load image from file
image = pygame.image.load(filename)
except pygame.error as e:
print("ERROR: Can't open image '" + filename + "': " + e.message)
raise GuiException("ERROR: Can't open image '" + filename + "': " + e.message)
# Extract image size and determine scaling
image_size = image.get_rect().size
image_scale = min([min(a,b)/b for a,b in zip(size, image_size)])
@@ -184,13 +79,90 @@ class GUI_PyGame:
def show_message(self, msg, color=(245,245,245), bg=(0,0,0), transparency=True):
# Choose font
font = pygame.font.Font(None, 144)
# Create rectangle for text
rect = pygame.Rect((0, 0, self.size[0], self.size[1]))
# Render text
text = render_textrect(msg, font, rect, color, bg, 1, 1)
# Wrap and render text
wrapped_text, text_height = self.wrap_text(msg, font, self.size)
rendered_text = self.render_text(wrapped_text, text_height, 1, 1, font, color, bg, transparency)
self.surface_list.append((rendered_text, (0,0)))
def wrap_text(self, msg, font, size):
final_lines = [] # resulting wrapped text
requested_lines = msg.splitlines() # wrap input along line breaks
accumulated_height = 0 # accumulated height
# Form a series of lines
for requested_line in requested_lines:
# Handle too long lines
if font.size(requested_line)[0] > size[0]:
# Split at white spaces
words = requested_line.split(' ')
# if any of our words are too long to fit, return.
for word in words:
if font.size(word)[0] >= size[0]:
raise GuiException("The word " + word + " is too long to fit.")
# Start a new line
accumulated_line = ""
# Put words on the line as long as they fit
for word in words:
test_line = accumulated_line + word + " "
# Build the line while the words fit.
if font.size(test_line)[0] < size[0]:
accumulated_line = test_line
else:
accumulated_height += font.size(test_line)[1]
final_lines.append(accumulated_line)
accumulated_line = word + " "
# Finish requested_line
accumulated_height += font.size(accumulated_line)[1]
final_lines.append(accumulated_line)
# Line fits as it is
else:
accumulated_height += font.size(requested_line)[1]
final_lines.append(requested_line)
# Check height of wrapped text
if accumulated_height >= size[1]:
raise GuiException("Wrapped text is too high to fit.")
return final_lines, accumulated_height
def render_text(self, text, text_height, valign, halign, font, color, bg, transparency):
# Determine vertical position
if valign == 0: # top aligned
voffset = 0
elif valign == 1: # centered
voffset = int((self.size[1] - text_height) / 2)
elif valign == 2: # bottom aligned
voffset = self.size[1] - text_height
else:
raise GuiException("Invalid valign argument: " + str(valign))
# Create Surface object and fill it with the given background
surface = pygame.Surface(self.size)
surface.fill(bg)
# Blit one line after another
accumulated_height = 0
for line in text:
if line != "":
tempsurface = font.render(line, 1, color)
if halign == 0: # left aligned
hoffset = 0
elif halign == 1: # centered
hoffset = (self.size[0] - tempsurface.get_width()) / 2
elif halign == 2: # right aligned
hoffset = rect.width - tempsurface.get_width()
else:
raise GuiException("Invalid halign argument: " + str(justification))
surface.blit(tempsurface, (hoffset, voffset + accumulated_height))
accumulated_height += font.size(line)[1]
# Make background color transparent
if transparency:
text.set_colorkey(bg)
self.surface_list.append((text, rect.topleft))
surface.set_colorkey(bg)
# Return the rendered surface
return surface
def convert_event(self, event):
if event.type == pygame.QUIT: