internal/task: switch to opentype for TTF font rendering

When working on CL 358897, I forgot that there's already an SFNT font
parser and glyph rasterizer available in the x/image module. Using it
produces an equivalent final image, and allows x/build to depend on 1
less module.

Also add a regression test for drawTerminal.

Updates golang/go#47403.

Change-Id: I77166a2180a7db9c81a30f77b12bb20dcd21bd8b
Reviewed-on: https://go-review.googlesource.com/c/build/+/388834
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/go.mod b/go.mod
index c4739ab..4a22407 100644
--- a/go.mod
+++ b/go.mod
@@ -19,7 +19,6 @@
 	github.com/esimov/stackblur-go v1.0.1
 	github.com/gliderlabs/ssh v0.3.3
 	github.com/golang-migrate/migrate/v4 v4.15.0-beta.3
-	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
 	github.com/golang/protobuf v1.5.2
 	github.com/google/go-cmp v0.5.6
 	github.com/google/go-github v17.0.0+incompatible
diff --git a/go.sum b/go.sum
index ad1e3a0..085a181 100644
--- a/go.sum
+++ b/go.sum
@@ -272,8 +272,6 @@
 github.com/golang-migrate/migrate/v4 v4.15.0-beta.3 h1:tqLMcxs6gB6+b2Jhwao4s2sNMSKku+0rjtBtKucKWkg=
 github.com/golang-migrate/migrate/v4 v4.15.0-beta.3/go.mod h1:g9qbiDvB47WyrRnNu2t2gMZFNHKnatsYRxsGZbCi4EM=
 github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
diff --git a/internal/task/testdata/terminal.png b/internal/task/testdata/terminal.png
new file mode 100644
index 0000000..33dda4c
--- /dev/null
+++ b/internal/task/testdata/terminal.png
Binary files differ
diff --git a/internal/task/tweet.go b/internal/task/tweet.go
index 304a161..39c7a92 100644
--- a/internal/task/tweet.go
+++ b/internal/task/tweet.go
@@ -25,11 +25,11 @@
 
 	"github.com/dghubble/oauth1"
 	"github.com/esimov/stackblur-go"
-	"github.com/golang/freetype/truetype"
 	"golang.org/x/build/internal/secret"
 	"golang.org/x/build/internal/workflow"
 	"golang.org/x/image/font"
 	"golang.org/x/image/font/gofont/gomono"
+	"golang.org/x/image/font/opentype"
 	"golang.org/x/image/math/fixed"
 )
 
@@ -496,7 +496,7 @@
 // with the given text displayed.
 func drawTerminal(text string) (image.Image, error) {
 	// Load font from TTF data.
-	f, err := truetype.Parse(gomono.TTF)
+	f, err := opentype.Parse(gomono.TTF)
 	if err != nil {
 		return nil, err
 	}
@@ -521,7 +521,11 @@
 		roundedRect(image.Rect(50, 80, width-50, height-80), 10), image.Point{}, draw.Over)
 
 	// Text.
-	d := font.Drawer{Dst: m, Src: image.White, Face: truetype.NewFace(f, &truetype.Options{Size: 24})}
+	face, err := opentype.NewFace(f, &opentype.FaceOptions{Size: 24, DPI: 72})
+	if err != nil {
+		return nil, err
+	}
+	d := font.Drawer{Dst: m, Src: image.White, Face: face}
 	const lineHeight = 32
 	for n, line := range strings.Split(text, "\n") {
 		d.Dot = fixed.P(80, 135+n*lineHeight)
diff --git a/internal/task/tweet_test.go b/internal/task/tweet_test.go
index 0948e69..bdd5716 100644
--- a/internal/task/tweet_test.go
+++ b/internal/task/tweet_test.go
@@ -7,10 +7,15 @@
 import (
 	"bytes"
 	"context"
+	"flag"
 	"fmt"
+	"image"
+	"image/png"
 	"io"
 	"net/http"
 	"net/http/httptest"
+	"os"
+	"path/filepath"
 	"testing"
 
 	"golang.org/x/build/internal/workflow"
@@ -175,6 +180,79 @@
 	fmt.Fprintf(f.w, format, v...)
 }
 
+var updateFlag = flag.Bool("update", false, "Update golden files.")
+
+func TestDrawTerminal(t *testing.T) {
+	got, err := drawTerminal(`$ go install golang.org/dl/go1.18beta1@latest
+$ go1.18beta1 download
+Downloaded   0.0% (        0 / 111109966 bytes) ...
+Downloaded  50.0% ( 55554983 / 111109966 bytes) ...
+Downloaded 100.0% (111109966 / 111109966 bytes)
+Unpacking go1.18beta1.linux-s390x.tar.gz ...
+Success. You may now run 'go1.18beta1'
+$ go1.18beta1 version
+go version go1.18beta1 linux/s390x`)
+	if err != nil {
+		t.Fatalf("drawTerminal: got error=%v, want nil", err)
+	}
+	if *updateFlag {
+		encodePNG(t, filepath.Join("testdata", "terminal.png"), got)
+		return
+	}
+	want := decodePNG(t, filepath.Join("testdata", "terminal.png"))
+	if !got.Bounds().Eq(want.Bounds()) {
+		t.Fatalf("drawTerminal: got image bounds=%v, want %v", got.Bounds(), want.Bounds())
+	}
+	diff := func(a, b uint32) uint64 {
+		if a < b {
+			return uint64(b - a)
+		}
+		return uint64(a - b)
+	}
+	var total uint64
+	for y := 0; y < want.Bounds().Dy(); y++ {
+		for x := 0; x < want.Bounds().Dx(); x++ {
+			r0, g0, b0, a0 := got.At(x, y).RGBA()
+			r1, g1, b1, a1 := want.At(x, y).RGBA()
+			const D = 0xffff * 20 / 100 // Diff threshold of 20% for RGB color components.
+			if diff(r0, r1) > D || diff(g0, g1) > D || diff(b0, b1) > D || a0 != a1 {
+				t.Errorf("at (%d, %d):\n got RGBA %v\nwant RGBA %v", x, y, got.At(x, y), want.At(x, y))
+			}
+			total += diff(r0, r1) + diff(g0, g1) + diff(b0, b1)
+		}
+	}
+	if testing.Verbose() {
+		t.Logf("average pixel color diff: %v%%", 100*float64(total)/float64(0xffff*want.Bounds().Dx()*want.Bounds().Dy()))
+	}
+}
+
+func encodePNG(t *testing.T, name string, m image.Image) {
+	t.Helper()
+	var buf bytes.Buffer
+	err := (&png.Encoder{CompressionLevel: png.BestCompression}).Encode(&buf, m)
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = os.WriteFile(name, buf.Bytes(), 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func decodePNG(t *testing.T, name string) image.Image {
+	t.Helper()
+	f, err := os.Open(name)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer f.Close()
+	m, err := png.Decode(f)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return m
+}
+
 func TestPostTweet(t *testing.T) {
 	mux := http.NewServeMux()
 	mux.HandleFunc("upload.twitter.com/1.1/media/upload.json", func(w http.ResponseWriter, req *http.Request) {