From 5b63ed60f465c0768b756950f3f590ab4b06d6d5 Mon Sep 17 00:00:00 2001 From: 12private12 <120792mon@gmail.com> Date: Tue, 12 Aug 2025 23:15:16 +0800 Subject: [PATCH] Delete text directory --- text/atlas.go | 247 ------------------------------- text/atlas_test.go | 29 ---- text/doc.go | 2 - text/text.go | 359 --------------------------------------------- text/text_test.go | 87 ----------- 5 files changed, 724 deletions(-) delete mode 100644 text/atlas.go delete mode 100644 text/atlas_test.go delete mode 100644 text/doc.go delete mode 100644 text/text.go delete mode 100644 text/text_test.go diff --git a/text/atlas.go b/text/atlas.go deleted file mode 100644 index 84470db..0000000 --- a/text/atlas.go +++ /dev/null @@ -1,247 +0,0 @@ -package text - -import ( - "image" - "image/draw" - "sort" - "unicode" - - "github.com/faiface/pixel" - "golang.org/x/image/font" - "golang.org/x/image/math/fixed" -) - -// Atlas7x13 is an Atlas using basicfont.Face7x13 with the ASCII rune set -var Atlas7x13 *Atlas - -// Glyph describes one glyph in an Atlas. -type Glyph struct { - Dot pixel.Vec - Frame pixel.Rect - Advance float64 -} - -// Atlas is a set of pre-drawn glyphs of a fixed set of runes. This allows for efficient text drawing. -type Atlas struct { - face font.Face - pic pixel.Picture - mapping map[rune]Glyph - ascent float64 - descent float64 - lineHeight float64 -} - -// NewAtlas creates a new Atlas containing glyphs of the union of the given sets of runes (plus -// unicode.ReplacementChar) from the given font face. -// -// Creating an Atlas is rather expensive, do not create a new Atlas each frame. -// -// Do not destroy or close the font.Face after creating the Atlas. Atlas still uses it. -func NewAtlas(face font.Face, runeSets ...[]rune) *Atlas { - seen := make(map[rune]bool) - runes := []rune{unicode.ReplacementChar} - for _, set := range runeSets { - for _, r := range set { - if !seen[r] { - runes = append(runes, r) - seen[r] = true - } - } - } - - fixedMapping, fixedBounds := makeSquareMapping(face, runes, fixed.I(2)) - - atlasImg := image.NewRGBA(image.Rect( - fixedBounds.Min.X.Floor(), - fixedBounds.Min.Y.Floor(), - fixedBounds.Max.X.Ceil(), - fixedBounds.Max.Y.Ceil(), - )) - - for r, fg := range fixedMapping { - if dr, mask, maskp, _, ok := face.Glyph(fg.dot, r); ok { - draw.Draw(atlasImg, dr, mask, maskp, draw.Src) - } - } - - bounds := pixel.R( - i2f(fixedBounds.Min.X), - i2f(fixedBounds.Min.Y), - i2f(fixedBounds.Max.X), - i2f(fixedBounds.Max.Y), - ) - - mapping := make(map[rune]Glyph) - for r, fg := range fixedMapping { - mapping[r] = Glyph{ - Dot: pixel.V( - i2f(fg.dot.X), - bounds.Max.Y-(i2f(fg.dot.Y)-bounds.Min.Y), - ), - Frame: pixel.R( - i2f(fg.frame.Min.X), - bounds.Max.Y-(i2f(fg.frame.Min.Y)-bounds.Min.Y), - i2f(fg.frame.Max.X), - bounds.Max.Y-(i2f(fg.frame.Max.Y)-bounds.Min.Y), - ).Norm(), - Advance: i2f(fg.advance), - } - } - - return &Atlas{ - face: face, - pic: pixel.PictureDataFromImage(atlasImg), - mapping: mapping, - ascent: i2f(face.Metrics().Ascent), - descent: i2f(face.Metrics().Descent), - lineHeight: i2f(face.Metrics().Height), - } -} - -// Picture returns the underlying Picture containing an arrangement of all the glyphs contained -// within the Atlas. -func (a *Atlas) Picture() pixel.Picture { - return a.pic -} - -// Contains reports wheter r in contained within the Atlas. -func (a *Atlas) Contains(r rune) bool { - _, ok := a.mapping[r] - return ok -} - -// Glyph returns the description of r within the Atlas. -func (a *Atlas) Glyph(r rune) Glyph { - return a.mapping[r] -} - -// Kern returns the kerning distance between runes r0 and r1. Positive distance means that the -// glyphs should be further apart. -func (a *Atlas) Kern(r0, r1 rune) float64 { - return i2f(a.face.Kern(r0, r1)) -} - -// Ascent returns the distance from the top of the line to the baseline. -func (a *Atlas) Ascent() float64 { - return a.ascent -} - -// Descent returns the distance from the baseline to the bottom of the line. -func (a *Atlas) Descent() float64 { - return a.descent -} - -// LineHeight returns the recommended vertical distance between two lines of text. -func (a *Atlas) LineHeight() float64 { - return a.lineHeight -} - -// DrawRune returns parameters necessary for drawing a rune glyph. -// -// Rect is a rectangle where the glyph should be positioned. Frame is the glyph frame inside the -// Atlas's Picture. NewDot is the new position of the dot. -func (a *Atlas) DrawRune(prevR, r rune, dot pixel.Vec) (rect, frame, bounds pixel.Rect, newDot pixel.Vec) { - if !a.Contains(r) { - r = unicode.ReplacementChar - } - if !a.Contains(unicode.ReplacementChar) { - return pixel.Rect{}, pixel.Rect{}, pixel.Rect{}, dot - } - if !a.Contains(prevR) { - prevR = unicode.ReplacementChar - } - - if prevR >= 0 { - dot.X += a.Kern(prevR, r) - } - - glyph := a.Glyph(r) - - rect = glyph.Frame.Moved(dot.Sub(glyph.Dot)) - bounds = rect - - if bounds.W()*bounds.H() != 0 { - bounds = pixel.R( - bounds.Min.X, - dot.Y-a.Descent(), - bounds.Max.X, - dot.Y+a.Ascent(), - ) - } - - dot.X += glyph.Advance - - return rect, glyph.Frame, bounds, dot -} - -type fixedGlyph struct { - dot fixed.Point26_6 - frame fixed.Rectangle26_6 - advance fixed.Int26_6 -} - -// makeSquareMapping finds an optimal glyph arrangement of the given runes, so that their common -// bounding box is as square as possible. -func makeSquareMapping(face font.Face, runes []rune, padding fixed.Int26_6) (map[rune]fixedGlyph, fixed.Rectangle26_6) { - width := sort.Search(int(fixed.I(1024*1024)), func(i int) bool { - width := fixed.Int26_6(i) - _, bounds := makeMapping(face, runes, padding, width) - return bounds.Max.X-bounds.Min.X >= bounds.Max.Y-bounds.Min.Y - }) - return makeMapping(face, runes, padding, fixed.Int26_6(width)) -} - -// makeMapping arranges glyphs of the given runes into rows in such a way, that no glyph is located -// fully to the right of the specified width. Specifically, it places glyphs in a row one by one and -// once it reaches the specified width, it starts a new row. -func makeMapping(face font.Face, runes []rune, padding, width fixed.Int26_6) (map[rune]fixedGlyph, fixed.Rectangle26_6) { - mapping := make(map[rune]fixedGlyph) - bounds := fixed.Rectangle26_6{} - - dot := fixed.P(0, 0) - - for _, r := range runes { - b, advance, ok := face.GlyphBounds(r) - if !ok { - continue - } - - // this is important for drawing, artifacts arise otherwise - frame := fixed.Rectangle26_6{ - Min: fixed.P(b.Min.X.Floor(), b.Min.Y.Floor()), - Max: fixed.P(b.Max.X.Ceil(), b.Max.Y.Ceil()), - } - - dot.X -= frame.Min.X - frame = frame.Add(dot) - - mapping[r] = fixedGlyph{ - dot: dot, - frame: frame, - advance: advance, - } - bounds = bounds.Union(frame) - - dot.X = frame.Max.X - - // padding + align to integer - dot.X += padding - dot.X = fixed.I(dot.X.Ceil()) - - // width exceeded, new row - if frame.Max.X >= width { - dot.X = 0 - dot.Y += face.Metrics().Ascent + face.Metrics().Descent - - // padding + align to integer - dot.Y += padding - dot.Y = fixed.I(dot.Y.Ceil()) - } - } - - return mapping, bounds -} - -func i2f(i fixed.Int26_6) float64 { - return float64(i) / (1 << 6) -} diff --git a/text/atlas_test.go b/text/atlas_test.go deleted file mode 100644 index fdb69b0..0000000 --- a/text/atlas_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package text_test - -import ( - "testing" - - "github.com/faiface/pixel/text" - "golang.org/x/image/font/inconsolata" -) - -func TestAtlas7x13(t *testing.T) { - if text.Atlas7x13 == nil { - t.Fatalf("Atlas7x13 is nil") - } - - for _, tt := range []struct { - runes []rune - want bool - }{{text.ASCII, true}, {[]rune("ÅÄÖ"), false}} { - for _, r := range tt.runes { - if got := text.Atlas7x13.Contains(r); got != tt.want { - t.Fatalf("Atlas7x13.Contains('%s') = %v, want %v", string(r), got, tt.want) - } - } - } -} - -func TestAtlasInconsolata(t *testing.T) { - text.NewAtlas(inconsolata.Regular8x16, text.ASCII) -} diff --git a/text/doc.go b/text/doc.go deleted file mode 100644 index 2a1c258..0000000 --- a/text/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package text implements efficient text drawing for the Pixel library. -package text diff --git a/text/text.go b/text/text.go deleted file mode 100644 index 8e047a9..0000000 --- a/text/text.go +++ /dev/null @@ -1,359 +0,0 @@ -package text - -import ( - "image/color" - "math" - "unicode" - "unicode/utf8" - - "github.com/faiface/pixel" - "golang.org/x/image/font/basicfont" -) - -// ASCII is a set of all ASCII runes. These runes are codepoints from 32 to 127 inclusive. -var ASCII []rune - -func init() { - ASCII = make([]rune, unicode.MaxASCII-32) - for i := range ASCII { - ASCII[i] = rune(32 + i) - } - Atlas7x13 = NewAtlas(basicfont.Face7x13, ASCII) -} - -// RangeTable takes a *unicode.RangeTable and generates a set of runes contained within that -// RangeTable. -func RangeTable(table *unicode.RangeTable) []rune { - var runes []rune - for _, rng := range table.R16 { - for r := rng.Lo; r <= rng.Hi; r += rng.Stride { - runes = append(runes, rune(r)) - } - } - for _, rng := range table.R32 { - for r := rng.Lo; r <= rng.Hi; r += rng.Stride { - runes = append(runes, rune(r)) - } - } - return runes -} - -// Text allows for effiecient and convenient text drawing. -// -// To create a Text object, use the New constructor: -// txt := text.New(pixel.ZV, text.NewAtlas(face, text.ASCII)) -// -// As suggested by the constructor, a Text object is always associated with one font face and a -// fixed set of runes. For example, the Text we created above can draw text using the font face -// contained in the face variable and is capable of drawing ASCII characters. -// -// Here we create a Text object which can draw ASCII and Katakana characters: -// txt := text.New(0, text.NewAtlas(face, text.ASCII, text.RangeTable(unicode.Katakana))) -// -// Similarly to IMDraw, Text functions as a buffer. It implements io.Writer interface, so writing -// text to it is really simple: -// fmt.Print(txt, "Hello, world!") -// -// Newlines, tabs and carriage returns are supported. -// -// Finally, if we want the written text to show up on some other Target, we can draw it: -// txt.Draw(target) -// -// Text exports two important fields: Orig and Dot. Dot is the position where the next character -// will be written. Dot is automatically moved when writing to a Text object, but you can also -// manipulate it manually. Orig specifies the text origin, usually the top-left dot position. Dot is -// always aligned to Orig when writing newlines. The Clear method resets the Dot to Orig. -type Text struct { - // Orig specifies the text origin, usually the top-left dot position. Dot is always aligned - // to Orig when writing newlines. - Orig pixel.Vec - - // Dot is the position where the next character will be written. Dot is automatically moved - // when writing to a Text object, but you can also manipulate it manually - Dot pixel.Vec - - // Color is the color of the text that is to be written. Defaults to white. - Color color.Color - - // LineHeight is the vertical distance between two lines of text. - // - // Example: - // txt.LineHeight = 1.5 * txt.Atlas().LineHeight() - LineHeight float64 - - // TabWidth is the horizontal tab width. Tab characters will align to the multiples of this - // width. - // - // Example: - // txt.TabWidth = 8 * txt.Atlas().Glyph(' ').Advance - TabWidth float64 - - atlas *Atlas - - buf []byte - prevR rune - bounds pixel.Rect - glyph pixel.TrianglesData - tris pixel.TrianglesData - - mat pixel.Matrix - col pixel.RGBA - trans pixel.TrianglesData - transD pixel.Drawer - dirty bool - anchor pixel.Anchor -} - -// New creates a new Text capable of drawing runes contained in the provided Atlas. Orig and Dot -// will be initially set to orig. -// -// Here we create a Text capable of drawing ASCII characters using the Go Regular font. -// ttf, err := truetype.Parse(goregular.TTF) -// if err != nil { -// panic(err) -// } -// face := truetype.NewFace(ttf, &truetype.Options{ -// Size: 14, -// }) -// txt := text.New(orig, text.NewAtlas(face, text.ASCII)) -func New(orig pixel.Vec, atlas *Atlas) *Text { - txt := &Text{ - Orig: orig, - Dot: orig, - Color: pixel.Alpha(1), - LineHeight: atlas.LineHeight(), - TabWidth: atlas.Glyph(' ').Advance * 4, - atlas: atlas, - mat: pixel.IM, - col: pixel.Alpha(1), - } - - txt.glyph.SetLen(6) - for i := range txt.glyph { - txt.glyph[i].Color = pixel.Alpha(1) - txt.glyph[i].Intensity = 1 - } - - txt.transD.Picture = txt.atlas.pic - txt.transD.Triangles = &txt.trans - txt.transD.Cached = true - - txt.Clear() - - return txt -} - -// Atlas returns the underlying Text's Atlas containing all of the pre-drawn glyphs. The Atlas is -// also useful for getting values such as the recommended line height. -func (txt *Text) Atlas() *Atlas { - return txt.atlas -} - -// Bounds returns the bounding box of the text currently written to the Text excluding whitespace. -// -// If the Text is empty, a zero rectangle is returned. -func (txt *Text) Bounds() pixel.Rect { - return txt.bounds -} - -// BoundsOf returns the bounding box of s if it was to be written to the Text right now. -func (txt *Text) BoundsOf(s string) pixel.Rect { - dot := txt.Dot - prevR := txt.prevR - bounds := pixel.Rect{} - - for _, r := range s { - var control bool - dot, control = txt.controlRune(r, dot) - if control { - continue - } - - var b pixel.Rect - _, _, b, dot = txt.Atlas().DrawRune(prevR, r, dot) - - if bounds.W()*bounds.H() == 0 { - bounds = b - } else { - bounds = bounds.Union(b) - } - - prevR = r - } - - return bounds -} - -// AlignedTo returns the text moved by the given anchor. -func (txt *Text) AlignedTo(anchor pixel.Anchor) *Text { - txt.anchor = anchor - return txt -} - -// Clear removes all written text from the Text. The Dot field is reset to Orig. -func (txt *Text) Clear() { - txt.prevR = -1 - txt.bounds = pixel.Rect{} - txt.tris.SetLen(0) - txt.dirty = true - txt.Dot = txt.Orig -} - -// Write writes a slice of bytes to the Text. This method never fails, always returns len(p), nil. -func (txt *Text) Write(p []byte) (n int, err error) { - txt.buf = append(txt.buf, p...) - txt.drawBuf() - return len(p), nil -} - -// WriteString writes a string to the Text. This method never fails, always returns len(s), nil. -func (txt *Text) WriteString(s string) (n int, err error) { - txt.buf = append(txt.buf, s...) - txt.drawBuf() - return len(s), nil -} - -// WriteByte writes a byte to the Text. This method never fails, always returns nil. -// -// Writing a multi-byte rune byte-by-byte is perfectly supported. -func (txt *Text) WriteByte(c byte) error { - txt.buf = append(txt.buf, c) - txt.drawBuf() - return nil -} - -// WriteRune writes a rune to the Text. This method never fails, always returns utf8.RuneLen(r), nil. -func (txt *Text) WriteRune(r rune) (n int, err error) { - var b [4]byte - n = utf8.EncodeRune(b[:], r) - txt.buf = append(txt.buf, b[:n]...) - txt.drawBuf() - return n, nil -} - -// Draw draws all text written to the Text to the provided Target. The text is transformed by the -// provided Matrix. -// -// This method is equivalent to calling DrawColorMask with nil color mask. -// -// If there's a lot of text written to the Text, changing a matrix or a color mask often might hurt -// performance. Consider using your Target's SetMatrix or SetColorMask methods if available. -func (txt *Text) Draw(t pixel.Target, matrix pixel.Matrix) { - txt.DrawColorMask(t, matrix, nil) -} - -// DrawColorMask draws all text written to the Text to the provided Target. The text is transformed -// by the provided Matrix and masked by the provided color mask. -// -// If there's a lot of text written to the Text, changing a matrix or a color mask often might hurt -// performance. Consider using your Target's SetMatrix or SetColorMask methods if available. -func (txt *Text) DrawColorMask(t pixel.Target, matrix pixel.Matrix, mask color.Color) { - if matrix != txt.mat { - txt.mat = matrix - txt.dirty = true - } - - offset := txt.Orig.Sub(txt.Bounds().Max.Add(txt.Bounds().AnchorPos(txt.anchor.Opposite()))) - txt.mat = pixel.IM.Moved(offset).Chained(txt.mat) - - if mask == nil { - mask = pixel.Alpha(1) - } - rgba := pixel.ToRGBA(mask) - if rgba != txt.col { - txt.col = rgba - txt.dirty = true - } - - if txt.dirty { - txt.trans.SetLen(txt.tris.Len()) - txt.trans.Update(&txt.tris) - - for i := range txt.trans { - txt.trans[i].Position = txt.mat.Project(txt.trans[i].Position) - txt.trans[i].Color = txt.trans[i].Color.Mul(txt.col) - } - - txt.transD.Dirty() - txt.dirty = false - } - - txt.transD.Draw(t) -} - -// controlRune checks if r is a control rune (newline, tab, ...). If it is, a new dot position and -// true is returned. If r is not a control rune, the original dot and false is returned. -func (txt *Text) controlRune(r rune, dot pixel.Vec) (newDot pixel.Vec, control bool) { - switch r { - case '\n': - dot.X = txt.Orig.X - dot.Y -= txt.LineHeight - case '\r': - dot.X = txt.Orig.X - case '\t': - rem := math.Mod(dot.X-txt.Orig.X, txt.TabWidth) - rem = math.Mod(rem, rem+txt.TabWidth) - if rem == 0 { - rem = txt.TabWidth - } - dot.X += rem - default: - return dot, false - } - return dot, true -} - -func (txt *Text) drawBuf() { - if !utf8.FullRune(txt.buf) { - return - } - - rgba := pixel.ToRGBA(txt.Color) - for i := range txt.glyph { - txt.glyph[i].Color = rgba - } - - for utf8.FullRune(txt.buf) { - r, size := utf8.DecodeRune(txt.buf) - txt.buf = txt.buf[size:] - - var control bool - txt.Dot, control = txt.controlRune(r, txt.Dot) - if control { - continue - } - - var rect, frame, bounds pixel.Rect - rect, frame, bounds, txt.Dot = txt.Atlas().DrawRune(txt.prevR, r, txt.Dot) - - txt.prevR = r - - rv := [...]pixel.Vec{ - {X: rect.Min.X, Y: rect.Min.Y}, - {X: rect.Max.X, Y: rect.Min.Y}, - {X: rect.Max.X, Y: rect.Max.Y}, - {X: rect.Min.X, Y: rect.Max.Y}, - } - - fv := [...]pixel.Vec{ - {X: frame.Min.X, Y: frame.Min.Y}, - {X: frame.Max.X, Y: frame.Min.Y}, - {X: frame.Max.X, Y: frame.Max.Y}, - {X: frame.Min.X, Y: frame.Max.Y}, - } - - for i, j := range [...]int{0, 1, 2, 0, 2, 3} { - txt.glyph[i].Position = rv[j] - txt.glyph[i].Picture = fv[j] - } - - txt.tris = append(txt.tris, txt.glyph...) - txt.dirty = true - - if txt.bounds.W()*txt.bounds.H() == 0 { - txt.bounds = bounds - } else { - txt.bounds = txt.bounds.Union(bounds) - } - } -} diff --git a/text/text_test.go b/text/text_test.go deleted file mode 100644 index a5f30af..0000000 --- a/text/text_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package text_test - -import ( - "fmt" - "math/rand" - "testing" - "unicode" - - "golang.org/x/image/font/basicfont" - "golang.org/x/image/font/gofont/goregular" - - "github.com/faiface/pixel" - "github.com/faiface/pixel/text" - "github.com/golang/freetype/truetype" -) - -func TestClear(t *testing.T) { - txt := text.New(pixel.ZV, text.Atlas7x13) - - if got, want := txt.Dot, pixel.ZV; !eqVectors(got, want) { - t.Fatalf("txt.Dot = %v, want %v", got, want) - } - - fmt.Fprint(txt, "Test\nClear") - - if got, want := txt.Dot, pixel.V(35, -13); !eqVectors(got, want) { - t.Fatalf("txt.Dot = %v, want %v", got, want) - } - - txt.Clear() - - if got, want := txt.Dot, pixel.ZV; !eqVectors(got, want) { - t.Fatalf("txt.Dot = %v, want %v", got, want) - } -} - -func BenchmarkNewAtlas(b *testing.B) { - runeSets := []struct { - name string - set []rune - }{ - {"ASCII", text.ASCII}, - {"Latin", text.RangeTable(unicode.Latin)}, - } - - ttf, _ := truetype.Parse(goregular.TTF) - face := truetype.NewFace(ttf, &truetype.Options{ - Size: 16, - GlyphCacheEntries: 1, - }) - - for _, runeSet := range runeSets { - b.Run(runeSet.name, func(b *testing.B) { - for i := 0; i < b.N; i++ { - _ = text.NewAtlas(face, runeSet.set) - } - }) - } -} - -func BenchmarkTextWrite(b *testing.B) { - runeSet := text.ASCII - atlas := text.NewAtlas(basicfont.Face7x13, runeSet) - - lengths := []int{1, 10, 100, 1000} - chunks := make([][]byte, len(lengths)) - for i := range chunks { - chunk := make([]rune, lengths[i]) - for j := range chunk { - chunk[j] = runeSet[rand.Intn(len(runeSet))] - } - chunks[i] = []byte(string(chunk)) - } - - for _, chunk := range chunks { - b.Run(fmt.Sprintf("%d", len(chunk)), func(b *testing.B) { - txt := text.New(pixel.ZV, atlas) - for i := 0; i < b.N; i++ { - _, _ = txt.Write(chunk) - } - }) - } -} - -func eqVectors(a, b pixel.Vec) bool { - return (a.X == b.X && a.Y == b.Y) -}