mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 11:48:55 +01:00
147175d400
Updates #2454
271 lines
9.0 KiB
Go
271 lines
9.0 KiB
Go
// 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 offers functions to draw texts on an Ebitengine's image.
|
|
//
|
|
// For the example using a TrueType font, see examples in the examples directory.
|
|
package text
|
|
|
|
import (
|
|
"math"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
"golang.org/x/image/math/fixed"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
)
|
|
|
|
// Face is an interface representing a font face. The implementations are only GoTextFace and StdFace.
|
|
type Face interface {
|
|
// Metrics returns the metrics for this Face.
|
|
Metrics() Metrics
|
|
|
|
// UnsafeInternal returns the internal object for this face.
|
|
// The returned value is either a semi-standard font.Face or go-text's font.Face.
|
|
// This is unsafe since this might make internal cache states out of sync.
|
|
UnsafeInternal() any
|
|
|
|
faceCacheKey() faceCacheKey
|
|
|
|
advance(text string) float64
|
|
|
|
appendGlyphs(glyphs []Glyph, text string, indexOffset int, originX, originY float64) []Glyph
|
|
|
|
direction() Direction
|
|
|
|
// private is an unexported function preventing being implemented by other packages.
|
|
private()
|
|
}
|
|
|
|
// Metrics holds the metrics for a Face.
|
|
// A visual depiction is at https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
|
|
type Metrics struct {
|
|
// Height is the recommended amount of vertical space between two lines of text in pixels.
|
|
Height float64
|
|
|
|
// HAscent is the distance in pixels from the top of a line to its baseline for horizontal lines.
|
|
HAscent float64
|
|
|
|
// HDescent is the distance in pixels from the bottom of a line to its baseline for horizontal lines.
|
|
// The value is typically positive, even though a descender goes below the baseline.
|
|
HDescent float64
|
|
|
|
// Width is the recommended amount of horizontal space between two lines of text in pixels.
|
|
// If the face is StdFace or the font dosen't support a vertical direction, Width is 0.
|
|
Width float64
|
|
|
|
// VAscent is the distance in pixels from the top of a line to its baseline for vertical lines.
|
|
// If the face is StdFace or the font dosen't support a vertical direction, VAscent is 0.
|
|
VAscent float64
|
|
|
|
// VDescent is the distance in pixels from the top of a line to its baseline for vertical lines.
|
|
// If the face is StdFace or the font dosen't support a vertical direction, VDescent is 0.
|
|
VDescent float64
|
|
}
|
|
|
|
func fixed26_6ToFloat32(x fixed.Int26_6) float32 {
|
|
return float32(x>>6) + float32(x&((1<<6)-1))/float32(1<<6)
|
|
}
|
|
|
|
func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
|
|
return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
|
|
}
|
|
|
|
func float32ToFixed26_6(x float32) fixed.Int26_6 {
|
|
i := float32(math.Floor(float64(x)))
|
|
frac := x - i
|
|
return fixed.Int26_6(i)<<6 + fixed.Int26_6(frac*(1<<6))
|
|
}
|
|
|
|
func float64ToFixed26_6(x float64) fixed.Int26_6 {
|
|
i := math.Floor(x)
|
|
frac := x - i
|
|
return fixed.Int26_6(i)<<6 + fixed.Int26_6(frac*(1<<6))
|
|
}
|
|
|
|
func glyphVariationCount(face Face) int {
|
|
var s float64
|
|
if m := face.Metrics(); face.direction().isHorizontal() {
|
|
s = m.HAscent + m.HDescent
|
|
} else {
|
|
s = m.VAscent + m.VDescent
|
|
}
|
|
// The threshold is decided based on the rendering result of the examples (e.g. examples/text, examples/ui).
|
|
if s < 20 {
|
|
return 8
|
|
}
|
|
if s < 40 {
|
|
return 4
|
|
}
|
|
if s < 80 {
|
|
return 2
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func adjustGranularity(x fixed.Int26_6, face Face) fixed.Int26_6 {
|
|
c := glyphVariationCount(face)
|
|
factor := (1 << 6) / fixed.Int26_6(c)
|
|
return x / factor * factor
|
|
}
|
|
|
|
// Glyph represents one glyph to render.
|
|
type Glyph struct {
|
|
// StartIndexInBytes is the start index in bytes for the given string at AppendGlyphs.
|
|
StartIndexInBytes int
|
|
|
|
// EndIndexInBytes is the end index in bytes for the given string at AppendGlyphs.
|
|
EndIndexInBytes int
|
|
|
|
// GID is an ID for a glyph of TrueType or OpenType font. GID is valid when the font is GoTextFont.
|
|
GID uint32
|
|
|
|
// Image is a rasterized glyph image.
|
|
// Image is a grayscale image i.e. RGBA values are the same.
|
|
// Image should be used as a render source and should not be modified.
|
|
Image *ebiten.Image
|
|
|
|
// X is the X position to render this glyph.
|
|
// The position is determined in a sequence of characters given at AppendGlyphs.
|
|
// The position's origin is the first character's origin position.
|
|
X float64
|
|
|
|
// Y is the Y position to render this glyph.
|
|
// The position is determined in a sequence of characters given at AppendGlyphs.
|
|
// The position's origin is the first character's origin position.
|
|
Y float64
|
|
}
|
|
|
|
// Advance returns the advanced distance from the origin position when rendering the given text with the given face.
|
|
//
|
|
// Advance doesn't treat multiple lines.
|
|
//
|
|
// Advance is concurrent-safe.
|
|
func Advance(text string, face Face) float64 {
|
|
return face.advance(text)
|
|
}
|
|
|
|
// Direction represents a direction of text rendering.
|
|
// Direction indicates both the primary direction, in which a text in one line is rendered,
|
|
// and the secondary direction, in which multiple lines are rendered.
|
|
type Direction int
|
|
|
|
const (
|
|
// DirectionLeftToRight indicates that the primary direction is from left to right,
|
|
// and the secondary direction is from top to bottom.
|
|
DirectionLeftToRight Direction = iota
|
|
|
|
// DirectionRightToLeft indicates that the primary direction is from right to left,
|
|
// and the secondary direction is from top to bottom.
|
|
DirectionRightToLeft
|
|
|
|
// DirectionTopToBottomAndLeftToRight indicates that the primary direction is from top to bottom,
|
|
// and the secondary direction is from left to right.
|
|
// This is used e.g. for Mongolian.
|
|
DirectionTopToBottomAndLeftToRight
|
|
|
|
// DirectionTopToBottomAndRightToLeft indicates that the primary direction is from top to bottom,
|
|
// and the secondary direction is from right to left.
|
|
// This is used e.g. for Japanese.
|
|
DirectionTopToBottomAndRightToLeft
|
|
)
|
|
|
|
func (d Direction) isHorizontal() bool {
|
|
switch d {
|
|
case DirectionLeftToRight, DirectionRightToLeft:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Measure measures the boundary size of the text.
|
|
// With a horizontal direction face, the width is the longest line's advance, and the height is the total of line heights.
|
|
// With a vertical direction face, the width and the height are calculated in an opposite manner.
|
|
//
|
|
// Measure is concurrent-safe.
|
|
func Measure(text string, face Face, lineSpacingInPixels float64) (width, height float64) {
|
|
if text == "" {
|
|
return 0, 0
|
|
}
|
|
|
|
var primary float64
|
|
var lineCount int
|
|
for t := text; ; {
|
|
lineCount++
|
|
line, rest, found := strings.Cut(t, "\n")
|
|
a := face.advance(line)
|
|
if primary < a {
|
|
primary = a
|
|
}
|
|
if !found {
|
|
break
|
|
}
|
|
t = rest
|
|
}
|
|
|
|
m := face.Metrics()
|
|
|
|
if face.direction().isHorizontal() {
|
|
secondary := float64(lineCount-1)*lineSpacingInPixels + m.HAscent + m.HDescent
|
|
return primary, secondary
|
|
}
|
|
secondary := float64(lineCount-1)*lineSpacingInPixels + m.VAscent + m.VDescent
|
|
return secondary, primary
|
|
}
|
|
|
|
// CacheGlyphs pre-caches the glyphs for the given text and the given font face into the cache.
|
|
//
|
|
// CacheGlyphs doesn't treat multiple lines.
|
|
//
|
|
// Glyphs used for rendering are cached in the least-recently-used way.
|
|
// Then old glyphs might be evicted from the cache.
|
|
// As the cache capacity has limitations, it is not guaranteed that all the glyphs for runes given at CacheGlyphs are cached.
|
|
// The cache is shared with Draw and AppendGlyphs.
|
|
//
|
|
// One rune can have multiple variations of glyphs due to sub-pixels in X or Y direction.
|
|
// CacheGlyphs creates all such variations for one rune, while Draw and AppendGlyphs create only necessary glyphs.
|
|
//
|
|
// Draw and AppendGlyphs automatically create and cache necessary glyphs, so usually you don't have to call CacheGlyphs explicitly.
|
|
// However, for example, when you call Draw for each rune of one big text, Draw tries to create the glyph cache and render it for each rune.
|
|
// This is very inefficient because creating a glyph image and rendering it are different operations
|
|
// (`(*ebiten.Image).WritePixels` and `(*ebiten.Image).DrawImage`) and can never be merged as one draw call.
|
|
// CacheGlyphs creates necessary glyphs without rendering them so that these operations are likely merged into one draw call regardless of the size of the text.
|
|
//
|
|
// CacheGlyphs is concurrent-safe.
|
|
func CacheGlyphs(text string, face Face) {
|
|
var x, y float64
|
|
|
|
c := glyphVariationCount(face)
|
|
|
|
var buf []Glyph
|
|
// Create all the possible variations (#2528).
|
|
for i := 0; i < c; i++ {
|
|
buf = appendGlyphs(buf, text, face, x, y, nil)
|
|
buf = buf[:0]
|
|
|
|
if face.direction().isHorizontal() {
|
|
x += 1.0 / float64(c)
|
|
} else {
|
|
y += 1.0 / float64(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
var currentUniqueID uint64
|
|
|
|
func nextUniqueID() uint64 {
|
|
return atomic.AddUint64(¤tUniqueID, 1)
|
|
}
|