From 30f2366f9a2b13cdc2775c3a972d08f1297acdaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Thu, 23 Aug 2018 01:59:38 +0200 Subject: [PATCH] xgb-render: preliminary text rendering I have finally got it working at all, now let's fix bounds etc. --- prototypes/xgb-xrender.go | 193 +++++++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 2 deletions(-) diff --git a/prototypes/xgb-xrender.go b/prototypes/xgb-xrender.go index 3ae51ad..6e07b4e 100644 --- a/prototypes/xgb-xrender.go +++ b/prototypes/xgb-xrender.go @@ -1,9 +1,16 @@ package main import ( + "fmt" "github.com/BurntSushi/xgb" "github.com/BurntSushi/xgb/render" "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" "math/rand" ) @@ -11,6 +18,98 @@ import ( func F64ToFixed(f float64) render.Fixed { return render.Fixed(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() { X, err := xgb.NewConn() if err != nil { @@ -119,6 +218,79 @@ func main() { // ...or just scan through pformats.Formats and look for matches, which is // 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) if err != nil { log.Fatalln(err) @@ -133,9 +305,22 @@ func main() { 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 start, end uint32 recolor := func() { - start := rand.Uint32() & 0xffffff + start = rand.Uint32() & 0xffffff from = render.Color{ Red: 0x101 * uint16((start>>16)&0xff), Green: 0x101 * uint16((start>>8)&0xff), @@ -143,7 +328,7 @@ func main() { Alpha: 0xffff, } - end := rand.Uint32() & 0xffffff + end = rand.Uint32() & 0xffffff to = render.Color{ Red: 0x101 * uint16((end>>16)&0xff), Green: 0x101 * uint16((end>>8)&0xff), @@ -169,6 +354,10 @@ func main() { 0, 0, 0, 0, 50, 50, w-100, h-100) _ = 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 {