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 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"
"golang.org/x/image/math/fixed"
"github.com/hajimehoshi/ebiten/v2"
2023-11-19 18:50:27 +01:00
"github.com/hajimehoshi/ebiten/v2/vector"
2023-11-11 11:04:13 +01:00
)
2023-11-26 07:52:40 +01:00
// Face is an interface representing a font face. The implementations are only faces in this package, like GoTextFace and StdFace.
2023-11-11 11:04:13 +01:00
type Face interface {
// Metrics returns the metrics for this Face.
Metrics ( ) Metrics
advance ( text string ) float64
2023-11-25 18:36:08 +01:00
hasGlyph ( r rune ) bool
2023-11-19 18:50:27 +01:00
appendGlyphsForLine ( glyphs [ ] Glyph , line string , indexOffset int , originX , originY float64 ) [ ] Glyph
2023-11-25 18:36:08 +01:00
appendVectorPathForLine ( path * vector . Path , line string , originX , originY float64 )
2023-11-11 11:04:13 +01:00
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 {
2023-12-02 06:26:17 +01:00
// HLineGap is the recommended amount of vertical space between two lines of text in pixels.
HLineGap float64
2023-11-11 11:04:13 +01:00
// 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
2023-12-02 06:26:17 +01:00
// VLineGap 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, VLineGap is 0.
VLineGap float64
2023-11-12 09:47:31 +01:00
2023-11-11 11:04:13 +01:00
// 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
}
2023-11-12 09:47:31 +01:00
func fixed26_6ToFloat32 ( x fixed . Int26_6 ) float32 {
return float32 ( x >> 6 ) + float32 ( x & ( ( 1 << 6 ) - 1 ) ) / float32 ( 1 << 6 )
}
2023-11-11 11:04:13 +01:00
func fixed26_6ToFloat64 ( x fixed . Int26_6 ) float64 {
return float64 ( x >> 6 ) + float64 ( x & ( ( 1 << 6 ) - 1 ) ) / float64 ( 1 << 6 )
}
2023-11-12 09:47:31 +01:00
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 ) )
}
2023-11-11 11:04:13 +01:00
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 ) )
}
2023-11-16 04:00:15 +01:00
func glyphVariationCount ( face Face ) int {
2023-11-16 04:36:24 +01:00
var s float64
if m := face . Metrics ( ) ; face . direction ( ) . isHorizontal ( ) {
s = m . HAscent + m . HDescent
} else {
s = m . VAscent + m . VDescent
2023-11-16 04:00:15 +01:00
}
2023-11-16 04:36:24 +01:00
// The threshold is decided based on the rendering result of the examples (e.g. examples/text, examples/ui).
if s < 20 {
2023-11-16 04:00:15 +01:00
return 8
}
2023-11-16 04:36:24 +01:00
if s < 40 {
return 4
}
if s < 80 {
return 2
}
return 1
2023-11-16 04:00:15 +01:00
}
2023-11-12 09:26:19 +01:00
2023-11-16 04:00:15 +01:00
func adjustGranularity ( x fixed . Int26_6 , face Face ) fixed . Int26_6 {
c := glyphVariationCount ( face )
2023-11-16 16:12:17 +01:00
factor := ( 1 << 6 ) / fixed . Int26_6 ( c )
return x / factor * factor
2023-11-11 11:04:13 +01:00
}
// Glyph represents one glyph to render.
type Glyph struct {
2023-11-13 17:16:26 +01:00
// StartIndexInBytes is the start index in bytes for the given string at AppendGlyphs.
StartIndexInBytes int
2023-11-11 11:04:13 +01:00
2023-11-13 17:16:26 +01:00
// EndIndexInBytes is the end index in bytes for the given string at AppendGlyphs.
EndIndexInBytes int
2023-11-26 07:52:40 +01:00
// GID is an ID for a glyph of TrueType or OpenType font. GID is valid when the face is GoTextFace.
2023-11-13 17:16:26 +01:00
GID uint32
2023-11-11 11:04:13 +01:00
// 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.
2023-11-14 19:16:59 +01:00
func Advance ( text string , face Face ) float64 {
2023-11-11 11:04:13 +01:00
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.
2023-11-16 17:17:29 +01:00
func Measure ( text string , face Face , lineSpacingInPixels float64 ) ( width , height float64 ) {
2023-11-11 11:04:13 +01:00
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 ( ) {
2023-11-16 17:17:29 +01:00
secondary := float64 ( lineCount - 1 ) * lineSpacingInPixels + m . HAscent + m . HDescent
2023-11-11 11:04:13 +01:00
return primary , secondary
}
2023-11-16 17:17:29 +01:00
secondary := float64 ( lineCount - 1 ) * lineSpacingInPixels + m . VAscent + m . VDescent
2023-11-11 11:04:13 +01:00
return secondary , primary
}
2023-11-12 09:26:19 +01:00
// 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
2023-11-16 04:00:15 +01:00
c := glyphVariationCount ( face )
2023-11-12 09:26:19 +01:00
var buf [ ] Glyph
// Create all the possible variations (#2528).
2023-11-16 04:00:15 +01:00
for i := 0 ; i < c ; i ++ {
2023-11-13 15:15:54 +01:00
buf = appendGlyphs ( buf , text , face , x , y , nil )
2023-11-12 09:37:18 +01:00
buf = buf [ : 0 ]
2023-11-12 09:26:19 +01:00
if face . direction ( ) . isHorizontal ( ) {
2023-11-16 04:00:15 +01:00
x += 1.0 / float64 ( c )
2023-11-12 09:26:19 +01:00
} else {
2023-11-16 04:00:15 +01:00
y += 1.0 / float64 ( c )
2023-11-12 09:26:19 +01:00
}
}
}