2019-11-10 08:51:02 -05:00
|
|
|
package d2ui
|
2019-10-25 19:37:04 -04:00
|
|
|
|
|
|
|
import (
|
2020-02-01 21:06:22 -05:00
|
|
|
"encoding/binary"
|
2019-10-25 22:20:36 -04:00
|
|
|
"image/color"
|
2019-10-26 23:59:27 -04:00
|
|
|
"strings"
|
2020-02-01 21:06:22 -05:00
|
|
|
"unicode"
|
2019-10-25 22:20:36 -04:00
|
|
|
|
2020-02-01 21:06:22 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
2020-02-01 18:55:56 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
2020-02-01 20:39:28 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
|
2019-10-25 19:37:04 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
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 {
|
2020-02-01 18:55:56 -05:00
|
|
|
fontSprite *Sprite
|
2019-11-21 21:40:12 -05:00
|
|
|
fontTable map[uint16]uint16
|
|
|
|
metrics map[uint16]FontSize
|
2019-10-25 19:37:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetFont creates or loads an existing font
|
2019-12-21 20:53:18 -05:00
|
|
|
func GetFont(fontPath string, palettePath string) *Font {
|
|
|
|
cacheItem, exists := fontCache[fontPath+"_"+palettePath]
|
2019-10-25 19:37:04 -04:00
|
|
|
if exists {
|
|
|
|
return cacheItem
|
|
|
|
}
|
2019-12-21 20:53:18 -05:00
|
|
|
newFont := CreateFont(fontPath, palettePath)
|
|
|
|
fontCache[fontPath+"_"+palettePath] = newFont
|
2019-10-25 19:37:04 -04:00
|
|
|
return newFont
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateFont creates an instance of a MPQ Font
|
2019-12-21 20:53:18 -05:00
|
|
|
func CreateFont(font string, palettePath string) *Font {
|
2019-10-25 19:37:04 -04:00
|
|
|
result := &Font{
|
2019-11-21 21:40:12 -05:00
|
|
|
fontTable: make(map[uint16]uint16),
|
|
|
|
metrics: make(map[uint16]FontSize),
|
2019-10-25 19:37:04 -04:00
|
|
|
}
|
2019-11-21 21:40:12 -05:00
|
|
|
// bug: performance issue when using CJK fonts, because ten thousand frames will be rendered PER font
|
2020-02-01 18:55:56 -05:00
|
|
|
animation, _ := d2asset.LoadAnimation(font+".dc6", palettePath)
|
|
|
|
result.fontSprite, _ = LoadSprite(animation)
|
2019-10-25 19:37:04 -04:00
|
|
|
woo := "Woo!\x01"
|
2020-02-01 18:55:56 -05:00
|
|
|
fontData, err := d2asset.LoadFile(font + ".tbl")
|
2019-12-24 01:48:45 -05:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2019-10-25 19:37:04 -04:00
|
|
|
if string(fontData[0:5]) != woo {
|
|
|
|
panic("No woo :(")
|
|
|
|
}
|
2019-11-21 21:40:12 -05:00
|
|
|
|
|
|
|
containsCjk := false
|
2019-10-25 19:37:04 -04:00
|
|
|
for i := 12; i < len(fontData); i += 14 {
|
2019-11-21 21:40:12 -05:00
|
|
|
// 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
|
2019-10-25 19:37:04 -04:00
|
|
|
}
|
|
|
|
}
|
2019-11-21 21:40:12 -05:00
|
|
|
|
2019-10-25 19:37:04 -04:00
|
|
|
return result
|
|
|
|
}
|
2019-10-25 22:20:36 -04:00
|
|
|
|
|
|
|
// GetTextMetrics returns the size of the specified text
|
2019-12-21 20:53:18 -05:00
|
|
|
func (v *Font) GetTextMetrics(text string) (width, height int) {
|
2020-02-01 21:51:49 -05:00
|
|
|
width = 0
|
|
|
|
curWidth := 0
|
|
|
|
height = 0
|
2019-12-21 20:53:18 -05:00
|
|
|
_, maxCharHeight := v.fontSprite.GetFrameBounds()
|
2019-11-21 21:40:12 -05:00
|
|
|
for _, ch := range text {
|
2019-10-26 23:59:27 -04:00
|
|
|
if ch == '\n' {
|
2020-02-01 21:06:22 -05:00
|
|
|
width = d2common.MaxInt(width, curWidth)
|
2019-10-26 23:59:27 -04:00
|
|
|
curWidth = 0
|
|
|
|
height += maxCharHeight + 6
|
|
|
|
continue
|
|
|
|
}
|
2019-11-21 21:40:12 -05:00
|
|
|
|
|
|
|
curWidth += v.getCharWidth(ch)
|
2019-10-25 22:20:36 -04:00
|
|
|
}
|
2020-02-01 21:06:22 -05:00
|
|
|
width = d2common.MaxInt(width, curWidth)
|
2019-10-26 23:59:27 -04:00
|
|
|
height += maxCharHeight
|
2019-10-25 22:20:36 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-21 20:53:18 -05:00
|
|
|
// Render draws the font on the target surface
|
2020-02-01 20:39:28 -05:00
|
|
|
func (v *Font) Render(x, y int, text string, color color.Color, target d2render.Surface) {
|
2019-12-21 20:53:18 -05:00
|
|
|
v.fontSprite.SetColorMod(color)
|
|
|
|
v.fontSprite.SetBlend(false)
|
2019-10-26 23:59:27 -04:00
|
|
|
|
|
|
|
maxCharHeight := uint32(0)
|
|
|
|
for _, m := range v.metrics {
|
2020-02-01 21:06:22 -05:00
|
|
|
maxCharHeight = d2common.Max(maxCharHeight, uint32(m.Height))
|
2019-10-26 23:59:27 -04:00
|
|
|
}
|
|
|
|
|
2019-12-28 16:46:08 -05:00
|
|
|
targetWidth, _ := target.GetSize()
|
2019-10-26 23:59:27 -04:00
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
for lineIdx, line := range lines {
|
|
|
|
lineWidth, _ := v.GetTextMetrics(line)
|
2020-02-01 21:51:49 -05:00
|
|
|
xPos := x + ((targetWidth / 2) - lineWidth/2)
|
2019-10-26 23:59:27 -04:00
|
|
|
|
|
|
|
for _, ch := range line {
|
2019-11-21 21:40:12 -05:00
|
|
|
width := v.getCharWidth(ch)
|
|
|
|
index := v.fontTable[uint16(ch)]
|
2019-12-21 20:53:18 -05:00
|
|
|
v.fontSprite.SetCurrentFrame(int(index))
|
|
|
|
_, height := v.fontSprite.GetCurrentFrameSize()
|
2020-02-01 21:51:49 -05:00
|
|
|
v.fontSprite.SetPosition(xPos, y+height)
|
2019-12-21 20:53:18 -05:00
|
|
|
v.fontSprite.Render(target)
|
2020-02-01 21:51:49 -05:00
|
|
|
xPos += width
|
2019-10-26 23:59:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if lineIdx >= len(lines)-1 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
xPos = x
|
|
|
|
y += int(maxCharHeight + 6)
|
2019-10-25 22:20:36 -04:00
|
|
|
}
|
|
|
|
}
|
2019-11-21 21:40:12 -05:00
|
|
|
|
2019-12-21 20:53:18 -05:00
|
|
|
func (v *Font) getCharWidth(char rune) (width int) {
|
2019-11-21 21:40:12 -05:00
|
|
|
if char < unicode.MaxLatin1 {
|
2019-12-21 20:53:18 -05:00
|
|
|
return int(v.metrics[uint16(char)].Width)
|
2019-11-21 21:40:12 -05:00
|
|
|
}
|
2019-12-21 20:53:18 -05:00
|
|
|
return int(v.metrics[unicode.MaxLatin1].Width)
|
2019-11-21 21:40:12 -05:00
|
|
|
}
|