package handler import ( "bytes" "encoding/json" "encoding/xml" "errors" "fmt" "github.com/fogleman/gg" "github.com/golang/freetype" "github.com/golang/freetype/truetype" "github.com/gorilla/mux" "golang.org/x/image/draw" "golang.org/x/image/font" _ "golang.org/x/image/font/gofont/gobold" "golang.org/x/image/font/gofont/goregular" "golang.org/x/image/math/fixed" "html/template" "image" "image/color" "image/jpeg" "io" "os" "status/model" "strconv" "strings" "time" //"image/png" "net/http" ) const ( exitSuccess = 0 exitFailure = 1 ) const ( imagePath = "/home/m15o/Downloads/noto-emoji-main/png/128" aliasFile = "/home/m15o/Downloads/noto-emoji-main/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) { if r > 47 && r < 58 { return "", errors.New("numeric char") } 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, w io.Writer) error { buf := new(bytes.Buffer) err := jpeg.Encode(buf, img, nil) if err != nil { return err } _, err = io.Copy(w, buf) if err != nil { return err } return nil } func (h *Handler) showManageView(w http.ResponseWriter, r *http.Request) { logged, err := h.sess.Get(r) if err != nil { unauthorized(w, r) return } var page int64 = 0 if val, ok := r.URL.Query()["page"]; ok && len(val[0]) == 1 { page, _ = strconv.ParseInt(val[0], 10, 64) } statuses, showMore, err := h.storage.StatusByUsername(logged, 20, page) if err != nil { serverError(w, err) return } session, err := h.sess.Store.Get(r, "status") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } flash := "" if flashes := session.Flashes(); len(flashes) > 0 { flash = flashes[0].(string) } session.Save(r, w) h.renderLayout(w, "manage", map[string]interface{}{ "statuses": statuses, "showMore": showMore, "page": page, "flash": flash, "next_page": page + 1, "prev_page": page - 1, }, logged) } func (h *Handler) showUserView(w http.ResponseWriter, r *http.Request) { logged, _ := h.sess.Get(r) var page int64 = 0 if val, ok := r.URL.Query()["page"]; ok && len(val[0]) == 1 { page, _ = strconv.ParseInt(val[0], 10, 64) } username := mux.Vars(r)["user"] user, err := h.storage.UserByName(username) if err != nil { notFound(w) return } statuses, showMore, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 20, page) if err != nil { serverError(w, err) return } face := "" if len(statuses) > 0 { face = statuses[0].Face } h.renderLayout(w, "user", map[string]interface{}{ "user": username, "statuses": statuses, "face": face, "homepage": user.Homepage, "about": template.HTML(user.About), "picture": user.Picture, "email": user.Email, "showMore": showMore, "page": page, "next_page": page + 1, "prev_page": page - 1, }, logged) } type statusjson struct { Author string `json:"author"` Content string `json:"content"` Face string `json:"face"` TimeAgo string `json:"timeAgo"` } func (h *Handler) showUserStatusJSONView(w http.ResponseWriter, r *http.Request) { user := mux.Vars(r)["user"] if !h.storage.UserExists(user) { notFound(w) return } statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) if err != nil { serverError(w, err) return } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Content-Type", "application/json") var res statusjson if len(statuses) > 0 { res.Author = statuses[0].User res.Content = statuses[0].Content res.Face = statuses[0].Face res.TimeAgo = statuses[0].TimeAgo() } json.NewEncoder(w).Encode(res) } func (h *Handler) showUserStatusView(w http.ResponseWriter, r *http.Request) { statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) if err != nil { serverError(w, err) return } var status model.Status if len(statuses) > 0 { status = statuses[0] } h.view("status").Execute(w, map[string]interface{}{"status": status}) } func drawText(canvas *image.RGBA, text string) error { var ( fgColor image.Image fontFace *truetype.Font err error fontSize = 12.0 ) fgColor = image.White fontFace, err = freetype.ParseFont(goregular.TTF) fontDrawer := &font.Drawer{ Dst: canvas, Src: fgColor, Face: truetype.NewFace(fontFace, &truetype.Options{ Size: fontSize, Hinting: font.HintingFull, }), } //textBounds, _ := fontDrawer.BoundString(text) xPosition := fixed.I(20) //textHeight := textBounds.Max.Y - textBounds.Min.Y yPosition := fixed.I(20) fontDrawer.Dot = fixed.Point26_6{ X: xPosition, Y: yPosition, } fontDrawer.DrawString(text) return err } func createAvatar(text string) *image.RGBA { bgColor := color.RGBA{ R: 255, G: 20, B: 100, A: 255, } background := image.NewRGBA(image.Rect(0, 0, 200, 100)) draw.Draw(background, background.Bounds(), &image.Uniform{C: bgColor}, image.Point{}, draw.Src) drawText(background, text) return background } func (h *Handler) showUserStatusBadgeView(w http.ResponseWriter, r *http.Request) { user := mux.Vars(r)["user"] if !h.storage.UserExists(user) { notFound(w) return } statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) if err != nil { serverError(w, err) return } var username string if len(statuses) > 0 { username = statuses[0].User } background := "#f0ffff" dc := gg.NewContext(88, 31) dc.SetHexColor(background) dc.Clear() dc.SetHexColor("#191970") font, err := truetype.Parse(goregular.TTF) if err != nil { panic("") } face := truetype.NewFace(font, &truetype.Options{ Size: 12, }) dc.SetFontFace(face) if err != nil { fmt.Println(err) } //dc.DrawString(username, 10, 14) dc.DrawStringAnchored(username, 88/2, 8, 0.5, 0.5) dc.DrawStringAnchored("status.cafe", 88/2, 20, 0.5, 0.5) dc.EncodePNG(w) } // //func (h *Handler) showUserStatusImageViewEmoji(w http.ResponseWriter, r *http.Request) { // user := mux.Vars(r)["user"] // if !h.storage.UserExists(user) { // notFound(w) // return // } // statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) // if err != nil { // serverError(w, err) // return // } // // var username string // var face string // if len(statuses) > 0 { // username = statuses[0].User // face = statuses[0].Face // } // // 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), fmt.Sprintf("%s %s", username, face)) // // err = outputJPEG(img, w) // if err != nil { // fmt.Fprintln(os.Stderr, err) // os.Exit(exitFailure) // } //} func (h *Handler) showUserStatusImageView(w http.ResponseWriter, r *http.Request) { user := mux.Vars(r)["user"] if !h.storage.UserExists(user) { notFound(w) return } statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0) if err != nil { serverError(w, err) return } //w.Header().Set("Access-Control-Allow-Origin", "*") //w.Header().Set("Content-Type", "application/json") var text string if len(statuses) > 0 { text = statuses[0].Content } background := "#ffffff" foreground := "#000000" if c := r.URL.Query().Get("background"); c != "" { background = "#" + c } if c := r.URL.Query().Get("foreground"); c != "" { foreground = "#" + c } width := 125 dc := gg.NewContext(width, 125) dc.SetHexColor(background) dc.Clear() dc.SetHexColor(foreground) font, err := truetype.Parse(goregular.TTF) if err != nil { panic("") } face := truetype.NewFace(font, &truetype.Options{ Size: 9, }) dc.SetFontFace(face) if err != nil { fmt.Println(err) } dc.DrawStringWrapped(text, 8, 4, 0, 0, float64(width-(2*8)), 1.5, gg.AlignLeft) dc.EncodePNG(w) //dc.SavePNG("out.png") //avatar := createAvatar(text) //png.Encode(w, avatar) } func truncate(s string, max int) string { if len(s) > max { return s[:max] + "..." } return s } func (h *Handler) showAtomView(w http.ResponseWriter, r *http.Request) { username := mux.Vars(r)["user"] user, err := h.storage.UserByName(username) if err != nil { notFound(w) return } feed := Feed{ Title: user.Name, ID: fmt.Sprintf("https://status.cafe/users/%s/", user.Name), Author: &Person{ Name: user.Name, URI: fmt.Sprintf("https://status.cafe/users/%s", user.Name), }, Updated: Time(time.Now()), Link: []Link{ { Rel: "self", Href: fmt.Sprintf("https://status.cafe/users/%s.atom", user.Name), }, { Rel: "alternate", Href: fmt.Sprintf("https://status.cafe/users/%s", user.Name), Type: "text/html", }, }, Icon: user.Picture, Logo: user.Picture, } statuses, _, err := h.storage.StatusByUsername(user.Name, 20, 0) if err != nil { serverError(w, err) return } for _, status := range statuses { feed.Entry = append(feed.Entry, createAtomEntryFromStatus(status)) } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Content-Type", "application/atom+xml") var data []byte data, err = xml.MarshalIndent(&feed, "", " ") if err != nil { serverError(w, err) } w.Write([]byte(xml.Header + string(data))) }