doodle/lib/render/sdl/text.go
Noah Petherbridge 3d08291bc5 Demo Running Level as Title Screen Wallpaper
* Load SDL2 fonts from go-bindata storage so we don't have to ship
  external font files on disk.
* Dedupe names of doodads so we don't show double on the front-end
  (go-bindata bundled doodads + those on local filesystem)
* Use go-bindata for accessing wallpaper images.
* Better flashed messages walking you through the Link Tool.
* Stylize the title screen (MainScene) by rendering a live example level
  as the background wallpaper, with mobile doodads in motion.
2019-06-27 22:59:36 -07:00

200 lines
4.2 KiB
Go

package sdl
import (
"fmt"
"strings"
"sync"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
"github.com/veandco/go-sdl2/sdl"
"github.com/veandco/go-sdl2/ttf"
)
// TODO: font filenames
var defaultFontFilename = "DejaVuSans.ttf"
// Font holds cached SDL_TTF structures for loaded fonts. They are created
// automatically when fonts are either preinstalled (InstallFont) or loaded for
// the first time as demanded by the DrawText method.
type Font struct {
Filename string
data []byte // raw binary data of font
ttf *ttf.Font
}
var (
fonts = map[string]*ttf.Font{} // keys like "DejaVuSans@14" by font size
installedFont = map[string][]byte{} // installed font files' binary handles
fontsMu sync.RWMutex
)
// InstallFont preloads the font cache using TTF binary data in memory.
func InstallFont(filename string, binary []byte) {
fontsMu.Lock()
installedFont[filename] = binary
fontsMu.Unlock()
}
// LoadFont loads and caches the font at a given size.
func LoadFont(filename string, size int) (*ttf.Font, error) {
if filename == "" {
filename = defaultFontFilename
}
// Cached font available?
keyName := fmt.Sprintf("%s@%d", filename, size)
if font, ok := fonts[keyName]; ok {
return font, nil
}
// Do we have this font in memory?
var (
font *ttf.Font
err error
)
if binary, ok := installedFont[filename]; ok {
var RWops *sdl.RWops
RWops, err = sdl.RWFromMem(binary)
if err != nil {
return nil, fmt.Errorf("LoadFont(%s): RWFromMem: %s", filename, err)
}
font, err = ttf.OpenFontRW(RWops, 0, size)
} else {
font, err = ttf.OpenFont(filename, size)
}
// Error opening the font?
if err != nil {
return nil, fmt.Errorf("LoadFont(%s): %s", filename, err)
}
// Cache this font name and size.
fonts[keyName] = font
return font, nil
}
// Keysym returns the current key pressed, taking into account the Shift
// key modifier.
func (r *Renderer) Keysym(ev *events.State) string {
if key := ev.KeyName.Read(); key != "" {
if ev.ShiftActive.Pressed() {
if symbol, ok := shiftMap[key]; ok {
return symbol
}
return strings.ToUpper(key)
}
}
return ""
}
// ComputeTextRect computes and returns a Rect for how large the text would
// appear if rendered.
func (r *Renderer) ComputeTextRect(text render.Text) (render.Rect, error) {
var (
rect render.Rect
font *ttf.Font
surface *sdl.Surface
color = ColorToSDL(text.Color)
err error
)
if font, err = LoadFont(text.FontFilename, text.Size); err != nil {
return rect, err
}
if surface, err = font.RenderUTF8Blended(text.Text, color); err != nil {
return rect, err
}
defer surface.Free()
rect.W = surface.W
rect.H = surface.H
return rect, err
}
// DrawText draws text on the canvas.
func (r *Renderer) DrawText(text render.Text, point render.Point) error {
var (
font *ttf.Font
surface *sdl.Surface
tex *sdl.Texture
err error
)
if font, err = LoadFont(text.FontFilename, text.Size); err != nil {
return err
}
write := func(dx, dy int32, color sdl.Color) {
if surface, err = font.RenderUTF8Blended(text.Text, color); err != nil {
return
}
defer surface.Free()
if tex, err = r.renderer.CreateTextureFromSurface(surface); err != nil {
return
}
defer tex.Destroy()
tmp := &sdl.Rect{
X: point.X + dx,
Y: point.Y + dy,
W: surface.W,
H: surface.H,
}
r.renderer.Copy(tex, nil, tmp)
}
// Does the text have a stroke around it?
if text.Stroke != render.Invisible {
color := ColorToSDL(text.Stroke)
write(-1, -1, color)
write(-1, 0, color)
write(-1, 1, color)
write(1, -1, color)
write(1, 0, color)
write(1, 1, color)
write(0, -1, color)
write(0, 1, color)
}
// Does it have a drop shadow?
if text.Shadow != render.Invisible {
write(1, 1, ColorToSDL(text.Shadow))
}
// Draw the text itself.
write(0, 0, ColorToSDL(text.Color))
return err
}
// shiftMap maps keys to their Shift versions.
var shiftMap = map[string]string{
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
"[": "{",
"]": "}",
`\`: "|",
";": ":",
`'`: `"`,
",": "<",
".": ">",
"/": "?",
}