mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 19:58:54 +01:00
Compare commits
4 Commits
17c2b1b325
...
2748f31fc4
Author | SHA1 | Date | |
---|---|---|---|
|
2748f31fc4 | ||
|
d19a774316 | ||
|
41e8d063e8 | ||
|
2fab556dd9 |
@ -783,12 +783,3 @@ func ChangeHSV(c ColorM, hueTheta float64, saturationScale float32, valueScale f
|
|||||||
c = c.Concat(yCbCrToRgb)
|
c = c.Concat(yCbCrToRgb)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
type cachedScalingColorMKey struct {
|
|
||||||
r, g, b, a float32
|
|
||||||
}
|
|
||||||
|
|
||||||
type cachedScalingColorMValue struct {
|
|
||||||
c *colorMImplScale
|
|
||||||
atime uint64
|
|
||||||
}
|
|
||||||
|
106
text/v2/cache.go
Normal file
106
text/v2/cache.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2024 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 (
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/hook"
|
||||||
|
)
|
||||||
|
|
||||||
|
var monotonicClock atomic.Int64
|
||||||
|
|
||||||
|
const infTime = math.MaxInt64
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
hook.AppendHookOnBeforeUpdate(func() error {
|
||||||
|
monotonicClock.Add(1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type cacheValue[Value any] struct {
|
||||||
|
value Value
|
||||||
|
|
||||||
|
// atime is the last time when the value was accessed.
|
||||||
|
atime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type cache[Key comparable, Value any] struct {
|
||||||
|
// softLimit indicates the soft limit of the number of values in the cache.
|
||||||
|
softLimit int
|
||||||
|
|
||||||
|
values map[Key]*cacheValue[Value]
|
||||||
|
|
||||||
|
// atime is the last time when the cache was accessed.
|
||||||
|
atime int64
|
||||||
|
|
||||||
|
m sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCache[Key comparable, Value any](softLimit int) *cache[Key, Value] {
|
||||||
|
return &cache[Key, Value]{
|
||||||
|
softLimit: softLimit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cache[Key, Value]) getOrCreate(key Key, create func() (Value, bool)) Value {
|
||||||
|
n := monotonicClock.Load()
|
||||||
|
|
||||||
|
c.m.Lock()
|
||||||
|
defer c.m.Unlock()
|
||||||
|
|
||||||
|
e, ok := c.values[key]
|
||||||
|
if ok {
|
||||||
|
e.atime = n
|
||||||
|
return e.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.values == nil {
|
||||||
|
c.values = map[Key]*cacheValue[Value]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
ent, canExpire := create()
|
||||||
|
e = &cacheValue[Value]{
|
||||||
|
value: ent,
|
||||||
|
atime: infTime,
|
||||||
|
}
|
||||||
|
if canExpire {
|
||||||
|
e.atime = n
|
||||||
|
}
|
||||||
|
c.values[key] = e
|
||||||
|
|
||||||
|
// Clean up old entries.
|
||||||
|
if c.atime < n {
|
||||||
|
// If the number of values exceeds the soft limits, old values are removed.
|
||||||
|
// Even after cleaning up the cache, the number of values might still exceed the soft limit,
|
||||||
|
// but this is fine.
|
||||||
|
if len(c.values) > c.softLimit {
|
||||||
|
for key, e := range c.values {
|
||||||
|
// 60 is an arbitrary number.
|
||||||
|
if e.atime >= n-60 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(c.values, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.atime = n
|
||||||
|
|
||||||
|
return e.value
|
||||||
|
}
|
101
text/v2/glyph.go
101
text/v2/glyph.go
@ -1,101 +0,0 @@
|
|||||||
// 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 (
|
|
||||||
"math"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/hook"
|
|
||||||
)
|
|
||||||
|
|
||||||
var monotonicClock int64
|
|
||||||
|
|
||||||
const infTime = math.MaxInt64
|
|
||||||
|
|
||||||
func now() int64 {
|
|
||||||
return monotonicClock
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
hook.AppendHookOnBeforeUpdate(func() error {
|
|
||||||
monotonicClock++
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type glyphImageCacheEntry struct {
|
|
||||||
image *ebiten.Image
|
|
||||||
atime int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type glyphImageCache[Key comparable] struct {
|
|
||||||
cache map[Key]*glyphImageCacheEntry
|
|
||||||
atime int64
|
|
||||||
m sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *glyphImageCache[Key]) getOrCreate(face Face, key Key, create func() *ebiten.Image) *ebiten.Image {
|
|
||||||
g.m.Lock()
|
|
||||||
defer g.m.Unlock()
|
|
||||||
|
|
||||||
n := now()
|
|
||||||
|
|
||||||
e, ok := g.cache[key]
|
|
||||||
if ok {
|
|
||||||
e.atime = n
|
|
||||||
return e.image
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.cache == nil {
|
|
||||||
g.cache = map[Key]*glyphImageCacheEntry{}
|
|
||||||
}
|
|
||||||
|
|
||||||
img := create()
|
|
||||||
e = &glyphImageCacheEntry{
|
|
||||||
image: img,
|
|
||||||
}
|
|
||||||
if img != nil {
|
|
||||||
e.atime = n
|
|
||||||
} else {
|
|
||||||
// If the glyph image is nil, the entry doesn't have to be removed.
|
|
||||||
// Keep this until the face is GCed.
|
|
||||||
e.atime = infTime
|
|
||||||
}
|
|
||||||
g.cache[key] = e
|
|
||||||
|
|
||||||
// Clean up old entries.
|
|
||||||
if g.atime < n {
|
|
||||||
// cacheSoftLimit indicates the soft limit of the number of glyphs in the cache.
|
|
||||||
// If the number of glyphs exceeds this soft limits, old glyphs are removed.
|
|
||||||
// Even after cleaning up the cache, the number of glyphs might still exceed the soft limit, but
|
|
||||||
// this is fine.
|
|
||||||
cacheSoftLimit := 128 * glyphVariationCount(face)
|
|
||||||
if len(g.cache) > cacheSoftLimit {
|
|
||||||
for key, e := range g.cache {
|
|
||||||
// 60 is an arbitrary number.
|
|
||||||
if e.atime >= now()-60 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(g.cache, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.atime = n
|
|
||||||
|
|
||||||
return img
|
|
||||||
}
|
|
@ -369,8 +369,9 @@ func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Im
|
|||||||
yoffset: subpixelOffset.Y,
|
yoffset: subpixelOffset.Y,
|
||||||
variations: g.ensureVariationsString(),
|
variations: g.ensureVariationsString(),
|
||||||
}
|
}
|
||||||
img := g.Source.getOrCreateGlyphImage(g, key, func() *ebiten.Image {
|
img := g.Source.getOrCreateGlyphImage(g, key, func() (*ebiten.Image, bool) {
|
||||||
return segmentsToImage(glyph.scaledSegments, subpixelOffset, b)
|
img := segmentsToImage(glyph.scaledSegments, subpixelOffset, b)
|
||||||
|
return img, img != nil
|
||||||
})
|
})
|
||||||
|
|
||||||
imgX := (origin.X + b.Min.X).Floor()
|
imgX := (origin.X + b.Min.X).Floor()
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-text/typesetting/font"
|
"github.com/go-text/typesetting/font"
|
||||||
"github.com/go-text/typesetting/font/opentype"
|
"github.com/go-text/typesetting/font/opentype"
|
||||||
@ -50,7 +49,6 @@ type glyph struct {
|
|||||||
type goTextOutputCacheValue struct {
|
type goTextOutputCacheValue struct {
|
||||||
outputs []shaping.Output
|
outputs []shaping.Output
|
||||||
glyphs []glyph
|
glyphs []glyph
|
||||||
atime int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type goTextGlyphImageCacheKey struct {
|
type goTextGlyphImageCacheKey struct {
|
||||||
@ -65,14 +63,12 @@ type GoTextFaceSource struct {
|
|||||||
f *font.Face
|
f *font.Face
|
||||||
metadata Metadata
|
metadata Metadata
|
||||||
|
|
||||||
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
outputCache *cache[goTextOutputCacheKey, goTextOutputCacheValue]
|
||||||
glyphImageCache map[float64]*glyphImageCache[goTextGlyphImageCacheKey]
|
glyphImageCache map[float64]*cache[goTextGlyphImageCacheKey, *ebiten.Image]
|
||||||
|
|
||||||
addr *GoTextFaceSource
|
addr *GoTextFaceSource
|
||||||
|
|
||||||
shaper shaping.HarfbuzzShaper
|
shaper shaping.HarfbuzzShaper
|
||||||
|
|
||||||
m sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toFontResource(source io.Reader) (font.Resource, error) {
|
func toFontResource(source io.Reader) (font.Resource, error) {
|
||||||
@ -115,6 +111,7 @@ func NewGoTextFaceSource(source io.Reader) (*GoTextFaceSource, error) {
|
|||||||
}
|
}
|
||||||
s.addr = s
|
s.addr = s
|
||||||
s.metadata = metadataFromLoader(l)
|
s.metadata = metadataFromLoader(l)
|
||||||
|
s.outputCache = newCache[goTextOutputCacheKey, goTextOutputCacheValue](512)
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@ -171,15 +168,18 @@ func (g *GoTextFaceSource) UnsafeInternal() any {
|
|||||||
func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Output, []glyph) {
|
func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Output, []glyph) {
|
||||||
g.copyCheck()
|
g.copyCheck()
|
||||||
|
|
||||||
g.m.Lock()
|
|
||||||
defer g.m.Unlock()
|
|
||||||
|
|
||||||
key := face.outputCacheKey(text)
|
key := face.outputCacheKey(text)
|
||||||
if out, ok := g.outputCache[key]; ok {
|
e := g.outputCache.getOrCreate(key, func() (goTextOutputCacheValue, bool) {
|
||||||
out.atime = now()
|
outputs, gs := g.shapeImpl(text, face)
|
||||||
return out.outputs, out.glyphs
|
return goTextOutputCacheValue{
|
||||||
}
|
outputs: outputs,
|
||||||
|
glyphs: gs,
|
||||||
|
}, true
|
||||||
|
})
|
||||||
|
return e.outputs, e.glyphs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GoTextFaceSource) shapeImpl(text string, face *GoTextFace) ([]shaping.Output, []glyph) {
|
||||||
f := face.Source.f
|
f := face.Source.f
|
||||||
f.SetVariations(face.variations)
|
f.SetVariations(face.variations)
|
||||||
|
|
||||||
@ -254,27 +254,6 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.outputCache == nil {
|
|
||||||
g.outputCache = map[goTextOutputCacheKey]*goTextOutputCacheValue{}
|
|
||||||
}
|
|
||||||
g.outputCache[key] = &goTextOutputCacheValue{
|
|
||||||
outputs: outputs,
|
|
||||||
glyphs: gs,
|
|
||||||
atime: now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
const cacheSoftLimit = 512
|
|
||||||
if len(g.outputCache) > cacheSoftLimit {
|
|
||||||
for key, e := range g.outputCache {
|
|
||||||
// 60 is an arbitrary number.
|
|
||||||
if e.atime >= now()-60 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(g.outputCache, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputs, gs
|
return outputs, gs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,14 +261,14 @@ func (g *GoTextFaceSource) scale(size float64) float64 {
|
|||||||
return size / float64(g.f.Upem())
|
return size / float64(g.f.Upem())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goTextGlyphImageCacheKey, create func() *ebiten.Image) *ebiten.Image {
|
func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goTextGlyphImageCacheKey, create func() (*ebiten.Image, bool)) *ebiten.Image {
|
||||||
if g.glyphImageCache == nil {
|
if g.glyphImageCache == nil {
|
||||||
g.glyphImageCache = map[float64]*glyphImageCache[goTextGlyphImageCacheKey]{}
|
g.glyphImageCache = map[float64]*cache[goTextGlyphImageCacheKey, *ebiten.Image]{}
|
||||||
}
|
}
|
||||||
if _, ok := g.glyphImageCache[goTextFace.Size]; !ok {
|
if _, ok := g.glyphImageCache[goTextFace.Size]; !ok {
|
||||||
g.glyphImageCache[goTextFace.Size] = &glyphImageCache[goTextGlyphImageCacheKey]{}
|
g.glyphImageCache[goTextFace.Size] = newCache[goTextGlyphImageCacheKey, *ebiten.Image](128 * glyphVariationCount(goTextFace))
|
||||||
}
|
}
|
||||||
return g.glyphImageCache[goTextFace.Size].getOrCreate(goTextFace, key, create)
|
return g.glyphImageCache[goTextFace.Size].getOrCreate(key, create)
|
||||||
}
|
}
|
||||||
|
|
||||||
type singleFontmap struct {
|
type singleFontmap struct {
|
||||||
|
@ -43,7 +43,7 @@ type goXFaceGlyphImageCacheKey struct {
|
|||||||
type GoXFace struct {
|
type GoXFace struct {
|
||||||
f *faceWithCache
|
f *faceWithCache
|
||||||
|
|
||||||
glyphImageCache glyphImageCache[goXFaceGlyphImageCacheKey]
|
glyphImageCache *cache[goXFaceGlyphImageCacheKey, *ebiten.Image]
|
||||||
|
|
||||||
cachedMetrics Metrics
|
cachedMetrics Metrics
|
||||||
|
|
||||||
@ -52,30 +52,32 @@ type GoXFace struct {
|
|||||||
|
|
||||||
// NewGoXFace creates a new GoXFace from a semi-standard font.Face.
|
// NewGoXFace creates a new GoXFace from a semi-standard font.Face.
|
||||||
func NewGoXFace(face font.Face) *GoXFace {
|
func NewGoXFace(face font.Face) *GoXFace {
|
||||||
s := &GoXFace{
|
g := &GoXFace{
|
||||||
f: &faceWithCache{
|
f: &faceWithCache{
|
||||||
f: face,
|
f: face,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.addr = s
|
// Set addr as early as possible. This is necessary for glyphVariationCount.
|
||||||
return s
|
g.addr = g
|
||||||
|
g.glyphImageCache = newCache[goXFaceGlyphImageCacheKey, *ebiten.Image](128 * glyphVariationCount(g))
|
||||||
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GoXFace) copyCheck() {
|
func (g *GoXFace) copyCheck() {
|
||||||
if s.addr != s {
|
if g.addr != g {
|
||||||
panic("text: illegal use of non-zero GoXFace copied by value")
|
panic("text: illegal use of non-zero GoXFace copied by value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metrics implements Face.
|
// Metrics implements Face.
|
||||||
func (s *GoXFace) Metrics() Metrics {
|
func (g *GoXFace) Metrics() Metrics {
|
||||||
s.copyCheck()
|
g.copyCheck()
|
||||||
|
|
||||||
if s.cachedMetrics != (Metrics{}) {
|
if g.cachedMetrics != (Metrics{}) {
|
||||||
return s.cachedMetrics
|
return g.cachedMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
fm := s.f.Metrics()
|
fm := g.f.Metrics()
|
||||||
m := Metrics{
|
m := Metrics{
|
||||||
HLineGap: fixed26_6ToFloat64(fm.Height - fm.Ascent - fm.Descent),
|
HLineGap: fixed26_6ToFloat64(fm.Height - fm.Ascent - fm.Descent),
|
||||||
HAscent: fixed26_6ToFloat64(fm.Ascent),
|
HAscent: fixed26_6ToFloat64(fm.Ascent),
|
||||||
@ -91,7 +93,7 @@ func (s *GoXFace) Metrics() Metrics {
|
|||||||
if fm.CapHeight < 0 {
|
if fm.CapHeight < 0 {
|
||||||
m.CapHeight *= -1
|
m.CapHeight *= -1
|
||||||
}
|
}
|
||||||
s.cachedMetrics = m
|
g.cachedMetrics = m
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,25 +102,25 @@ func (s *GoXFace) Metrics() Metrics {
|
|||||||
// UnsafeInternal is unsafe since this might make internal cache states out of sync.
|
// UnsafeInternal is unsafe since this might make internal cache states out of sync.
|
||||||
//
|
//
|
||||||
// UnsafeInternal might have breaking changes even in the same major version.
|
// UnsafeInternal might have breaking changes even in the same major version.
|
||||||
func (s *GoXFace) UnsafeInternal() font.Face {
|
func (g *GoXFace) UnsafeInternal() font.Face {
|
||||||
s.copyCheck()
|
g.copyCheck()
|
||||||
return s.f.f
|
return g.f.f
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance implements Face.
|
// advance implements Face.
|
||||||
func (s *GoXFace) advance(text string) float64 {
|
func (g *GoXFace) advance(text string) float64 {
|
||||||
return fixed26_6ToFloat64(font.MeasureString(s.f, text))
|
return fixed26_6ToFloat64(font.MeasureString(g.f, text))
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasGlyph implements Face.
|
// hasGlyph implements Face.
|
||||||
func (s *GoXFace) hasGlyph(r rune) bool {
|
func (g *GoXFace) hasGlyph(r rune) bool {
|
||||||
_, ok := s.f.GlyphAdvance(r)
|
_, ok := g.f.GlyphAdvance(r)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendGlyphsForLine implements Face.
|
// appendGlyphsForLine implements Face.
|
||||||
func (s *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph {
|
func (g *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph {
|
||||||
s.copyCheck()
|
g.copyCheck()
|
||||||
|
|
||||||
origin := fixed.Point26_6{
|
origin := fixed.Point26_6{
|
||||||
X: float64ToFixed26_6(originX),
|
X: float64ToFixed26_6(originX),
|
||||||
@ -128,9 +130,9 @@ func (s *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i
|
|||||||
|
|
||||||
for i, r := range line {
|
for i, r := range line {
|
||||||
if prevR >= 0 {
|
if prevR >= 0 {
|
||||||
origin.X += s.f.Kern(prevR, r)
|
origin.X += g.f.Kern(prevR, r)
|
||||||
}
|
}
|
||||||
img, imgX, imgY, a := s.glyphImage(r, origin)
|
img, imgX, imgY, a := g.glyphImage(r, origin)
|
||||||
|
|
||||||
// Adjust the position to the integers.
|
// Adjust the position to the integers.
|
||||||
// The current glyph images assume that they are rendered on integer positions so far.
|
// The current glyph images assume that they are rendered on integer positions so far.
|
||||||
@ -156,12 +158,12 @@ func (s *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i
|
|||||||
return glyphs
|
return glyphs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
func (g *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
||||||
// Assume that GoXFace's direction is always horizontal.
|
// Assume that GoXFace's direction is always horizontal.
|
||||||
origin.X = adjustGranularity(origin.X, s)
|
origin.X = adjustGranularity(origin.X, g)
|
||||||
origin.Y &^= ((1 << 6) - 1)
|
origin.Y &^= ((1 << 6) - 1)
|
||||||
|
|
||||||
b, a, _ := s.f.GlyphBounds(r)
|
b, a, _ := g.f.GlyphBounds(r)
|
||||||
subpixelOffset := fixed.Point26_6{
|
subpixelOffset := fixed.Point26_6{
|
||||||
X: (origin.X + b.Min.X) & ((1 << 6) - 1),
|
X: (origin.X + b.Min.X) & ((1 << 6) - 1),
|
||||||
Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
|
Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
|
||||||
@ -170,15 +172,16 @@ func (s *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int
|
|||||||
rune: r,
|
rune: r,
|
||||||
xoffset: subpixelOffset.X,
|
xoffset: subpixelOffset.X,
|
||||||
}
|
}
|
||||||
img := s.glyphImageCache.getOrCreate(s, key, func() *ebiten.Image {
|
img := g.glyphImageCache.getOrCreate(key, func() (*ebiten.Image, bool) {
|
||||||
return s.glyphImageImpl(r, subpixelOffset, b)
|
img := g.glyphImageImpl(r, subpixelOffset, b)
|
||||||
|
return img, img != nil
|
||||||
})
|
})
|
||||||
imgX := (origin.X + b.Min.X).Floor()
|
imgX := (origin.X + b.Min.X).Floor()
|
||||||
imgY := (origin.Y + b.Min.Y).Floor()
|
imgY := (origin.Y + b.Min.Y).Floor()
|
||||||
return img, imgX, imgY, a
|
return img, imgX, imgY, a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GoXFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *ebiten.Image {
|
func (g *GoXFace) 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()
|
w, h := (glyphBounds.Max.X - glyphBounds.Min.X).Ceil(), (glyphBounds.Max.Y - glyphBounds.Min.Y).Ceil()
|
||||||
if w == 0 || h == 0 {
|
if w == 0 || h == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -194,7 +197,7 @@ func (s *GoXFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBo
|
|||||||
d := font.Drawer{
|
d := font.Drawer{
|
||||||
Dst: rgba,
|
Dst: rgba,
|
||||||
Src: image.White,
|
Src: image.White,
|
||||||
Face: s.f,
|
Face: g.f,
|
||||||
Dot: fixed.Point26_6{
|
Dot: fixed.Point26_6{
|
||||||
X: -glyphBounds.Min.X + subpixelOffset.X,
|
X: -glyphBounds.Min.X + subpixelOffset.X,
|
||||||
Y: -glyphBounds.Min.Y + subpixelOffset.Y,
|
Y: -glyphBounds.Min.Y + subpixelOffset.Y,
|
||||||
@ -206,14 +209,14 @@ func (s *GoXFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// direction implements Face.
|
// direction implements Face.
|
||||||
func (s *GoXFace) direction() Direction {
|
func (g *GoXFace) direction() Direction {
|
||||||
return DirectionLeftToRight
|
return DirectionLeftToRight
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendVectorPathForLine implements Face.
|
// appendVectorPathForLine implements Face.
|
||||||
func (s *GoXFace) appendVectorPathForLine(path *vector.Path, line string, originX, originY float64) {
|
func (g *GoXFace) appendVectorPathForLine(path *vector.Path, line string, originX, originY float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metrics implements Face.
|
// Metrics implements Face.
|
||||||
func (s *GoXFace) private() {
|
func (g *GoXFace) private() {
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user