1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-07 00:56:51 -05:00

Fix CJK render problem partially (#222)

* Fixed CJK render problem
* Add partial support of loading CJK fonts
This commit is contained in:
負弌 2019-11-22 10:40:12 +08:00 committed by Tim Sarbin
parent 23e228d88b
commit 30c3bb7330
2 changed files with 63 additions and 26 deletions

View File

@ -40,19 +40,19 @@ import (
// Engine is the core OpenDiablo2 engine // Engine is the core OpenDiablo2 engine
type Engine struct { type Engine struct {
Settings *d2corecommon.Configuration // Engine configuration settings from json file Settings *d2corecommon.Configuration // Engine configuration settings from json file
Files map[string]string // Map that defines which files are in which MPQs Files map[string]string // Map that defines which files are in which MPQs
CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that. CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that.
LoadingSprite d2render.Sprite // The sprite shown when loading stuff LoadingSprite d2render.Sprite // The sprite shown when loading stuff
loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays. loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays.
loadingIndex int // Determines which load function is currently being called loadingIndex int // Determines which load function is currently being called
thingsToLoad []func() // The load functions for the next scene thingsToLoad []func() // The load functions for the next scene
stepLoadingSize float64 // The size for each loading step stepLoadingSize float64 // The size for each loading step
CurrentScene d2coreinterface.Scene // The current scene being rendered CurrentScene d2coreinterface.Scene // The current scene being rendered
UIManager *d2ui.Manager // The UI manager UIManager *d2ui.Manager // The UI manager
SoundManager *d2audio.Manager // The sound manager SoundManager *d2audio.Manager // The sound manager
nextScene d2coreinterface.Scene // The next scene to be loaded at the end of the game loop nextScene d2coreinterface.Scene // The next scene to be loaded at the end of the game loop
fullscreenKey bool // When true, the fullscreen toggle is still being pressed fullscreenKey bool // When true, the fullscreen toggle is still being pressed
lastTime float64 // Last time we updated the scene lastTime float64 // Last time we updated the scene
showFPS bool showFPS bool
} }
@ -104,11 +104,18 @@ var mutex sync.Mutex
func (v *Engine) LoadFile(fileName string) []byte { func (v *Engine) LoadFile(fileName string) []byte {
fileName = strings.ReplaceAll(fileName, "{LANG}", d2resource.LanguageCode) fileName = strings.ReplaceAll(fileName, "{LANG}", d2resource.LanguageCode)
// todo: separate CJK and latin characters from LanguageCode
if "CHI" == strings.ToUpper(d2resource.LanguageCode) {
fileName = strings.ReplaceAll(fileName, "{LANG_FONT}", d2resource.LanguageCode)
} else {
fileName = strings.ReplaceAll(fileName, "{LANG_FONT}", "latin")
}
fileName = strings.ToLower(fileName) fileName = strings.ToLower(fileName)
fileName = strings.ReplaceAll(fileName, `/`, "\\") fileName = strings.ReplaceAll(fileName, `/`, "\\")
if fileName[0] == '\\' { if fileName[0] == '\\' {
fileName = fileName[1:] fileName = fileName[1:]
} }
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
// TODO: May want to cache some things if performance becomes an issue // TODO: May want to cache some things if performance becomes an issue

View File

@ -15,6 +15,10 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2render" "github.com/OpenDiablo2/OpenDiablo2/d2render"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten"
"encoding/binary"
"unicode"
) )
var fontCache = map[string]*Font{} var fontCache = map[string]*Font{}
@ -28,7 +32,8 @@ type FontSize struct {
// Font represents a font // Font represents a font
type Font struct { type Font struct {
fontSprite d2render.Sprite fontSprite d2render.Sprite
metrics map[uint8]FontSize fontTable map[uint16]uint16
metrics map[uint16]FontSize
} }
// GetFont creates or loads an existing font // GetFont creates or loads an existing font
@ -45,21 +50,39 @@ func GetFont(font string, palette d2enum.PaletteType, fileProvider d2interface.F
// CreateFont creates an instance of a MPQ Font // CreateFont creates an instance of a MPQ Font
func CreateFont(font string, palette d2enum.PaletteType, fileProvider d2interface.FileProvider) *Font { func CreateFont(font string, palette d2enum.PaletteType, fileProvider d2interface.FileProvider) *Font {
result := &Font{ result := &Font{
metrics: make(map[uint8]FontSize), 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.CreateSprite(fileProvider.LoadFile(font+".dc6"), d2datadict.Palettes[palette]) result.fontSprite = d2render.CreateSprite(fileProvider.LoadFile(font+".dc6"), d2datadict.Palettes[palette])
woo := "Woo!\x01" woo := "Woo!\x01"
fontData := fileProvider.LoadFile(font + ".tbl") fontData := fileProvider.LoadFile(font + ".tbl")
if string(fontData[0:5]) != woo { if string(fontData[0:5]) != woo {
panic("No woo :(") panic("No woo :(")
} }
containsCjk := false
for i := 12; i < len(fontData); i += 14 { for i := 12; i < len(fontData); i += 14 {
fontSize := FontSize{ // font mappings, map unicode code points to array indics
Width: fontData[i+3], unicodeCode := binary.LittleEndian.Uint16(fontData[i : i+2])
Height: fontData[i+4], 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
} }
result.metrics[fontData[i+8]] = fontSize
} }
return result return result
} }
@ -69,19 +92,19 @@ func (v *Font) GetTextMetrics(text string) (width, height uint32) {
curWidth := uint32(0) curWidth := uint32(0)
height = uint32(0) height = uint32(0)
maxCharHeight := uint32(0) maxCharHeight := uint32(0)
// todo: it can be saved as a struct member, since it only depends on `.Frames`
for _, m := range v.fontSprite.Frames { for _, m := range v.fontSprite.Frames {
maxCharHeight = d2helper.Max(maxCharHeight, uint32(m.Height)) maxCharHeight = d2helper.Max(maxCharHeight, uint32(m.Height))
} }
for i := 0; i < len(text); i++ { for _, ch := range text {
ch := text[i]
if ch == '\n' { if ch == '\n' {
width = d2helper.Max(width, curWidth) width = d2helper.Max(width, curWidth)
curWidth = 0 curWidth = 0
height += maxCharHeight + 6 height += maxCharHeight + 6
continue continue
} }
metric := v.metrics[uint8(ch)]
curWidth += uint32(metric.Width) curWidth += v.getCharWidth(ch)
} }
width = d2helper.Max(width, curWidth) width = d2helper.Max(width, curWidth)
height += maxCharHeight height += maxCharHeight
@ -105,12 +128,12 @@ func (v *Font) Draw(x, y int, text string, color color.Color, target *ebiten.Ima
xPos := x + ((targetWidth / 2) - int(lineWidth/2)) xPos := x + ((targetWidth / 2) - int(lineWidth/2))
for _, ch := range line { for _, ch := range line {
char := uint8(ch) width := v.getCharWidth(ch)
metric := v.metrics[char] index := v.fontTable[uint16(ch)]
v.fontSprite.Frame = int16(char) v.fontSprite.Frame = int16(index)
v.fontSprite.MoveTo(xPos, y+int(v.fontSprite.Frames[char].Height)) v.fontSprite.MoveTo(xPos, y+int(v.fontSprite.Frames[index].Height))
v.fontSprite.Draw(target) v.fontSprite.Draw(target)
xPos += int(metric.Width) xPos += int(width)
} }
if lineIdx >= len(lines)-1 { if lineIdx >= len(lines)-1 {
@ -121,3 +144,10 @@ func (v *Font) Draw(x, y int, text string, color color.Color, target *ebiten.Ima
y += int(maxCharHeight + 6) y += int(maxCharHeight + 6)
} }
} }
func (v *Font) getCharWidth(char rune) (width uint32) {
if char < unicode.MaxLatin1 {
return uint32(v.metrics[uint16(char)].Width)
}
return uint32(v.metrics[unicode.MaxLatin1].Width)
}