Skip to content

Instantly share code, notes, and snippets.

@uobikiemukot
Created December 11, 2018 07:58
Show Gist options
  • Save uobikiemukot/3e9d4f80fd34d524797a0519235632af to your computer and use it in GitHub Desktop.
Save uobikiemukot/3e9d4f80fd34d524797a0519235632af to your computer and use it in GitHub Desktop.
emoji and text renderer
package main
import (
"bufio"
"bytes"
"fmt"
"image"
"image/jpeg"
_ "image/png"
"io"
"os"
"strings"
"github.com/golang/freetype/truetype"
"golang.org/x/image/draw"
"golang.org/x/image/font"
"golang.org/x/image/font/gofont/gobold"
"golang.org/x/image/math/fixed"
)
const (
exitSuccess = 0
exitFailure = 1
)
const (
imagePath = "./noto-emoji/png/128"
aliasFile = "./noto-emoji/emoji_aliases.txt"
fontSize = 64 // point
imageWidth = 640 // pixel
imageHeight = 120 // pixel
textTopMargin = 80 // fixed.I
lineHeight = 70 // fixed.I
)
// emoji alias
var alias = map[string]string{}
func init() {
fp, err := os.Open(aliasFile)
if err != nil {
panic(err)
}
s := bufio.NewScanner(fp)
for s.Scan() {
line := s.Text()
/*
alias file format:
# 'fe0f' is not in these sequences
1f3c3;1f3c3_200d_2642 # RUNNER -> man running
*/
if strings.HasPrefix(line, "#") {
continue
}
s := strings.Split(line, ";")
if len(s) != 2 {
continue
}
i := strings.Index(s[1], " ")
if i < 0 {
continue
}
from := s[0]
to := s[1][:i]
alias[from] = to
}
if err := s.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading alias file:", err)
}
}
func exist(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// GetPath if unicode codepoint is included in list and file exists return file path
func getPath(r rune) (string, error) {
name := fmt.Sprintf("%.4x", r)
var path string
if v, ok := alias[name]; ok {
path = fmt.Sprintf("%s/emoji_u%s.png", imagePath, v)
} else {
path = fmt.Sprintf("%s/emoji_u%s.png", imagePath, name)
}
if !exist(path) {
return "", fmt.Errorf("%s does NOT exist", path)
}
return path, nil
}
func loadEmoji(r rune, size int) (image.Image, bool) {
var img image.Image
path, err := getPath(r)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return img, false
}
fp, err := os.Open(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return img, false
}
defer fp.Close()
img, _, err = image.Decode(fp)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return img, false
}
rect := image.Rect(0, 0, size, size)
dst := image.NewRGBA(rect)
draw.ApproxBiLinear.Scale(dst, rect, img, img.Bounds(), draw.Over, nil)
return dst, true
}
func renderLine(img draw.Image, dr *font.Drawer, s string) {
size := dr.Face.Metrics().Ascent.Floor() + dr.Face.Metrics().Descent.Floor()
for _, r := range s {
emoji, ok := loadEmoji(r, size)
if ok {
// Drawer.Dot is glyph baseline of next glyph
// get left/top coordinates for draw.Draw().
p := image.Pt(dr.Dot.X.Floor(), dr.Dot.Y.Floor()-dr.Face.Metrics().Ascent.Floor())
rect := image.Rect(0, 0, size, size).Add(p)
// draw emoji and ascend baseline
draw.Draw(img, rect, emoji, image.ZP, draw.Over)
dr.Dot.X += fixed.I(size)
} else {
// fallback: use normal glyph
dr.DrawString(string(r))
}
}
}
func renderText(img draw.Image, face font.Face, text string) error {
dr := &font.Drawer{
Dst: img,
Src: image.White,
Face: face,
Dot: fixed.Point26_6{},
}
for i, s := range strings.Split(text, "\n") {
dr.Dot.X = (fixed.I(imageWidth) - dr.MeasureString(s)) / 2
dr.Dot.Y = fixed.I(textTopMargin + i*lineHeight)
renderLine(img, dr, s)
}
return nil
}
func outputJPEG(img image.Image) error {
buf := new(bytes.Buffer)
err := jpeg.Encode(buf, img, nil)
if err != nil {
return err
}
_, err = io.Copy(os.Stdout, buf)
if err != nil {
return err
}
return nil
}
func main() {
img := image.NewRGBA(image.Rect(0, 0, imageWidth, imageHeight))
ft, err := truetype.Parse(gobold.TTF)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(exitFailure)
}
opt := truetype.Options{
Size: fontSize,
DPI: 0,
Hinting: 0,
GlyphCacheEntries: 0,
SubPixelsX: 0,
SubPixelsY: 0,
}
renderText(img, truetype.NewFace(ft, &opt), "Hello, world! 🙋")
err = outputJPEG(img)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(exitFailure)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment