123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- // Copyright 2017 The Ebiten 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 TTF font, see font package in the examples.
- //
- // Deprecated: as of v2.7. Use text/v2 instead.
- package text
- import (
- "image"
- "image/color"
- "sync"
- "golang.org/x/image/font"
- "golang.org/x/image/math/fixed"
- "github.com/hajimehoshi/ebiten/v2"
- "github.com/hajimehoshi/ebiten/v2/internal/hook"
- )
- var (
- monotonicClock int64
- )
- func now() int64 {
- return monotonicClock
- }
- func init() {
- hook.AppendHookOnBeforeUpdate(func() error {
- monotonicClock++
- return nil
- })
- }
- func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
- return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
- }
- func adjustOffsetGranularity(x fixed.Int26_6) fixed.Int26_6 {
- return x / (1 << 4) * (1 << 4)
- }
- func drawGlyph(dst *ebiten.Image, img *ebiten.Image, topleft fixed.Point26_6, op *ebiten.DrawImageOptions) {
- if img == nil {
- return
- }
- op2 := &ebiten.DrawImageOptions{}
- if op != nil {
- *op2 = *op
- op2.GeoM.Reset()
- }
- op2.GeoM.Translate(fixed26_6ToFloat64(topleft.X), fixed26_6ToFloat64(topleft.Y))
- if op != nil {
- op2.GeoM.Concat(op.GeoM)
- }
- dst.DrawImage(img, op2)
- }
- type glyphImageCacheKey struct {
- rune rune
- xoffset fixed.Int26_6
- }
- type glyphImageCacheEntry struct {
- image *ebiten.Image
- atime int64
- }
- var (
- glyphImageCache = map[*faceWithCache]map[glyphImageCacheKey]*glyphImageCacheEntry{}
- )
- func getGlyphImage(face *faceWithCache, r rune, offset fixed.Point26_6) *ebiten.Image {
- if _, ok := glyphImageCache[face]; !ok {
- glyphImageCache[face] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
- }
- key := glyphImageCacheKey{
- rune: r,
- xoffset: offset.X,
- }
- if e, ok := glyphImageCache[face][key]; ok {
- e.atime = now()
- return e.image
- }
- b, _, _ := face.GlyphBounds(r)
- w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
- if w == 0 || h == 0 {
- glyphImageCache[face][key] = &glyphImageCacheEntry{
- image: nil,
- atime: now(),
- }
- return nil
- }
- if b.Min.X&((1<<6)-1) != 0 {
- w++
- }
- if b.Min.Y&((1<<6)-1) != 0 {
- h++
- }
- rgba := image.NewRGBA(image.Rect(0, 0, w, h))
- d := font.Drawer{
- Dst: rgba,
- Src: image.White,
- Face: face,
- }
- x, y := -b.Min.X, -b.Min.Y
- x += offset.X
- y += offset.Y
- d.Dot = fixed.Point26_6{X: x, Y: y}
- d.DrawString(string(r))
- img := ebiten.NewImageFromImage(rgba)
- glyphImageCache[face][key] = &glyphImageCacheEntry{
- image: img,
- atime: now(),
- }
- return img
- }
- var textM sync.Mutex
- // Draw draws a given text on a given destination image dst.
- //
- // face is the font for text rendering.
- // (x, y) represents the origin position in this figure:
- // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png.
- // Be careful that this doesn't represent upper-left corner position.
- //
- // clr is the color for text rendering.
- //
- // The '\n' newline character puts the following text on the next line.
- // Line height is based on Metrics().Height of the font.
- //
- // Glyphs used for rendering are cached in least-recently-used way.
- // Then old glyphs might be evicted from the cache.
- // As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at Draw are cached.
- // The cache is shared with CacheGlyphs.
- //
- // It is OK to call Draw with a same text and a same face at every frame in terms of performance.
- //
- // Draw/DrawWithOptions and CacheGlyphs are implemented like this:
- //
- // Draw = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
- // + Draw them onto the destination by `(*ebiten.Image).DrawImage`
- // CacheGlyphs = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
- //
- // Be careful that the passed font face is held by this package and is never released.
- // This is a known issue (#498).
- //
- // Draw is concurrent-safe.
- func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {
- op := &ebiten.DrawImageOptions{}
- op.GeoM.Translate(float64(x), float64(y))
- op.ColorScale.ScaleWithColor(clr)
- DrawWithOptions(dst, text, face, op)
- }
- // DrawWithOptions draws a given text on a given destination image dst.
- //
- // face is the font for text rendering.
- // options is the options to draw glyph images.
- // The origin point is the origin position in this figure:
- // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png.
- // Be careful that the origin point is not upper-left corner position of dst.
- // The default glyph color is white. options' ColorScale adjusts the color.
- //
- // The '\n' newline character puts the following text on the next line.
- // Line height is based on Metrics().Height of the font.
- //
- // Glyphs used for rendering are cached in least-recently-used way.
- // Then old glyphs might be evicted from the cache.
- // As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at DrawWithOptions are cached.
- // The cache is shared with CacheGlyphs.
- //
- // It is OK to call DrawWithOptions with a same text and a same face at every frame in terms of performance.
- //
- // Draw/DrawWithOptions and CacheGlyphs are implemented like this:
- //
- // Draw = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
- // + Draw them onto the destination by `(*ebiten.Image).DrawImage`
- // CacheGlyphs = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
- //
- // Be careful that the passed font face is held by this package and is never released.
- // This is a known issue (#498).
- //
- // DrawWithOptions is concurrent-safe.
- func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *ebiten.DrawImageOptions) {
- textM.Lock()
- defer textM.Unlock()
- fc := faceWithCacheFromFace(face)
- var dx, dy fixed.Int26_6
- prevR := rune(-1)
- faceHeight := fc.Metrics().Height
- for _, r := range text {
- if prevR >= 0 {
- dx += fc.Kern(prevR, r)
- }
- if r == '\n' {
- dx = 0
- dy += faceHeight
- prevR = rune(-1)
- continue
- }
- // Adjust the position to the integers.
- // The current glyph images assume that they are rendered on integer positions so far.
- b, a, _ := fc.GlyphBounds(r)
- offset := fixed.Point26_6{
- X: (adjustOffsetGranularity(dx) + b.Min.X) & ((1 << 6) - 1),
- Y: b.Min.Y & ((1 << 6) - 1),
- }
- img := getGlyphImage(fc, r, offset)
- drawGlyph(dst, img, fixed.Point26_6{
- X: dx + b.Min.X - offset.X,
- Y: dy + b.Min.Y - offset.Y,
- }, options)
- dx += a
- prevR = r
- }
- // 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.
- const cacheSoftLimit = 512
- // Clean up the cache.
- if len(glyphImageCache[fc]) > cacheSoftLimit {
- for r, e := range glyphImageCache[fc] {
- // 60 is an arbitrary number.
- if e.atime < now()-60 {
- delete(glyphImageCache[fc], r)
- }
- }
- }
- }
- // BoundString returns the measured size of a given string using a given font.
- // This method will return the exact size in pixels that a string drawn by Draw will be.
- // The bound's origin point indicates the origin position in this figure:
- // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png.
- //
- // BoundString behaves almost exactly like golang.org/x/image/font's BoundString,
- // but newline characters '\n' in the input string move the text position to the following line.
- //
- // face is the font for text rendering.
- // text is the string that's being measured.
- //
- // Be careful that the passed font face is held by this package and is never released.
- // This is a known issue (#498).
- //
- // BoundString is concurrent-safe.
- //
- // Deprecated: as of v2.6. Use golang.org/x/image/font.BoundString instead, or use a face's Metrics (Ascent and Descent) and font.MeasureString instead.
- func BoundString(face font.Face, text string) image.Rectangle {
- textM.Lock()
- defer textM.Unlock()
- fc := faceWithCacheFromFace(face)
- m := fc.Metrics()
- faceHeight := m.Height
- fx, fy := fixed.I(0), fixed.I(0)
- prevR := rune(-1)
- var bounds fixed.Rectangle26_6
- for _, r := range text {
- if prevR >= 0 {
- fx += fc.Kern(prevR, r)
- }
- if r == '\n' {
- fx = fixed.I(0)
- fy += faceHeight
- prevR = rune(-1)
- continue
- }
- b, a, _ := fc.GlyphBounds(r)
- b.Min.X += fx
- b.Max.X += fx
- b.Min.Y += fy
- b.Max.Y += fy
- bounds = bounds.Union(b)
- fx += a
- prevR = r
- }
- return image.Rect(
- bounds.Min.X.Floor(),
- bounds.Min.Y.Floor(),
- bounds.Max.X.Ceil(),
- bounds.Max.Y.Ceil(),
- )
- }
- // CacheGlyphs precaches the glyphs for the given text and the given font face into the cache.
- //
- // Glyphs used for rendering are cached in least-recently-used way.
- // Then old glyphs might be evicted from the cache.
- // As the cache capacity has limit, it is not guaranteed that all the glyphs for runes given at CacheGlyphs are cached.
- // The cache is shared with Draw.
- //
- // Draw/DrawWithOptions and CacheGlyphs are implemented like this:
- //
- // Draw = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
- // + Draw them onto the destination by `(*ebiten.Image).DrawImage`
- // CacheGlyphs = Create glyphs by `(*ebiten.Image).WritePixels` and put them into the cache if necessary
- //
- // Draw automatically creates and caches 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.
- //
- // If a rune's glyph is already cached, CacheGlyphs does nothing for the rune.
- //
- // One rune can have multiple variations of glyphs due to sub-pixels in X direction.
- // CacheGlyphs creates all such variations for one rune, while Draw creates only necessary glyphs.
- func CacheGlyphs(face font.Face, text string) {
- textM.Lock()
- defer textM.Unlock()
- fc := faceWithCacheFromFace(face)
- var dx fixed.Int26_6
- prevR := rune(-1)
- for _, r := range text {
- if prevR >= 0 {
- dx += fc.Kern(prevR, r)
- }
- if r == '\n' {
- dx = 0
- continue
- }
- b, a, _ := fc.GlyphBounds(r)
- // Cache all 4 variations for one rune (#2528).
- for i := 0; i < 4; i++ {
- offset := fixed.Point26_6{
- X: (fixed.Int26_6(i*(1<<4)) + b.Min.X) & ((1 << 6) - 1),
- Y: b.Min.Y & ((1 << 6) - 1),
- }
- getGlyphImage(fc, r, offset)
- }
- dx += a
- prevR = r
- }
- }
- // FaceWithLineHeight returns a font.Face with the given lineHeight in pixels.
- // The returned face will otherwise have the same glyphs and metrics as face.
- func FaceWithLineHeight(face font.Face, lineHeight float64) font.Face {
- return faceWithLineHeight{
- face: face,
- lineHeight: fixed.Int26_6(lineHeight * (1 << 6)),
- }
- }
- type faceWithLineHeight struct {
- face font.Face
- lineHeight fixed.Int26_6
- }
- func (f faceWithLineHeight) Close() error {
- return f.face.Close()
- }
- func (f faceWithLineHeight) Glyph(origin fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
- return f.face.Glyph(origin, r)
- }
- func (f faceWithLineHeight) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
- return f.face.GlyphBounds(r)
- }
- func (f faceWithLineHeight) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
- return f.face.GlyphAdvance(r)
- }
- func (f faceWithLineHeight) Kern(r0, r1 rune) fixed.Int26_6 {
- return f.face.Kern(r0, r1)
- }
- func (f faceWithLineHeight) Metrics() font.Metrics {
- m := f.face.Metrics()
- m.Height = f.lineHeight
- return m
- }
- // Glyph is information to render one glyph.
- type Glyph struct {
- // Rune is a character for this glyph.
- Rune rune
- // Image is an image for this glyph.
- // 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
- }
- // AppendGlyphs appends the glyph information to glyphs.
- // You can render each glyphs as you like. See examples/text for an example of AppendGlyphs.
- func AppendGlyphs(glyphs []Glyph, face font.Face, text string) []Glyph {
- textM.Lock()
- defer textM.Unlock()
- fc := faceWithCacheFromFace(face)
- var pos fixed.Point26_6
- prevR := rune(-1)
- faceHeight := fc.Metrics().Height
- for _, r := range text {
- if prevR >= 0 {
- pos.X += fc.Kern(prevR, r)
- }
- if r == '\n' {
- pos.X = 0
- pos.Y += faceHeight
- prevR = rune(-1)
- continue
- }
- b, a, _ := fc.GlyphBounds(r)
- offset := fixed.Point26_6{
- X: (adjustOffsetGranularity(pos.X) + b.Min.X) & ((1 << 6) - 1),
- Y: b.Min.Y & ((1 << 6) - 1),
- }
- if img := getGlyphImage(fc, r, offset); img != nil {
- // Adjust the position to the integers.
- // The current glyph images assume that they are rendered on integer positions so far.
- glyphs = append(glyphs, Glyph{
- Rune: r,
- Image: img,
- X: fixed26_6ToFloat64(pos.X + b.Min.X - offset.X),
- Y: fixed26_6ToFloat64(pos.Y + b.Min.Y - offset.Y),
- })
- }
- pos.X += a
- prevR = r
- }
- return glyphs
- }
|