treasurehunting2/PySDL2-0.9.5/sdl2/ext/font.py
2017-05-13 11:00:53 +02:00

358 lines
12 KiB
Python

"""Font and text rendering routines."""
import os
from .. import surface, rect, pixels
from .common import SDLError
from .compat import *
from .sprite import SoftwareSprite
from .color import Color, convert_to_color
_HASSDLTTF = True
try:
from .. import sdlttf
except ImportError:
_HASSDLTTF = False
__all__ = ["BitmapFont", "FontManager"]
class BitmapFont(object):
"""A bitmap graphics to character mapping.
The BitmapFont class uses an image surface to find and render font
character glyphs for text. It requires a mapping table, which
denotes the characters available on the image.
The mapping table is a list of strings, where each string reflects a
'line' of characters on the image. Each character within each line
has the same size as specified by the size argument.
A typical mapping table might look like
[ '0123456789',
'ABCDEFGHIJ',
'KLMNOPQRST',
'UVWXYZ ',
'abcdefghij',
'klmnopqrst',
'uvwxyz ',
',;.:!?+-()' ]
"""
DEFAULTMAP = ["0123456789",
"ABCDEFGHIJ",
"KLMNOPQRST",
"UVWXYZ ",
"abcdefghij",
"klmnopqrst",
"uvwxyz ",
",;.:!?+-()"
]
def __init__(self, imgsurface, size, mapping=None):
"""Creates a new BitmapFont instance from the passed image.
Each character is expected to be of the same size (a 2-value tuple
denoting the width and height) and to be in order of the passed
mapping.
"""
if mapping is None:
self.mapping = list(BitmapFont.DEFAULTMAP)
else:
self.mapping = mapping
self.offsets = {}
if isinstance(imgsurface, SoftwareSprite):
self.surface = imgsurface.surface
# elif isinstance(surface, sprite.Sprite):
# TODO
elif isinstance(imgsurface, surface.SDL_Surface):
self.surface = imgsurface
self.size = size[0], size[1]
self._calculate_offsets()
def _calculate_offsets(self):
"""Calculates the internal character offsets for each line."""
self.offsets = {}
offsets = self.offsets
x, y = 0, 0
w, h = self.size
for line in self.mapping:
x = 0
for c in line:
offsets[c] = rect.SDL_Rect(x, y, w, h)
x += w
y += h
def render(self, text, bpp=None):
"""Renders the passed text on a new Sprite and returns it."""
tw, th = 0, 0
w, h = self.size
# TODO
lines = text.split(os.linesep)
for line in lines:
tw = max(tw, sum([w for c in line]))
th += h
if bpp is None:
bpp = self.surface.format.contents.BitsPerPixel
sf = surface.SDL_CreateRGBSurface(0, tw, th, bpp, 0, 0, 0, 0)
sf = sf.contents
imgsurface = SoftwareSprite(sf, False)
target = imgsurface.surface
blit_surface = surface.SDL_BlitSurface
fontsf = self.surface
offsets = self.offsets
dstr = rect.SDL_Rect(0, 0, 0, 0)
y = 0
for line in lines:
dstr.y = y
x = 0
for c in line:
dstr.x = x
if c in offsets:
blit_surface(fontsf, offsets[c], target, dstr)
# elif c != ' ':
# TODO: raise an exception for unknown char?
x += w
y += h
return imgsurface
def render_on(self, imgsurface, text, offset=(0, 0)):
"""Renders a text on the passed sprite, starting at a specific
offset.
The top-left start position of the text will be the passed offset and
4-value tuple with the changed area will be returned.
"""
w, h = self.size
target = None
if isinstance(imgsurface, SoftwareSprite):
target = imgsurface.surface
# elif isinstance(surface, sprite.Sprite):
# TODO
elif isinstance(imgsurface, surface.SDL_Surface):
target = imgsurface
else:
raise TypeError("unsupported surface type")
lines = text.split(os.linesep)
blit_surface = surface.SDL_BlitSurface
fontsf = self.surface
offsets = self.offsets
dstr = rect.SDL_Rect(0, 0, 0, 0)
y = offset[1]
for line in lines:
dstr.y = y
x = offset[0]
for c in line:
dstr.x = x
if c in offsets:
blit_surface(fontsf, offsets[c], target, dstr)
# elif c != ' ':
# TODO: raise an exception for unknown char?
x += w
y += h
return (offset[0], offset[1], x + w, y + h)
def contains(self, c):
"""Checks, whether a certain character exists in the font."""
return c == ' ' or c in self.offsets
def can_render(self, text):
"""Checks, whether all characters in the passed text can be rendered.
"""
lines = text.split(os.linesep)
for line in lines:
for c in line:
if c != ' ' and c not in self.offsets:
return False
return True
class FontManager(object):
"""Manage fonts and rendering of text."""
def __init__(self, font_path, alias=None, size=16,
color=Color(255, 255, 255), bg_color=Color(0, 0, 0), index=0):
"""Initialize the FontManager
One font path must be given to initialize the FontManager. The
default_font will be set to this font. color and bg_color
will give the FontManager a default color. size is the default
font size in pixels.
"""
if not _HASSDLTTF:
raise UnsupportedError(FontManager,
"FontManager requires sdlttf support")
if sdlttf.TTF_WasInit() == 0 and sdlttf.TTF_Init() != 0:
raise SDLError()
self.fonts = {} # fonts = {alias: {size:font_ptr}}
self.aliases = {} # aliases = {alias:font_path}
self._textcolor = pixels.SDL_Color(0, 0, 0)
self._bgcolor = pixels.SDL_Color(255, 255, 255)
self.color = color
self.bg_color = bg_color
self.size = size
self._default_font = self.add(font_path, alias, index)
def __del__(self):
"""Close all opened fonts."""
self.close()
def close(self):
"""Close all opened fonts."""
for alias, fonts in self.fonts.items():
for size, font in fonts.items():
if font:
sdlttf.TTF_CloseFont(font)
self.fonts = {}
self.aliases = {}
def add(self, font_path, alias=None, size=None, index=0):
"""Add a font to the Font Manager.
alias is by default the font name. But another name can be
passed. Returns the font pointer stored in self.fonts.
"""
size = size or self.size
if alias is None:
# If no alias given, take the font name as alias
basename = os.path.basename(font_path)
alias = os.path.splitext(basename)[0]
if alias in self.fonts:
if size in self.fonts[alias] and self.fonts[alias]:
# font with selected size already opened
return
else:
self._change_font_size(alias, size)
return
else:
if not os.path.isfile(font_path):
raise IOError("Cannot find %s" % font_path)
font = self._load_font(font_path, size, index)
self.aliases[alias] = font_path
self.fonts[alias] = {}
self.fonts[alias][size] = font
return font
def _load_font(self, font_path, size, index=0):
"""Helper function to open the font.
Raises an exception if something went wrong.
"""
if index == 0:
font = sdlttf.TTF_OpenFont(byteify(font_path, "utf-8"), size)
else:
font = sdlttf.TTF_OpenFontIndex(byteify(font_path, "utf-8"), size,
index)
if font is None:
raise SDLError()
return font
def _change_font_size(self, alias, size):
"""Loads an already opened font in another size."""
if alias not in self.fonts:
raise KeyError("Font %s not loaded in FontManager" % alias)
font = self._load_font(self.aliases[alias], size)
self.fonts[alias][size] = font
@property
def color(self):
"""The text color to be used."""
return Color(self._textcolor.r, self._textcolor.g, self._textcolor.b,
self._textcolor.a)
@color.setter
def color(self, value):
"""The text color to be used."""
c = convert_to_color(value)
self._textcolor = pixels.SDL_Color(c.r, c.g, c.b, c.a)
@property
def bg_color(self):
"""The background color to be used."""
return Color(self._bgcolor.r, self._bgcolor.g, self._bgcolor.b,
self._bgcolor.a)
@bg_color.setter
def bg_color(self, value):
"""The background color to be used."""
c = convert_to_color(value)
self._bgcolor = pixels.SDL_Color(c.r, c.g, c.b, c.a)
@property
def default_font(self):
"""Returns the name of the current default_font."""
for alias in self.fonts:
for size, font in self.fonts[alias].items():
if font == self._default_font:
return alias
@default_font.setter
def default_font(self, value):
"""value must be a font alias
Set the default_font to the given font name alias,
provided it's loaded in the font manager.
"""
alias = value
size = self.size
if alias not in self.fonts:
raise ValueError("Font %s not loaded in FontManager" % alias)
# Check if size is already loaded, otherwise do it.
if size not in self.fonts[alias]:
self._change_font_size(alias, size)
size = list(self.fonts[alias].keys())[0]
self._default_font = self.fonts[alias][size]
def render(self, text, alias=None, size=None, width=None, color=None,
bg_color=None, **kwargs):
"""Renders text to a surface.
This method uses the font designated by the alias or the
default_font. A size can be passed even if the font was not
loaded with this size. A width can be given for line wrapping.
If no bg_color or color are given, it will default to the
FontManager's bg_color and color.
"""
alias = alias or self.default_font
size = size or self.size
if bg_color is None:
bg_color = self._bgcolor
elif not isinstance(bg_color, pixels.SDL_Color):
c = convert_to_color(bg_color)
bg_color = pixels.SDL_Color(c.r, c.g, c.b, c.a)
if color is None:
color = self._textcolor
elif not isinstance(color, pixels.SDL_Color):
c = convert_to_color(color)
color = pixels.SDL_Color(c.r, c.g, c.b, c.a)
if len(self.fonts) == 0:
raise TypeError("There are no fonts selected.")
font = self._default_font
if alias not in self.aliases:
raise KeyError("Font %s not loaded" % font)
elif size not in self.fonts[alias]:
self._change_font_size(alias, size)
font = self.fonts[alias][size]
text = byteify(text, "utf-8")
if width:
sf = sdlttf.TTF_RenderUTF8_Blended_Wrapped(font, text, color,
width)
elif bg_color == pixels.SDL_Color(0, 0, 0):
sf = sdlttf.TTF_RenderUTF8_Blended(font, text, color)
else:
sf = sdlttf.TTF_RenderUTF8_Shaded(font, text, color,
bg_color)
if not sf:
raise SDLError(sdlttf.TTF_GetError())
return sf.contents