mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
parent
bf7acd54bb
commit
26744b46ff
@ -51,6 +51,9 @@ func runImageImportCheck(pass *analysis.Pass) (any, error) {
|
||||
if strings.HasSuffix(pkgPath, "_test") {
|
||||
return nil, nil
|
||||
}
|
||||
if pkgPath == "github.com/hajimehoshi/ebiten/v2/text/v2" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO: Remove this exception after v3 is released (#2336).
|
||||
if pkgPath == "github.com/hajimehoshi/ebiten/v2/ebitenutil" {
|
||||
|
@ -328,6 +328,7 @@ func (g *GoTextFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffse
|
||||
}
|
||||
|
||||
func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Image, int, int) {
|
||||
if glyph.bitmap != nil {
|
||||
if g.direction().isHorizontal() {
|
||||
origin.X = adjustGranularity(origin.X, g)
|
||||
origin.Y &^= ((1 << 6) - 1)
|
||||
@ -335,12 +336,20 @@ func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Im
|
||||
origin.X &^= ((1 << 6) - 1)
|
||||
origin.Y = adjustGranularity(origin.Y, g)
|
||||
}
|
||||
} else {
|
||||
origin.X &^= ((1 << 6) - 1)
|
||||
origin.Y &^= ((1 << 6) - 1)
|
||||
}
|
||||
|
||||
b := glyph.bounds
|
||||
subpixelOffset := fixed.Point26_6{
|
||||
|
||||
var subpixelOffset fixed.Point26_6
|
||||
if glyph.bitmap != nil {
|
||||
subpixelOffset = fixed.Point26_6{
|
||||
X: (origin.X + b.Min.X) & ((1 << 6) - 1),
|
||||
Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
|
||||
}
|
||||
}
|
||||
key := goTextGlyphImageCacheKey{
|
||||
gid: glyph.shapingGlyph.GlyphID,
|
||||
xoffset: subpixelOffset.X,
|
||||
@ -348,6 +357,9 @@ func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Im
|
||||
variations: g.ensureVariationsString(),
|
||||
}
|
||||
img := g.Source.getOrCreateGlyphImage(g, key, func() *ebiten.Image {
|
||||
if glyph.bitmap != nil {
|
||||
return ebiten.NewImageFromImage(glyph.bitmap)
|
||||
}
|
||||
return segmentsToImage(glyph.scaledSegments, subpixelOffset, b)
|
||||
})
|
||||
|
||||
|
@ -16,6 +16,9 @@ package text
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png" // As typesettings/font already imports image/png, it is fine to ignore side effects (#2336).
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
@ -26,6 +29,7 @@ import (
|
||||
"github.com/go-text/typesetting/opentype/loader"
|
||||
"github.com/go-text/typesetting/shaping"
|
||||
"golang.org/x/image/math/fixed"
|
||||
"golang.org/x/image/tiff"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
@ -46,6 +50,7 @@ type glyph struct {
|
||||
endIndex int
|
||||
scaledSegments []api.Segment
|
||||
bounds fixed.Rectangle26_6
|
||||
bitmap image.Image
|
||||
}
|
||||
|
||||
type goTextOutputCacheValue struct {
|
||||
@ -73,6 +78,9 @@ type GoTextFaceSource struct {
|
||||
|
||||
shaper shaping.HarfbuzzShaper
|
||||
|
||||
bitmapSizesResult []api.BitmapSize
|
||||
bitmapSizesOnce sync.Once
|
||||
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
@ -180,6 +188,17 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu
|
||||
|
||||
f := face.Source.f
|
||||
f.SetVariations(face.variations)
|
||||
f.XPpem = 0
|
||||
f.YPpem = 0
|
||||
var useBitmap bool
|
||||
for _, bs := range g.bitmapSizes() {
|
||||
if float64(bs.YPpem) == face.Size {
|
||||
f.XPpem = bs.XPpem
|
||||
f.YPpem = bs.YPpem
|
||||
useBitmap = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
runes := []rune(text)
|
||||
input := shaping.Input{
|
||||
@ -219,22 +238,63 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu
|
||||
indices = append(indices, len(text))
|
||||
|
||||
for _, gl := range out.Glyphs {
|
||||
gl := gl
|
||||
shapingGlyph := gl
|
||||
var segs []api.Segment
|
||||
switch data := g.f.GlyphData(gl.GlyphID).(type) {
|
||||
var bitmap image.Image
|
||||
switch data := g.f.GlyphData(shapingGlyph.GlyphID).(type) {
|
||||
case api.GlyphOutline:
|
||||
if out.Direction.IsSideways() {
|
||||
data.Sideways(fixed26_6ToFloat32(-gl.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem()))
|
||||
data.Sideways(fixed26_6ToFloat32(-shapingGlyph.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem()))
|
||||
}
|
||||
segs = data.Segments
|
||||
case api.GlyphSVG:
|
||||
segs = data.Outline.Segments
|
||||
case api.GlyphBitmap:
|
||||
if useBitmap {
|
||||
switch data.Format {
|
||||
case api.BlackAndWhite:
|
||||
img := image.NewAlpha(image.Rect(0, 0, data.Width, data.Height))
|
||||
for j := 0; j < data.Height; j++ {
|
||||
for i := 0; i < data.Width; i++ {
|
||||
idx := j*data.Width + i
|
||||
if data.Data[idx/8]&(1<<(7-idx%8)) != 0 {
|
||||
img.Pix[j*img.Stride+i] = 0xff
|
||||
}
|
||||
}
|
||||
}
|
||||
bitmap = img
|
||||
case api.PNG:
|
||||
img, err := png.Decode(bytes.NewReader(data.Data))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
bitmap = img
|
||||
case api.JPG:
|
||||
img, err := jpeg.Decode(bytes.NewReader(data.Data))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
bitmap = img
|
||||
case api.TIFF:
|
||||
img, err := tiff.Decode(bytes.NewReader(data.Data))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
bitmap = img
|
||||
}
|
||||
}
|
||||
|
||||
// Use outline segments in any cases for vector rendering.
|
||||
if data.Outline != nil {
|
||||
segs = data.Outline.Segments
|
||||
}
|
||||
}
|
||||
|
||||
gl := glyph{
|
||||
startIndex: indices[shapingGlyph.ClusterIndex],
|
||||
endIndex: indices[shapingGlyph.ClusterIndex+shapingGlyph.RuneCount],
|
||||
}
|
||||
|
||||
scaledSegs := make([]api.Segment, len(segs))
|
||||
scale := float32(g.scale(fixed26_6ToFloat64(out.Size)))
|
||||
for i, seg := range segs {
|
||||
@ -245,13 +305,15 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu
|
||||
}
|
||||
}
|
||||
|
||||
gs = append(gs, glyph{
|
||||
shapingGlyph: &gl,
|
||||
startIndex: indices[gl.ClusterIndex],
|
||||
endIndex: indices[gl.ClusterIndex+gl.RuneCount],
|
||||
scaledSegments: scaledSegs,
|
||||
bounds: segmentsToBounds(scaledSegs),
|
||||
})
|
||||
gl.shapingGlyph = &shapingGlyph
|
||||
gl.scaledSegments = scaledSegs
|
||||
gl.bounds = segmentsToBounds(scaledSegs)
|
||||
|
||||
if bitmap != nil {
|
||||
gl.bitmap = bitmap
|
||||
}
|
||||
|
||||
gs = append(gs, gl)
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,6 +354,13 @@ func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goT
|
||||
return g.glyphImageCache[goTextFace.Size].getOrCreate(goTextFace, key, create)
|
||||
}
|
||||
|
||||
func (g *GoTextFaceSource) bitmapSizes() []api.BitmapSize {
|
||||
g.bitmapSizesOnce.Do(func() {
|
||||
g.bitmapSizesResult = g.f.BitmapSizes()
|
||||
})
|
||||
return g.bitmapSizesResult
|
||||
}
|
||||
|
||||
type singleFontmap struct {
|
||||
face font.Face
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user