aboutsummaryrefslogtreecommitdiff
path: root/prototypes
diff options
context:
space:
mode:
authorPřemysl Janouch <p@janouch.name>2018-08-23 01:59:38 +0200
committerPřemysl Janouch <p@janouch.name>2018-09-02 18:25:34 +0200
commit30f2366f9a2b13cdc2775c3a972d08f1297acdaa (patch)
tree86f399900b28e64b325ae55475cc872e8381b5e0 /prototypes
parent215e3e863003d8c62600507b5e302a7f4da2de55 (diff)
downloadhaven-30f2366f9a2b13cdc2775c3a972d08f1297acdaa.tar.gz
haven-30f2366f9a2b13cdc2775c3a972d08f1297acdaa.tar.xz
haven-30f2366f9a2b13cdc2775c3a972d08f1297acdaa.zip
xgb-render: preliminary text rendering
I have finally got it working at all, now let's fix bounds etc.
Diffstat (limited to 'prototypes')
-rw-r--r--prototypes/xgb-xrender.go193
1 files 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 {