2023-11-25 18:36:08 +01:00
|
|
|
// Copyright 2023 The Ebitengine Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package text
|
|
|
|
|
|
|
|
import (
|
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
|
|
|
)
|
|
|
|
|
2023-12-02 06:53:58 +01:00
|
|
|
var _ Face = (*MultiFace)(nil)
|
2023-11-25 18:36:08 +01:00
|
|
|
|
|
|
|
// MultiFace is a Face that consists of multiple Face objects.
|
|
|
|
// The face in the first index is used in the highest priority, and the last the lowest priority.
|
|
|
|
//
|
|
|
|
// There is a known issue: if the writing directions of the faces don't agree, the rendering result might be messed up.
|
2023-12-02 06:53:58 +01:00
|
|
|
type MultiFace struct {
|
|
|
|
faces []Face
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewMultiFace creates a new MultiFace from the given faces.
|
2023-12-02 14:50:55 +01:00
|
|
|
func NewMultiFace(faces ...Face) *MultiFace {
|
|
|
|
return &MultiFace{
|
|
|
|
faces: faces,
|
|
|
|
}
|
2023-12-02 06:53:58 +01:00
|
|
|
}
|
2023-11-25 18:36:08 +01:00
|
|
|
|
|
|
|
// Metrics implements Face.
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) Metrics() Metrics {
|
2023-11-25 18:36:08 +01:00
|
|
|
var mt Metrics
|
2023-12-02 06:53:58 +01:00
|
|
|
for _, f := range m.faces {
|
2023-11-25 18:36:08 +01:00
|
|
|
mt1 := f.Metrics()
|
2023-12-02 06:26:17 +01:00
|
|
|
if mt1.HLineGap > mt.HLineGap {
|
|
|
|
mt.HLineGap = mt1.HLineGap
|
2023-11-25 18:36:08 +01:00
|
|
|
}
|
|
|
|
if mt1.HAscent > mt.HAscent {
|
|
|
|
mt.HAscent = mt1.HAscent
|
|
|
|
}
|
|
|
|
if mt1.HDescent > mt.HDescent {
|
|
|
|
mt.HDescent = mt1.HDescent
|
|
|
|
}
|
2023-12-02 06:26:17 +01:00
|
|
|
if mt1.VLineGap > mt.VLineGap {
|
|
|
|
mt.VLineGap = mt1.VLineGap
|
2023-11-25 18:36:08 +01:00
|
|
|
}
|
|
|
|
if mt1.VAscent > mt.VAscent {
|
|
|
|
mt.VAscent = mt1.VAscent
|
|
|
|
}
|
|
|
|
if mt1.VDescent > mt.VDescent {
|
|
|
|
mt.VDescent = mt1.VDescent
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mt
|
|
|
|
}
|
|
|
|
|
|
|
|
// advance implements Face.
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) advance(text string) float64 {
|
2023-11-25 18:36:08 +01:00
|
|
|
var a float64
|
|
|
|
for _, c := range m.splitText(text) {
|
|
|
|
if c.faceIndex == -1 {
|
|
|
|
continue
|
|
|
|
}
|
2023-12-02 06:53:58 +01:00
|
|
|
f := m.faces[c.faceIndex]
|
2023-11-25 18:36:08 +01:00
|
|
|
a += f.advance(text[c.textStartIndex:c.textEndIndex])
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
|
|
|
|
// hasGlyph implements Face.
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) hasGlyph(r rune) bool {
|
|
|
|
for _, f := range m.faces {
|
2023-11-25 18:36:08 +01:00
|
|
|
if f.hasGlyph(r) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// appendGlyphsForLine implements Face.
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph {
|
2023-11-25 18:36:08 +01:00
|
|
|
for _, c := range m.splitText(line) {
|
|
|
|
if c.faceIndex == -1 {
|
|
|
|
continue
|
|
|
|
}
|
2023-12-02 06:53:58 +01:00
|
|
|
f := m.faces[c.faceIndex]
|
2023-11-25 18:36:08 +01:00
|
|
|
t := line[c.textStartIndex:c.textEndIndex]
|
|
|
|
glyphs = f.appendGlyphsForLine(glyphs, t, indexOffset, originX, originY)
|
|
|
|
if a := f.advance(t); f.direction().isHorizontal() {
|
|
|
|
originX += a
|
|
|
|
} else {
|
|
|
|
originY += a
|
|
|
|
}
|
|
|
|
indexOffset += len(t)
|
|
|
|
}
|
|
|
|
return glyphs
|
|
|
|
}
|
|
|
|
|
|
|
|
// appendVectorPathForLine implements Face.
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) appendVectorPathForLine(path *vector.Path, line string, originX, originY float64) {
|
2023-11-25 18:36:08 +01:00
|
|
|
for _, c := range m.splitText(line) {
|
|
|
|
if c.faceIndex == -1 {
|
|
|
|
continue
|
|
|
|
}
|
2023-12-02 06:53:58 +01:00
|
|
|
f := m.faces[c.faceIndex]
|
2023-11-25 18:36:08 +01:00
|
|
|
t := line[c.textStartIndex:c.textEndIndex]
|
|
|
|
f.appendVectorPathForLine(path, t, originX, originY)
|
|
|
|
if a := f.advance(t); f.direction().isHorizontal() {
|
|
|
|
originX += a
|
|
|
|
} else {
|
|
|
|
originY += a
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// direction implements Face.
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) direction() Direction {
|
|
|
|
if len(m.faces) == 0 {
|
2023-11-25 18:36:08 +01:00
|
|
|
return DirectionLeftToRight
|
|
|
|
}
|
2023-12-02 06:53:58 +01:00
|
|
|
return m.faces[0].direction()
|
2023-11-25 18:36:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// private implements Face.
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) private() {
|
2023-11-25 18:36:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type textChunk struct {
|
|
|
|
textStartIndex int
|
|
|
|
textEndIndex int
|
|
|
|
faceIndex int
|
|
|
|
}
|
|
|
|
|
2023-12-02 06:53:58 +01:00
|
|
|
func (m *MultiFace) splitText(text string) []textChunk {
|
2023-11-25 18:36:08 +01:00
|
|
|
var chunks []textChunk
|
|
|
|
|
|
|
|
for ri, r := range text {
|
|
|
|
// -1 indicates the default face index. -1 is used when no face is found for the glyph.
|
|
|
|
fi := -1
|
|
|
|
|
|
|
|
_, l := utf8.DecodeRuneInString(text[ri:])
|
2023-12-02 06:53:58 +01:00
|
|
|
for i, f := range m.faces {
|
2023-11-25 18:36:08 +01:00
|
|
|
if !f.hasGlyph(r) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
fi = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
var s int
|
|
|
|
if len(chunks) > 0 {
|
|
|
|
if chunks[len(chunks)-1].faceIndex == fi {
|
|
|
|
chunks[len(chunks)-1].textEndIndex += l
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
s = chunks[len(chunks)-1].textEndIndex
|
|
|
|
}
|
|
|
|
chunks = append(chunks, textChunk{
|
|
|
|
textStartIndex: s,
|
|
|
|
textEndIndex: s + l,
|
|
|
|
faceIndex: fi,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks
|
|
|
|
}
|