1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-06 18:27:20 -05:00
OpenDiablo2/d2render/d2ui/font.go
Alex Yatskov 1b03e691b9 High level resource caching; resource cleanup (#264)
* Work on resource loading

* Use new material flag name. (#261)

Update ebiten ref while at it

* Hopefully fix CI (#262)

* Don't try to copy config.json on travis (#263)

I doesn't exist anymore

* Update D2Shared references

* Fix character selection rect

Co-authored-by: Ziemas <ziemas@ziemas.se>
2019-12-21 20:53:18 -05:00

146 lines
3.5 KiB
Go

package d2ui
import (
"image/color"
"strings"
"github.com/OpenDiablo2/D2Shared/d2helper"
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/hajimehoshi/ebiten"
"encoding/binary"
"unicode"
)
var fontCache = map[string]*Font{}
// FontSize represents the size of a character in a font
type FontSize struct {
Width uint8
Height uint8
}
// Font represents a font
type Font struct {
fontSprite *d2render.Sprite
fontTable map[uint16]uint16
metrics map[uint16]FontSize
}
// GetFont creates or loads an existing font
func GetFont(fontPath string, palettePath string) *Font {
cacheItem, exists := fontCache[fontPath+"_"+palettePath]
if exists {
return cacheItem
}
newFont := CreateFont(fontPath, palettePath)
fontCache[fontPath+"_"+palettePath] = newFont
return newFont
}
// CreateFont creates an instance of a MPQ Font
func CreateFont(font string, palettePath string) *Font {
result := &Font{
fontTable: make(map[uint16]uint16),
metrics: make(map[uint16]FontSize),
}
// bug: performance issue when using CJK fonts, because ten thousand frames will be rendered PER font
result.fontSprite, _ = d2render.LoadSprite(font+".dc6", palettePath)
woo := "Woo!\x01"
fontData := d2asset.MustLoadFile(font + ".tbl")
if string(fontData[0:5]) != woo {
panic("No woo :(")
}
containsCjk := false
for i := 12; i < len(fontData); i += 14 {
// font mappings, map unicode code points to array indics
unicodeCode := binary.LittleEndian.Uint16(fontData[i : i+2])
fontIndex := binary.LittleEndian.Uint16(fontData[i+8 : i+10])
result.fontTable[unicodeCode] = fontIndex
if unicodeCode < unicode.MaxLatin1 {
result.metrics[unicodeCode] = FontSize{
Width: fontData[i+3],
Height: fontData[i+4],
}
} else if !containsCjk {
// CJK characters are all in the same size
result.metrics[unicode.MaxLatin1] = FontSize{
Width: fontData[i+3],
Height: fontData[i+4],
}
containsCjk = true
}
}
return result
}
// GetTextMetrics returns the size of the specified text
func (v *Font) GetTextMetrics(text string) (width, height int) {
width = int(0)
curWidth := int(0)
height = int(0)
_, maxCharHeight := v.fontSprite.GetFrameBounds()
for _, ch := range text {
if ch == '\n' {
width = d2helper.MaxInt(width, curWidth)
curWidth = 0
height += maxCharHeight + 6
continue
}
curWidth += v.getCharWidth(ch)
}
width = d2helper.MaxInt(width, curWidth)
height += maxCharHeight
return
}
// Render draws the font on the target surface
func (v *Font) Render(x, y int, text string, color color.Color, target *ebiten.Image) {
v.fontSprite.SetColorMod(color)
v.fontSprite.SetBlend(false)
maxCharHeight := uint32(0)
for _, m := range v.metrics {
maxCharHeight = d2helper.Max(maxCharHeight, uint32(m.Height))
}
targetWidth, _ := target.Size()
lines := strings.Split(text, "\n")
for lineIdx, line := range lines {
lineWidth, _ := v.GetTextMetrics(line)
xPos := x + ((targetWidth / 2) - int(lineWidth/2))
for _, ch := range line {
width := v.getCharWidth(ch)
index := v.fontTable[uint16(ch)]
v.fontSprite.SetCurrentFrame(int(index))
_, height := v.fontSprite.GetCurrentFrameSize()
v.fontSprite.SetPosition(xPos, y+int(height))
v.fontSprite.Render(target)
xPos += int(width)
}
if lineIdx >= len(lines)-1 {
break
}
xPos = x
y += int(maxCharHeight + 6)
}
}
func (v *Font) getCharWidth(char rune) (width int) {
if char < unicode.MaxLatin1 {
return int(v.metrics[uint16(char)].Width)
}
return int(v.metrics[unicode.MaxLatin1].Width)
}