Created
December 11, 2018 07:58
-
-
Save uobikiemukot/3e9d4f80fd34d524797a0519235632af to your computer and use it in GitHub Desktop.
emoji and text renderer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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