xgb-render: preliminary text rendering
I have finally got it working at all, now let's fix bounds etc.
This commit is contained in:
parent
215e3e8630
commit
30f2366f9a
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue