-
Notifications
You must be signed in to change notification settings - Fork 245
Typing text on the screen
In this part, we're going to learn how to draw text using various fonts and how to get text input from the user.
In order to draw any text, a font is required. You might have already noticed, that Pixel tries to be very compatible with standard Go packages, such as "image"
, "image/color"
, "time"
, and so on. Unfortunately, there's no font package in the Go standard library.
However, there's a semi-standard package called "golang.org/x/image/font"
. I must say, this package deserves to be considered standard, it's remarkably well designed and easy to use. It defines one important interface: font.Face. Anything implementing this interface is a font object. There are many implementations of this interface found, for example, in "golang.org/x/image/font/basicfont"
, "golang.org/x/image/font/inconsolata"
and "github.com/golang/freetype/truetype"
.
As you can see, Go community has already done a great deal of work regarding fonts. Pixel makes use of this work, thus supporting large variety of fonts out of the box. Let's see how we do it!
Pixel has a text, which we can use to draw text to any Target. Let's take a look and use it! As usual, we'll start off with the basic Pixel boilerplate:
package main
import (
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
for !win.Closed() {
win.Update()
}
}
func main() {
pixelgl.Run(run)
}
Now, we need to import the "github.com/faiface/pixel/text"
package:
import (
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"github.com/faiface/pixel/text"
)
Before we can draw any text, we need to create a font atlas. What is that? To achieve the maximum performance, it's best to pre-draw the characters we want to use to an off-screen picture and then draw the characters straight from that picture. Drawing directly from the font.Face
object would be too slow. This is exactly what Atlas is. It's a collection of pre-drawn characters, ready to use for drawing.
An Atlas
is created by the text.NewAtlas constructor. The constructor takes a font.Face
, that's obvious. Then is takes an arbitrary number of, so called "rune sets". A rune set is just a slice of runes. The Atlas
will contain all of the runes provided in all of the sets. This might be a bit confusing, so let's see an example:
atlas := text.NewAtlas(
basicfont.Face7x13,
[]rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'},
)
Here we create an Atlas
from the basicfont.Face7x13 font face, which contains all digits. So, with this atlas, we're able to draw digits. Simple? Let's take a look at another example:
atlas := text.NewAtlas(
basicfont.Face7x13,
[]rune{'a', 'b', 'c'},
[]rune{'A', 'B', 'C'},
)
Here we supply two rune sets instead of one. The atlas will contain both lower-case a
, b
, c
letters, as well as upper-case ones. Here's another example:
atlas := text.NewAtlas(basicfont.Face7x13, text.ASCII)
This time, we use text.ASCII, which is the set of all ASCII runes defined in the text
package. One more example:
atlas := text.NewAtlas(face, text.ASCII, text.RangeTable(unicode.Latin))
This time, we don't use the basicfont.Face7x13
face, instead we use a font contained in some face
variable, which could be an arbitrary truetype font for example (we'll see how to load truetype fonts in a minute). Now, our atlas contains not only ASCII characters, but also all Latin characters. Function text.RangeTable converts an arbitrary unicode.RangeTable to a set of runes. The standard unicode package contains a large variety of useful Unicode range tables.
Now that we understand all of this, let's go ahead and create an atlas!
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
basicAtlas := text.NewAtlas(basicfont.Face7x13, text.ASCII)
For now, we'll use the font defined in the basicfont package, because it's the most accessible one, as we can see.
Now, how do we actually draw a text? For that we have the Text type. You can think of the Text
type as a sprite containing text. Contrary to a sprite, we can dynamically write new text to a Text
object as we'll see. So, without further ado, let's create a `Text object using the text.New constructor.
basicAtlas := text.NewAtlas(basicfont.Face7x13, text.ASCII)
basicTxt := text.New(pixel.V(100, 500), basicAtlas)
The text.New
constructor takes two arguments: the orig
position and an atlas. The atlas is obvious. What is the "orig" position? It's the position where we want to start drawing our text. We chose pixel.V(100, 500)
, so the text will start at that position. So, let's write some text:
basicAtlas := text.NewAtlas(basicfont.Face7x13, text.ASCII)
basicTxt := text.New(pixel.V(100, 500), basicAtlas)
fmt.Fprintln(basicTxt, "Hello, text!")
fmt.Fprintln(basicTxt, "I support multiple lines!")
fmt.Fprintf(basicTxt, "And I'm an %s, yay!", "io.Writer")
And draw the result to the screen:
for !win.Closed() {
win.Clear(colornames.Black)
basicTxt.Draw(win, pixel.IM)
win.Update()
}
And heya, here we go!
That text is a bit small, let's scale it up!
for !win.Closed() {
win.Clear(colornames.Black)
basicTxt.Draw(win, pixel.IM.Scaled(basicTxt.Orig, 4))
win.Update()
}
Much better!
We've got something running, we see text on the screen, but we don't fully understand how it works. In the constructor, we were setting a mysterious orig
argument:
basicTxt := text.New(pixel.V(100, 500), basicAtlas)
If we a look at the documentation of the Text struct, we see that it actually has an Orig
field. If we check the value of this field, we find that it's equal to the value we passed in the constructor.
Before we fully explain what this field does, we need to take a look at another important field: Dot
. Dot
is simple: it's the position of the next character. When we write some string to the Text
object, Dot
will be automatically moved accordingly. Initially, Dot
is set to the same value as Orig
. As we write text, Dot
moves. We can also adjust it manually, if we so desire.
To understand Dot
better, try writing small chunks of text to the Text
object and print the value of Dot
after each chunk.
What happens when we write a new line? Easy, Dot
moves one line down and aligns with Orig
. This is the most important use of Orig
. Notice, that only Orig
's X coordinate is used to align after newline. In fact, Text
never really uses it's Y coordinate.
Try changing the value of Orig
after each newline.
Another important use of Orig
is this:
txt.Clear()
txt.Dot = txt.Orig
This is the reset. First, we remove all text written to txt
, then we reset it's Dot
to Orig
. This is useful when we want to fully change the content of a Text
object. Reseting the Dot
is necessary (if wanted), txt.Clear()
does not do it for us.
Aside from Orig
and Dot
, there are several other things we can adjust when writing text.