2023-11-11 11:04:13 +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 (
|
|
|
|
"image"
|
|
|
|
"runtime"
|
2023-11-13 17:16:26 +01:00
|
|
|
"unicode/utf8"
|
2023-11-11 11:04:13 +01:00
|
|
|
|
|
|
|
"golang.org/x/image/font"
|
|
|
|
"golang.org/x/image/math/fixed"
|
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ Face = (*StdFace)(nil)
|
|
|
|
|
|
|
|
// StdFace is a Face implementation for a semi-standard font.Face (golang.org/x/image/font).
|
|
|
|
// StdFace is useful to transit from existing codebase with text v1, or to use some bitmap fonts defined as font.Face.
|
|
|
|
// StdFace must not be copied by value.
|
|
|
|
type StdFace struct {
|
|
|
|
f *faceWithCache
|
|
|
|
|
2023-11-12 10:46:22 +01:00
|
|
|
id uint64
|
|
|
|
|
2023-11-11 11:04:13 +01:00
|
|
|
addr *StdFace
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewStdFace creates a new StdFace from a semi-standard font.Face.
|
|
|
|
func NewStdFace(face font.Face) *StdFace {
|
|
|
|
s := &StdFace{
|
|
|
|
f: &faceWithCache{
|
|
|
|
f: face,
|
|
|
|
},
|
2023-11-12 09:47:31 +01:00
|
|
|
id: nextUniqueID(),
|
2023-11-11 11:04:13 +01:00
|
|
|
}
|
|
|
|
s.addr = s
|
2023-11-12 09:47:31 +01:00
|
|
|
runtime.SetFinalizer(s, finalizeStdFace)
|
2023-11-11 11:04:13 +01:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2023-11-12 09:47:31 +01:00
|
|
|
func finalizeStdFace(face *StdFace) {
|
|
|
|
runtime.SetFinalizer(face, nil)
|
|
|
|
theGlyphImageCache.clear(func(key faceCacheKey) bool {
|
2023-11-15 06:22:21 +01:00
|
|
|
return key.id == face.id
|
2023-11-12 09:47:31 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-11-11 11:04:13 +01:00
|
|
|
func (s *StdFace) copyCheck() {
|
|
|
|
if s.addr != s {
|
|
|
|
panic("text: illegal use of non-zero StdFace copied by value")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Metrics implelements Face.
|
|
|
|
func (s *StdFace) Metrics() Metrics {
|
|
|
|
s.copyCheck()
|
|
|
|
|
|
|
|
m := s.f.Metrics()
|
|
|
|
return Metrics{
|
|
|
|
Height: fixed26_6ToFloat64(m.Height),
|
|
|
|
HAscent: fixed26_6ToFloat64(m.Ascent),
|
|
|
|
HDescent: fixed26_6ToFloat64(m.Descent),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnsafeInternal implements Face.
|
|
|
|
func (s *StdFace) UnsafeInternal() any {
|
|
|
|
s.copyCheck()
|
|
|
|
return s.f.f
|
|
|
|
}
|
|
|
|
|
2023-11-12 10:46:22 +01:00
|
|
|
// faceCacheKey implements Face.
|
|
|
|
func (s *StdFace) faceCacheKey() faceCacheKey {
|
2023-11-12 09:47:31 +01:00
|
|
|
return faceCacheKey{
|
2023-11-15 06:22:21 +01:00
|
|
|
id: s.id,
|
2023-11-12 09:47:31 +01:00
|
|
|
}
|
2023-11-12 10:46:22 +01:00
|
|
|
}
|
|
|
|
|
2023-11-11 11:04:13 +01:00
|
|
|
// advance implements Face.
|
|
|
|
func (s *StdFace) advance(text string) float64 {
|
|
|
|
return fixed26_6ToFloat64(font.MeasureString(s.f, text))
|
|
|
|
}
|
|
|
|
|
|
|
|
// appendGlyphs implements Face.
|
2023-11-13 17:16:26 +01:00
|
|
|
func (s *StdFace) appendGlyphs(glyphs []Glyph, text string, indexOffset int, originX, originY float64) []Glyph {
|
2023-11-11 11:04:13 +01:00
|
|
|
s.copyCheck()
|
|
|
|
|
2023-11-12 17:31:25 +01:00
|
|
|
origin := fixed.Point26_6{
|
|
|
|
X: float64ToFixed26_6(originX),
|
|
|
|
Y: float64ToFixed26_6(originY),
|
|
|
|
}
|
2023-11-11 11:04:13 +01:00
|
|
|
prevR := rune(-1)
|
|
|
|
|
|
|
|
for i, r := range text {
|
|
|
|
if prevR >= 0 {
|
2023-11-12 17:31:25 +01:00
|
|
|
origin.X += s.f.Kern(prevR, r)
|
2023-11-11 11:04:13 +01:00
|
|
|
}
|
2023-11-12 17:31:25 +01:00
|
|
|
img, imgX, imgY, a := s.glyphImage(r, origin)
|
2023-11-11 11:04:13 +01:00
|
|
|
if img != nil {
|
|
|
|
// Adjust the position to the integers.
|
|
|
|
// The current glyph images assume that they are rendered on integer positions so far.
|
2023-11-13 17:16:26 +01:00
|
|
|
_, size := utf8.DecodeRuneInString(text[i:])
|
2023-11-11 11:04:13 +01:00
|
|
|
glyphs = append(glyphs, Glyph{
|
2023-11-13 17:16:26 +01:00
|
|
|
StartIndexInBytes: indexOffset + i,
|
|
|
|
EndIndexInBytes: indexOffset + i + size,
|
|
|
|
Image: img,
|
|
|
|
X: float64(imgX),
|
|
|
|
Y: float64(imgY),
|
2023-11-11 11:04:13 +01:00
|
|
|
})
|
|
|
|
}
|
2023-11-12 17:31:25 +01:00
|
|
|
origin.X += a
|
2023-11-11 11:04:13 +01:00
|
|
|
prevR = r
|
|
|
|
}
|
|
|
|
|
|
|
|
return glyphs
|
|
|
|
}
|
|
|
|
|
2023-11-13 05:04:55 +01:00
|
|
|
func (s *StdFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
|
|
|
// Assume that StdFace's direction is always horizontal.
|
|
|
|
origin.X = adjustGranularity(origin.X)
|
|
|
|
origin.Y &^= ((1 << 6) - 1)
|
|
|
|
|
2023-11-11 11:04:13 +01:00
|
|
|
b, a, _ := s.f.GlyphBounds(r)
|
2023-11-12 18:10:57 +01:00
|
|
|
subpixelOffset := fixed.Point26_6{
|
2023-11-13 05:04:55 +01:00
|
|
|
X: (origin.X + b.Min.X) & ((1 << 6) - 1),
|
|
|
|
Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
|
2023-11-11 11:04:13 +01:00
|
|
|
}
|
|
|
|
key := glyphImageCacheKey{
|
2023-11-12 09:47:31 +01:00
|
|
|
id: uint32(r),
|
2023-11-12 18:10:57 +01:00
|
|
|
xoffset: subpixelOffset.X,
|
2023-11-12 18:44:09 +01:00
|
|
|
// yoffset is always the same if the rune is the same, so this doesn't have to be a key.
|
2023-11-11 11:04:13 +01:00
|
|
|
}
|
|
|
|
img := theGlyphImageCache.getOrCreate(s, key, func() *ebiten.Image {
|
2023-11-12 18:10:57 +01:00
|
|
|
return s.glyphImageImpl(r, subpixelOffset, b)
|
2023-11-11 11:04:13 +01:00
|
|
|
})
|
2023-11-13 05:04:55 +01:00
|
|
|
imgX := (origin.X + b.Min.X).Floor()
|
|
|
|
imgY := (origin.Y + b.Min.Y).Floor()
|
2023-11-11 11:04:13 +01:00
|
|
|
return img, imgX, imgY, a
|
|
|
|
}
|
|
|
|
|
2023-11-12 18:10:57 +01:00
|
|
|
func (s *StdFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *ebiten.Image {
|
|
|
|
w, h := (glyphBounds.Max.X - glyphBounds.Min.X).Ceil(), (glyphBounds.Max.Y - glyphBounds.Min.Y).Ceil()
|
2023-11-11 11:04:13 +01:00
|
|
|
if w == 0 || h == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-11-12 18:10:57 +01:00
|
|
|
if glyphBounds.Min.X&((1<<6)-1) != 0 {
|
2023-11-11 11:04:13 +01:00
|
|
|
w++
|
|
|
|
}
|
2023-11-12 18:10:57 +01:00
|
|
|
if glyphBounds.Min.Y&((1<<6)-1) != 0 {
|
2023-11-11 11:04:13 +01:00
|
|
|
h++
|
|
|
|
}
|
|
|
|
rgba := image.NewRGBA(image.Rect(0, 0, w, h))
|
|
|
|
|
|
|
|
d := font.Drawer{
|
|
|
|
Dst: rgba,
|
|
|
|
Src: image.White,
|
|
|
|
Face: s.f,
|
2023-11-12 18:10:57 +01:00
|
|
|
Dot: fixed.Point26_6{
|
|
|
|
X: -glyphBounds.Min.X + subpixelOffset.X,
|
|
|
|
Y: -glyphBounds.Min.Y + subpixelOffset.Y,
|
|
|
|
},
|
2023-11-11 11:04:13 +01:00
|
|
|
}
|
|
|
|
d.DrawString(string(r))
|
|
|
|
|
|
|
|
return ebiten.NewImageFromImage(rgba)
|
|
|
|
}
|
|
|
|
|
|
|
|
// direction implelements Face.
|
|
|
|
func (s *StdFace) direction() Direction {
|
|
|
|
return DirectionLeftToRight
|
|
|
|
}
|
|
|
|
|
|
|
|
// Metrics implelements Face.
|
|
|
|
func (s *StdFace) private() {
|
|
|
|
}
|