From 30c3bb7330e794aeccd7784b2e30595acfb8d05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B2=A0=E5=BC=8C?= Date: Fri, 22 Nov 2019 10:40:12 +0800 Subject: [PATCH] Fix CJK render problem partially (#222) * Fixed CJK render problem * Add partial support of loading CJK fonts --- d2core/engine.go | 29 +++++++++++++-------- d2render/d2ui/font.go | 60 ++++++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/d2core/engine.go b/d2core/engine.go index 7cac368e..3d48004d 100644 --- a/d2core/engine.go +++ b/d2core/engine.go @@ -40,19 +40,19 @@ import ( // Engine is the core OpenDiablo2 engine type Engine struct { Settings *d2corecommon.Configuration // Engine configuration settings from json file - 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. - 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. - loadingIndex int // Determines which load function is currently being called - thingsToLoad []func() // The load functions for the next scene - stepLoadingSize float64 // The size for each loading step + 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. + 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. + loadingIndex int // Determines which load function is currently being called + thingsToLoad []func() // The load functions for the next scene + stepLoadingSize float64 // The size for each loading step CurrentScene d2coreinterface.Scene // The current scene being rendered - UIManager *d2ui.Manager // The UI manager - SoundManager *d2audio.Manager // The sound manager + UIManager *d2ui.Manager // The UI manager + SoundManager *d2audio.Manager // The sound manager 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 - lastTime float64 // Last time we updated the scene + fullscreenKey bool // When true, the fullscreen toggle is still being pressed + lastTime float64 // Last time we updated the scene showFPS bool } @@ -104,11 +104,18 @@ var mutex sync.Mutex func (v *Engine) LoadFile(fileName string) []byte { 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.ReplaceAll(fileName, `/`, "\\") if fileName[0] == '\\' { fileName = fileName[1:] } + mutex.Lock() defer mutex.Unlock() // TODO: May want to cache some things if performance becomes an issue diff --git a/d2render/d2ui/font.go b/d2render/d2ui/font.go index 77c9e8e0..d09ed504 100644 --- a/d2render/d2ui/font.go +++ b/d2render/d2ui/font.go @@ -15,6 +15,10 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2render" "github.com/hajimehoshi/ebiten" + + "encoding/binary" + + "unicode" ) var fontCache = map[string]*Font{} @@ -28,7 +32,8 @@ type FontSize struct { // Font represents a font type Font struct { fontSprite d2render.Sprite - metrics map[uint8]FontSize + fontTable map[uint16]uint16 + metrics map[uint16]FontSize } // 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 func CreateFont(font string, palette d2enum.PaletteType, fileProvider d2interface.FileProvider) *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]) woo := "Woo!\x01" fontData := fileProvider.LoadFile(font + ".tbl") if string(fontData[0:5]) != woo { panic("No woo :(") } + + containsCjk := false for i := 12; i < len(fontData); i += 14 { - fontSize := FontSize{ - Width: fontData[i+3], - Height: fontData[i+4], + // 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 } - result.metrics[fontData[i+8]] = fontSize } + return result } @@ -69,19 +92,19 @@ func (v *Font) GetTextMetrics(text string) (width, height uint32) { curWidth := uint32(0) height = 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 { maxCharHeight = d2helper.Max(maxCharHeight, uint32(m.Height)) } - for i := 0; i < len(text); i++ { - ch := text[i] + for _, ch := range text { if ch == '\n' { width = d2helper.Max(width, curWidth) curWidth = 0 height += maxCharHeight + 6 continue } - metric := v.metrics[uint8(ch)] - curWidth += uint32(metric.Width) + + curWidth += v.getCharWidth(ch) } width = d2helper.Max(width, curWidth) 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)) for _, ch := range line { - char := uint8(ch) - metric := v.metrics[char] - v.fontSprite.Frame = int16(char) - v.fontSprite.MoveTo(xPos, y+int(v.fontSprite.Frames[char].Height)) + width := v.getCharWidth(ch) + index := v.fontTable[uint16(ch)] + v.fontSprite.Frame = int16(index) + v.fontSprite.MoveTo(xPos, y+int(v.fontSprite.Frames[index].Height)) v.fontSprite.Draw(target) - xPos += int(metric.Width) + xPos += int(width) } 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) } } + +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) +}