xgb-render: preliminary text rendering
I have finally got it working at all, now let's fix bounds etc.
This commit is contained in:
		@@ -1,9 +1,16 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"github.com/BurntSushi/xgb"
 | 
						"github.com/BurntSushi/xgb"
 | 
				
			||||||
	"github.com/BurntSushi/xgb/render"
 | 
						"github.com/BurntSushi/xgb/render"
 | 
				
			||||||
	"github.com/BurntSushi/xgb/xproto"
 | 
						"github.com/BurntSushi/xgb/xproto"
 | 
				
			||||||
 | 
						"github.com/golang/freetype"
 | 
				
			||||||
 | 
						"github.com/golang/freetype/truetype"
 | 
				
			||||||
 | 
						"golang.org/x/image/font"
 | 
				
			||||||
 | 
						"golang.org/x/image/font/gofont/goregular"
 | 
				
			||||||
 | 
						"golang.org/x/image/math/fixed"
 | 
				
			||||||
 | 
						"image"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -11,6 +18,98 @@ import (
 | 
				
			|||||||
func F64ToFixed(f float64) render.Fixed { return render.Fixed(f * 65536) }
 | 
					func F64ToFixed(f float64) render.Fixed { return render.Fixed(f * 65536) }
 | 
				
			||||||
func FixedToF64(f render.Fixed) float64 { return float64(f) / 65536 }
 | 
					func FixedToF64(f render.Fixed) float64 { return float64(f) / 65536 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func glyphListBytes(buf []byte, runes []rune, size int) int {
 | 
				
			||||||
 | 
						b := 0
 | 
				
			||||||
 | 
						for _, r := range runes {
 | 
				
			||||||
 | 
							switch size {
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								buf[b] = byte(r)
 | 
				
			||||||
 | 
								b += 1
 | 
				
			||||||
 | 
							case 2:
 | 
				
			||||||
 | 
								xgb.Put16(buf[b:], uint16(r))
 | 
				
			||||||
 | 
								b += 2
 | 
				
			||||||
 | 
							case 4:
 | 
				
			||||||
 | 
								xgb.Put32(buf[b:], uint32(r))
 | 
				
			||||||
 | 
								b += 4
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return xgb.Pad(b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// When the len is 255, a GLYPHABLE follows, otherwise a list of CARD8/16/32.
 | 
				
			||||||
 | 
					func glyphEltHeaderBytes(buf []byte, len byte, deltaX, deltaY int16) int {
 | 
				
			||||||
 | 
						b := 0
 | 
				
			||||||
 | 
						buf[b] = len
 | 
				
			||||||
 | 
						b += 4
 | 
				
			||||||
 | 
						xgb.Put16(buf[b:], uint16(deltaX))
 | 
				
			||||||
 | 
						b += 2
 | 
				
			||||||
 | 
						xgb.Put16(buf[b:], uint16(deltaY))
 | 
				
			||||||
 | 
						b += 2
 | 
				
			||||||
 | 
						return xgb.Pad(b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type xgbCookie interface{ Check() error }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: We actually need a higher-level function that also keeps track of
 | 
				
			||||||
 | 
					// and loads glyphs into the X server.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// compositeString makes an appropriate render.CompositeGlyphs request,
 | 
				
			||||||
 | 
					// assuming that glyphs equal Unicode codepoints.
 | 
				
			||||||
 | 
					func compositeString(c *xgb.Conn, op byte, src, dst render.Picture,
 | 
				
			||||||
 | 
						maskFormat render.Pictformat, glyphset render.Glyphset, srcX, srcY int16,
 | 
				
			||||||
 | 
						destX, destY int16, text string) xgbCookie {
 | 
				
			||||||
 | 
						runes := []rune(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var highest rune
 | 
				
			||||||
 | 
						for _, r := range runes {
 | 
				
			||||||
 | 
							if r > highest {
 | 
				
			||||||
 | 
								highest = r
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						size := 1
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case highest > 1<<16:
 | 
				
			||||||
 | 
							size = 4
 | 
				
			||||||
 | 
						case highest > 1<<8:
 | 
				
			||||||
 | 
							size = 2
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// They gave up on the XCB protocol API and we need to serialize explicitly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// To spare us from caring about the padding, use the largest number lesser
 | 
				
			||||||
 | 
						// than 255 that is divisible by 4 (for size 2 and 4 the requirements are
 | 
				
			||||||
 | 
						// less strict but this works in the general case).
 | 
				
			||||||
 | 
						const maxPerChunk = 252
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						buf := make([]byte, (len(runes)+maxPerChunk-1)/maxPerChunk*8+len(runes)*size)
 | 
				
			||||||
 | 
						b := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for len(runes) > maxPerChunk {
 | 
				
			||||||
 | 
							b += glyphEltHeaderBytes(buf[b:], maxPerChunk, 0, 0)
 | 
				
			||||||
 | 
							b += glyphListBytes(buf[b:], runes[:maxPerChunk], size)
 | 
				
			||||||
 | 
							runes = runes[maxPerChunk:]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(runes) > 0 {
 | 
				
			||||||
 | 
							b += glyphEltHeaderBytes(buf[b:], byte(len(runes)), destX, destY)
 | 
				
			||||||
 | 
							b += glyphListBytes(buf[b:], runes, size)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch size {
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return render.CompositeGlyphs8(c, op, src, dst, maskFormat, glyphset,
 | 
				
			||||||
 | 
								srcX, srcY, buf)
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							return render.CompositeGlyphs16(c, op, src, dst, maskFormat, glyphset,
 | 
				
			||||||
 | 
								srcX, srcY, buf)
 | 
				
			||||||
 | 
						case 4:
 | 
				
			||||||
 | 
							return render.CompositeGlyphs32(c, op, src, dst, maskFormat, glyphset,
 | 
				
			||||||
 | 
								srcX, srcY, buf)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	X, err := xgb.NewConn()
 | 
						X, err := xgb.NewConn()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -119,6 +218,79 @@ func main() {
 | 
				
			|||||||
	// ...or just scan through pformats.Formats and look for matches, which is
 | 
						// ...or just scan through pformats.Formats and look for matches, which is
 | 
				
			||||||
	// what XRenderFindStandardFormat in Xlib does as well as exp/shiny.
 | 
						// what XRenderFindStandardFormat in Xlib does as well as exp/shiny.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f, err := freetype.ParseFont(goregular.TTF)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c := freetype.NewContext()
 | 
				
			||||||
 | 
						c.SetDPI(96) // TODO: Take this from the screen or monitor.
 | 
				
			||||||
 | 
						c.SetFont(f)
 | 
				
			||||||
 | 
						c.SetFontSize(9)
 | 
				
			||||||
 | 
						c.SetSrc(image.White)
 | 
				
			||||||
 | 
						c.SetHinting(font.HintingFull)
 | 
				
			||||||
 | 
						// TODO: Seems like we want to use NRGBA. Or RGBA if the A is always 1.
 | 
				
			||||||
 | 
						// Or implement our own image.Image for direct gamma-corrected RGB!
 | 
				
			||||||
 | 
						nrgb := image.NewRGBA(image.Rect(0, 0, 36, 36))
 | 
				
			||||||
 | 
						c.SetClip(nrgb.Bounds())
 | 
				
			||||||
 | 
						c.SetDst(nrgb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bounds := f.Bounds(c.PointToFixed(9 /* FIXME: Duplication. */))
 | 
				
			||||||
 | 
						log.Println("+%v", bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: Duplication.
 | 
				
			||||||
 | 
						opts := truetype.Options{
 | 
				
			||||||
 | 
							Size: 9,
 | 
				
			||||||
 | 
							DPI:  96,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Seems this satisfies the sfnt interface, DrawString just adds
 | 
				
			||||||
 | 
						// kerning and DrawMask on top.
 | 
				
			||||||
 | 
						face := truetype.NewFace(f, &opts)
 | 
				
			||||||
 | 
						_ = face
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Figure out a way to load glyphs into XRender.
 | 
				
			||||||
 | 
						var rgbFormat render.Pictformat
 | 
				
			||||||
 | 
						for _, pf := range pformats.Formats {
 | 
				
			||||||
 | 
							// Hopefully. Might want to check ARGB/BGRA.
 | 
				
			||||||
 | 
							if pf.Depth == 32 && pf.Direct.AlphaMask != 0 {
 | 
				
			||||||
 | 
								rgbFormat = pf.Id
 | 
				
			||||||
 | 
								log.Printf("%+v\n", pf)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gsid, err := render.NewGlyphsetId(X)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// NOTE: A depth of 24 will not work, the server always rejects it.
 | 
				
			||||||
 | 
						_ = render.CreateGlyphSet(X, gsid, rgbFormat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for r := rune(32); r < 128; r++ {
 | 
				
			||||||
 | 
							for i := 0; i < len(nrgb.Pix); i++ {
 | 
				
			||||||
 | 
								nrgb.Pix[i] = 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							advance, err := c.DrawString(string(r), fixed.P(18, 18))
 | 
				
			||||||
 | 
							_, _ = advance, err
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("skip")
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_ = render.AddGlyphs(X, gsid, 1, []uint32{uint32(r)},
 | 
				
			||||||
 | 
								[]render.Glyphinfo{{
 | 
				
			||||||
 | 
									Width:  uint16(36),
 | 
				
			||||||
 | 
									Height: uint16(36),
 | 
				
			||||||
 | 
									X:      int16(18),
 | 
				
			||||||
 | 
									Y:      int16(18),
 | 
				
			||||||
 | 
									XOff:   int16(18),
 | 
				
			||||||
 | 
									YOff:   0,
 | 
				
			||||||
 | 
								}}, []byte(nrgb.Pix))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pid, err := render.NewPictureId(X)
 | 
						pid, err := render.NewPictureId(X)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalln(err)
 | 
							log.Fatalln(err)
 | 
				
			||||||
@@ -133,9 +305,22 @@ func main() {
 | 
				
			|||||||
		log.Fatalln(err)
 | 
							log.Fatalln(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						whiteid, err := render.NewPictureId(X)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ = render.CreateSolidFill(X, whiteid, render.Color{
 | 
				
			||||||
 | 
							Red:   0xffff,
 | 
				
			||||||
 | 
							Green: 0xffff,
 | 
				
			||||||
 | 
							Blue:  0xffff,
 | 
				
			||||||
 | 
							Alpha: 0xffff,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var from, to render.Color
 | 
						var from, to render.Color
 | 
				
			||||||
 | 
						var start, end uint32
 | 
				
			||||||
	recolor := func() {
 | 
						recolor := func() {
 | 
				
			||||||
		start := rand.Uint32() & 0xffffff
 | 
							start = rand.Uint32() & 0xffffff
 | 
				
			||||||
		from = render.Color{
 | 
							from = render.Color{
 | 
				
			||||||
			Red:   0x101 * uint16((start>>16)&0xff),
 | 
								Red:   0x101 * uint16((start>>16)&0xff),
 | 
				
			||||||
			Green: 0x101 * uint16((start>>8)&0xff),
 | 
								Green: 0x101 * uint16((start>>8)&0xff),
 | 
				
			||||||
@@ -143,7 +328,7 @@ func main() {
 | 
				
			|||||||
			Alpha: 0xffff,
 | 
								Alpha: 0xffff,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		end := rand.Uint32() & 0xffffff
 | 
							end = rand.Uint32() & 0xffffff
 | 
				
			||||||
		to = render.Color{
 | 
							to = render.Color{
 | 
				
			||||||
			Red:   0x101 * uint16((end>>16)&0xff),
 | 
								Red:   0x101 * uint16((end>>16)&0xff),
 | 
				
			||||||
			Green: 0x101 * uint16((end>>8)&0xff),
 | 
								Green: 0x101 * uint16((end>>8)&0xff),
 | 
				
			||||||
@@ -169,6 +354,10 @@ func main() {
 | 
				
			|||||||
			0, 0, 0, 0, 50, 50, w-100, h-100)
 | 
								0, 0, 0, 0, 50, 50, w-100, h-100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		_ = render.FreePicture(X, gid)
 | 
							_ = render.FreePicture(X, gid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							_ = compositeString(X, render.PictOpOver, whiteid, pid,
 | 
				
			||||||
 | 
								0 /* TODO: mask Pictureformat? */, gsid, 0, 0, 100, 100,
 | 
				
			||||||
 | 
								fmt.Sprintf("%#06x - %#06x", start, end))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user