├── README.md ├── avatar.jpeg ├── avatar2.jpeg ├── avatar3.png ├── avatar4.png ├── avatar5.png ├── fonts └── CuprumFFU.ttf ├── go.mod ├── go.sum ├── images ├── github.png ├── homepage.png └── twitter.png ├── main.go ├── main_test.go ├── output ├── abdulsamet-template1.png ├── abdulsamet-template2.png ├── erkan-template1.png ├── erkan-template2.png ├── furkan-template1.png ├── furkan-template2.png ├── oguzhan-template1.png └── oguzhan-template2.png ├── static ├── index.html └── logo.png └── templates ├── 1.png └── 2.png /README.md: -------------------------------------------------------------------------------- 1 | # Go Türkiye Cover Generator 2 | 3 | Etkinliklerde değişen tek şey konuşmacıların konusu, ismi, nerede çalıştıklarıyla beraber 4 | etkinliğin zamanı. 5 | 6 | Her etkinlikte değişen konuşmacılara göre yeniden yeniden etkinlik cover resmini elle oluşturmak 7 | yerine neden bir png üretecek bir go servisi ile bunu yapmayalım? 8 | 9 | İster testlerden ister uygulamayı çalıştırıp doğrudan anasayfaya giderek 10 | verdiğiniz bilgilere dayalı png resmi generate ettirebilirsiniz. 11 | 12 | # Uygulamayı çalıştırmak 13 | 14 | ```bash 15 | go run . 16 | ``` 17 | 18 | 1323'de local bir http server ayağa kaldırılır. 19 | 20 | ```localhost:1323/``` giderseniz aşağıdaki resme benzer sayfa göreceksiniz. 21 | 22 | ![](images/homepage.png) 23 | 24 | Buradan ilgili alanları doldurup resmi oluşturabilirsiniz ya da testleri doğrudan 25 | istediğiniz test data'sı ile çalıştırabilirsiniz. 26 | 27 | # Testleri çalıştırmak 28 | 29 | ```bash 30 | go test 31 | ``` 32 | 33 | Yaptıktan sonra root dizindeki /outputs klasörüne gidip generate edilen resimlere bakabilirsiniz. 34 | 35 | # Teknik Detay 36 | 37 | * Font, resimler, ve html sayfası için `go embed` kullanıldı. 38 | * [Web framework olarak labstack/echo](https://github.com/labstack/echo) 39 | * [2D rendering için fogleman/gg](https://github.com/fogleman/gg) 40 | * [Font için golang/freetype](https://github.com/golang/freetype) 41 | * [Konuşmacı avatarını bozmadan resize edebilmek için nfnt/resize](https://github.com/nfnt/resize) 42 | -------------------------------------------------------------------------------- /avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/avatar.jpeg -------------------------------------------------------------------------------- /avatar2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/avatar2.jpeg -------------------------------------------------------------------------------- /avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/avatar3.png -------------------------------------------------------------------------------- /avatar4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/avatar4.png -------------------------------------------------------------------------------- /avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/avatar5.png -------------------------------------------------------------------------------- /fonts/CuprumFFU.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/fonts/CuprumFFU.ttf -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/GoTurkiye/cover-generator 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 7 | github.com/fogleman/gg v1.3.0 8 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 9 | github.com/labstack/echo v3.3.10+incompatible 10 | github.com/labstack/gommon v0.3.0 // indirect 11 | github.com/mattn/go-colorable v0.1.8 // indirect 12 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect 13 | github.com/stretchr/testify v1.7.0 // indirect 14 | github.com/valyala/fasttemplate v1.2.1 // indirect 15 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect 16 | golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 // indirect 17 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect 18 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 4 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 5 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= 6 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 7 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 8 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 9 | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= 10 | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= 11 | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= 12 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 13 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 14 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 15 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 16 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 17 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 18 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 19 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 20 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 21 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 27 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 28 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 29 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 30 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 31 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 32 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 33 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 34 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 35 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= 36 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 37 | golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs= 38 | golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 39 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 40 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= 41 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 42 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 43 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= 49 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 51 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 52 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 53 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 54 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 57 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 58 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 59 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 60 | -------------------------------------------------------------------------------- /images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/images/github.png -------------------------------------------------------------------------------- /images/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/images/homepage.png -------------------------------------------------------------------------------- /images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/images/twitter.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "github.com/fogleman/gg" 7 | "github.com/golang/freetype/truetype" 8 | "github.com/labstack/echo" 9 | "github.com/nfnt/resize" 10 | "golang.org/x/image/font" 11 | "image" 12 | "image/png" 13 | "net/http" 14 | "strconv" 15 | ) 16 | 17 | //go:embed static/logo.png 18 | var logo []byte 19 | 20 | //go:embed static/index.html 21 | var index []byte 22 | 23 | //go:embed fonts/CuprumFFU.ttf 24 | var cuprum []byte 25 | 26 | //go:embed templates/1.png 27 | var template1 []byte 28 | 29 | //go:embed templates/2.png 30 | var template2 []byte 31 | 32 | //go:embed images/twitter.png 33 | var twitterIcon []byte 34 | 35 | //go:embed images/github.png 36 | var githubIcon []byte 37 | 38 | type Props struct { 39 | TopicHexColor string 40 | AvatarContainerCircleBg string 41 | CircleY float64 42 | CircleR float64 43 | NameTextY float64 44 | JobTextY float64 45 | JobTextColor string 46 | EventDateBg string 47 | EventRectangleY float64 48 | } 49 | 50 | var templateMap map[string][]byte 51 | var templateToProps map[string]Props 52 | 53 | func main() { 54 | templateMap = initTemplateMap() 55 | templateToProps = initializeTemplateToProps() 56 | 57 | e := echo.New() 58 | e.HideBanner = true 59 | 60 | e.GET("/", renderHomePage) 61 | e.GET("/logo", renderLogo) 62 | e.POST("/create", createCoverImage) 63 | 64 | e.Logger.Fatal(e.Start(":1323")) 65 | } 66 | 67 | func renderHomePage(c echo.Context) error { 68 | _, err := c.Response().Write(index) 69 | return err 70 | } 71 | 72 | func renderLogo(c echo.Context) error { 73 | _, err := c.Response().Write(logo) 74 | return err 75 | } 76 | 77 | func createCoverImage(c echo.Context) error { 78 | templateId := c.FormValue("template") 79 | 80 | topic := c.FormValue("topic") 81 | avatar, errAvatar := c.FormFile("avatar") 82 | if errAvatar != nil { 83 | return c.JSON(http.StatusBadRequest, errAvatar) 84 | } 85 | name := c.FormValue("name") 86 | job := c.FormValue("job") 87 | eventTime := c.FormValue("eventTime") 88 | 89 | putTwitterInfo, _ := strconv.ParseBool(c.FormValue("putTwitterInfo")) 90 | twitterName := c.FormValue("twitterName") 91 | 92 | putGithubInfo, _ := strconv.ParseBool(c.FormValue("putGithubInfo")) 93 | githubName := c.FormValue("githubName") 94 | 95 | img, err := png.Decode(bytes.NewReader(templateMap[templateId])) 96 | if err != nil { 97 | return c.JSON(http.StatusInternalServerError, err.Error()) 98 | } 99 | 100 | imgWidth := img.Bounds().Size().X 101 | imgHeight := img.Bounds().Size().Y 102 | 103 | dc := gg.NewContext(imgWidth, imgHeight) 104 | dc.DrawImage(img, 0, 0) 105 | 106 | face, err := loadFontWithSpecificSize(65) 107 | if err != nil { 108 | return c.JSON(http.StatusInternalServerError, err.Error()) 109 | } 110 | dc.SetFontFace(face) 111 | 112 | dc.SetHexColor(templateToProps[templateId].TopicHexColor) 113 | topicW, _ := dc.MeasureString(topic) 114 | dc.DrawString(topic, 115 | float64(imgWidth/2)-(topicW/2), 116 | 390) 117 | 118 | file, err := avatar.Open() 119 | if err != nil { 120 | return c.JSON(http.StatusBadRequest, err.Error()) 121 | } 122 | defer file.Close() 123 | 124 | avatarImg, _, err := image.Decode(file) 125 | 126 | // Put Speaker Avatar 127 | avatarResized := resize.Resize(200, 200, avatarImg, resize.Lanczos3) 128 | dc.SetHexColor(templateToProps[templateId].AvatarContainerCircleBg) 129 | dc.DrawCircle(float64(imgWidth/2), templateToProps[templateId].CircleY, templateToProps[templateId].CircleR) 130 | dc.Clip() 131 | dc.DrawImageAnchored(avatarResized, imgWidth/2, int(templateToProps[templateId].CircleY), 0.5, 0.5) 132 | dc.ResetClip() 133 | 134 | // Put Speaker's Name and Company 135 | face, _ = loadFontWithSpecificSize(50) 136 | dc.SetFontFace(face) 137 | dc.SetHexColor("#00040B") 138 | dc.DrawString(name, float64(imgWidth/2)+120, templateToProps[templateId].NameTextY) 139 | 140 | face, _ = loadFontWithSpecificSize(30) 141 | dc.SetFontFace(face) 142 | dc.SetHexColor(templateToProps[templateId].JobTextColor) 143 | 144 | const rectangleWidth = 340 145 | dc.DrawStringWrapped(job, 146 | float64(imgWidth/2)+120, templateToProps[templateId].JobTextY, 0, 0, rectangleWidth, 1, gg.AlignLeft) 147 | 148 | twitterIconY := templateToProps[templateId].JobTextY 149 | jobTextWidth, jobTextHeight := dc.MeasureString(job) 150 | if jobTextWidth > rectangleWidth { 151 | twitterIconY += jobTextWidth - rectangleWidth 152 | } else { 153 | twitterIconY += jobTextHeight + 20 154 | } 155 | 156 | // Twitter and Github should be together because we set github icon position 157 | // relative to twitter icon position! 158 | 159 | if putTwitterInfo { 160 | twitterImage, err := png.Decode(bytes.NewReader(twitterIcon)) 161 | if err == nil { 162 | dc.DrawImage(twitterImage, (imgWidth/2)+120, int(twitterIconY)) 163 | face, _ = loadFontWithSpecificSize(20) 164 | dc.SetFontFace(face) 165 | dc.DrawString("@"+twitterName, float64(imgWidth/2)+150, twitterIconY+20) 166 | } 167 | } 168 | if putGithubInfo { 169 | githubImage, err := png.Decode(bytes.NewReader(githubIcon)) 170 | if err == nil { 171 | dc.DrawImage(githubImage, (imgWidth/2)+120, int(twitterIconY+30)) 172 | face, _ = loadFontWithSpecificSize(20) 173 | dc.SetFontFace(face) 174 | dc.DrawString("/"+githubName, float64(imgWidth/2)+150, twitterIconY+50) 175 | } 176 | } 177 | 178 | // Event date ve time 179 | face, _ = loadFontWithSpecificSize(40) 180 | dc.SetFontFace(face) 181 | dc.SetHexColor(templateToProps[templateId].EventDateBg) 182 | wDate, hDate := dc.MeasureString(eventTime) 183 | dateRectX := float64(imgWidth/2) - (wDate / 2) 184 | dc.DrawRectangle(dateRectX, templateToProps[templateId].EventRectangleY, wDate+30, hDate+10) 185 | dc.Fill() 186 | 187 | dc.SetHexColor("#FFFFFF") 188 | dc.DrawString(eventTime, dateRectX+(wDate+30-wDate)/2, templateToProps[templateId].EventRectangleY+hDate) 189 | 190 | return png.Encode(c.Response().Writer, dc.Image()) 191 | } 192 | 193 | func initializeTemplateToProps() map[string]Props { 194 | initMap := make(map[string]Props, 0) 195 | 196 | initMap["1"] = Props{ 197 | TopicHexColor: "#2E2D29", 198 | AvatarContainerCircleBg: "2D414A", 199 | CircleY: 590, 200 | CircleR: 100, 201 | NameTextY: 570, 202 | JobTextY: 590, 203 | JobTextColor: "#7F9EA3", 204 | EventDateBg: "#476C7C", 205 | EventRectangleY: 740, 206 | } 207 | initMap["2"] = Props{ 208 | TopicHexColor: "#291D07", 209 | AvatarContainerCircleBg: "#8F6863", 210 | CircleY: 540, 211 | CircleR: 100, 212 | NameTextY: 520, 213 | JobTextY: 540, 214 | JobTextColor: "#47350F", 215 | EventDateBg: "#4F3A0B", 216 | EventRectangleY: 720, 217 | } 218 | return initMap 219 | } 220 | 221 | func initTemplateMap() map[string][]byte { 222 | initMap := make(map[string][]byte, 0) 223 | initMap["1"] = template1 224 | initMap["2"] = template2 225 | 226 | return initMap 227 | } 228 | 229 | func loadFontWithSpecificSize(size float64) (font.Face, error) { 230 | f, err := truetype.Parse(cuprum) 231 | if err != nil { 232 | return nil, err 233 | } 234 | face := truetype.NewFace(f, &truetype.Options{ 235 | Size: size, 236 | }) 237 | 238 | return face, nil 239 | } 240 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/labstack/echo" 8 | "github.com/stretchr/testify/require" 9 | "io" 10 | "io/ioutil" 11 | "mime/multipart" 12 | "net/http" 13 | "net/http/httptest" 14 | "net/url" 15 | "os" 16 | "strconv" 17 | "testing" 18 | ) 19 | 20 | type request struct { 21 | fileName string 22 | template string 23 | topic string 24 | name string 25 | job string 26 | eventTime string 27 | outputImage string 28 | twitterName string 29 | githubName string 30 | } 31 | 32 | func initTestData() []request { 33 | requests := make([]request, 0) 34 | requests = append(requests, request{ 35 | fileName: "avatar5.png", 36 | template: "1", 37 | topic: "Go ile Protocol Buffer Kullanımı (Google Runtime)", 38 | name: "Mert Simsek", 39 | job: "Software Developer", 40 | eventTime: "12 Ağustos Perşembe Saat 21:00", 41 | outputImage: "mert-simsek-template1.png", 42 | twitterName: "mertingen", 43 | githubName: "mertingen", 44 | }) 45 | requests = append(requests, request{ 46 | fileName: "avatar4.png", 47 | template: "1", 48 | topic: "Go ile Http Client Paketi Geliştirme ve Test Süreçleri\n", 49 | name: "Erhan Yakut", 50 | job: "Senior Software Architect at Binalyze", 51 | eventTime: "30 Temmuz Cuma 21:00", 52 | outputImage: "erkan-template1.png", 53 | }) 54 | requests = append(requests, request{ 55 | fileName: "avatar4.png", 56 | template: "2", 57 | topic: "Go ile Http Client Paketi Geliştirme ve Test Süreçleri\n", 58 | name: "Erhan Yakut", 59 | job: "Senior Software Architect at Binalyze", 60 | eventTime: "30 Temmuz Cuma 21:00", 61 | outputImage: "erkan-template2.png", 62 | twitterName: "yakuter", 63 | }) 64 | requests = append(requests, request{ 65 | fileName: "avatar3.png", 66 | template: "1", 67 | topic: "Go Compilerına Tilde (~) Operatorü Eklemek", 68 | name: "Furkan Türkal", 69 | job: "Backend Developer at Trendyol", 70 | eventTime: "4 Haziran Cuma 21:00", 71 | outputImage: "furkan-template1.png", 72 | }) 73 | requests = append(requests, request{ 74 | fileName: "avatar3.png", 75 | template: "2", 76 | topic: "Go Compilerına Tilde (~) Operatorü Eklemek", 77 | name: "Furkan Türkal", 78 | job: "Backend Developer at Trendyol", 79 | eventTime: "4 Haziran Cuma 21:00", 80 | outputImage: "furkan-template2.png", 81 | }) 82 | requests = append(requests, request{ 83 | fileName: "avatar.jpeg", 84 | template: "1", 85 | topic: "Go+Vue.js ile Resim Hatırlatma Uygulaması Yapmak", 86 | name: "Abdulsamet İleri", 87 | job: "Full Stack Developer at Modanisa", 88 | eventTime: "17 Haziran Perşembe 21:00", 89 | outputImage: "abdulsamet-template1.png", 90 | }) 91 | requests = append(requests, request{ 92 | fileName: "avatar.jpeg", 93 | template: "2", 94 | topic: "Go+Vue.js ile Resim Hatırlatma Uygulaması Yapmak", 95 | name: "Abdulsamet İleri", 96 | job: "Full Stack Developer at Modanisa", 97 | eventTime: "17 Haziran Perşembe 21:00", 98 | outputImage: "abdulsamet-template2.png", 99 | }) 100 | requests = append(requests, request{ 101 | fileName: "avatar2.jpeg", 102 | template: "1", 103 | topic: "Go İle Network Programlama", 104 | name: "Oğuzhan Yılmaz", 105 | job: "CTO at Masomo Games", 106 | eventTime: "26 Haziran Cumartesi 21:00", 107 | outputImage: "oguzhan-template1.png", 108 | }) 109 | requests = append(requests, request{ 110 | fileName: "avatar2.jpeg", 111 | template: "2", 112 | topic: "Go İle Network Programlama", 113 | name: "Oğuzhan Yılmaz", 114 | job: "CTO at Masomo Games", 115 | eventTime: "26 Haziran Cumartesi 21:00", 116 | outputImage: "oguzhan-template2.png", 117 | }) 118 | 119 | return requests 120 | } 121 | 122 | func Test_CreateCoverImage(t *testing.T) { 123 | t.Run("When avatar is not specified", func(t *testing.T) { 124 | res, req := createHttpReq(http.MethodGet, "/api/v1/categories", nil) 125 | e := echo.New() 126 | echoTextContext := e.NewContext(req, res) 127 | err := createCoverImage(echoTextContext) 128 | require.NoError(t, err) 129 | 130 | var mfc map[string]interface{} 131 | errJson := json.Unmarshal(res.Body.Bytes(), &mfc) 132 | require.NoError(t, errJson) 133 | 134 | require.Equal(t, "request Content-Type isn't multipart/form-data", mfc["ErrorString"]) 135 | }) 136 | t.Run("All event template samples are generated successfully?", func(t *testing.T) { 137 | for _, r := range initTestData() { 138 | body, contentType := fileUploadRequest(r.fileName) 139 | res, req := createHttpReq(http.MethodPost, "/create", body) 140 | req.Header.Add("Content-Type", contentType) 141 | 142 | req.Form = url.Values{} 143 | req.Form.Set("template", r.template) 144 | req.Form.Set("topic", r.topic) 145 | req.Form.Set("name", r.name) 146 | req.Form.Set("job", r.job) 147 | req.Form.Set("eventTime", r.eventTime) 148 | req.Form.Set("putTwitterInfo", strconv.FormatBool(r.twitterName != "")) 149 | req.Form.Set("twitterName", r.twitterName) 150 | req.Form.Set("putGithubInfo", strconv.FormatBool(r.githubName != "")) 151 | req.Form.Set("githubName", r.githubName) 152 | e := echo.New() 153 | echoTextContext := e.NewContext(req, res) 154 | 155 | templateMap = initTemplateMap() 156 | templateToProps = initializeTemplateToProps() 157 | 158 | err := createCoverImage(echoTextContext) 159 | require.NoError(t, err) 160 | 161 | err = ioutil.WriteFile( 162 | fmt.Sprintf("output/%s", r.outputImage), 163 | res.Body.Bytes(), 164 | 0777, 165 | ) 166 | require.NoError(t, err) 167 | } 168 | 169 | }) 170 | } 171 | 172 | func createHttpReq(method string, endpoint string, body *bytes.Buffer) (w *httptest.ResponseRecorder, req *http.Request) { 173 | if body == nil { 174 | body = bytes.NewBuffer(make([]byte, 512)) 175 | } 176 | req = httptest.NewRequest(method, endpoint, body) 177 | rec := httptest.NewRecorder() 178 | return rec, req 179 | } 180 | 181 | func fileUploadRequest(fileName string) (body *bytes.Buffer, contentType string) { 182 | file, err := os.Open(fileName) 183 | if err != nil { 184 | fmt.Println(err) 185 | } 186 | defer file.Close() 187 | body = new(bytes.Buffer) 188 | writer := multipart.NewWriter(body) 189 | part, err := writer.CreateFormFile("avatar", "avatar") 190 | if err != nil { 191 | fmt.Println(err) 192 | } 193 | _, err = io.Copy(part, file) 194 | if err != nil { 195 | fmt.Println(err) 196 | } 197 | err = writer.Close() 198 | if err != nil { 199 | fmt.Println(err) 200 | } 201 | return body, writer.FormDataContentType() 202 | } 203 | -------------------------------------------------------------------------------- /output/abdulsamet-template1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/abdulsamet-template1.png -------------------------------------------------------------------------------- /output/abdulsamet-template2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/abdulsamet-template2.png -------------------------------------------------------------------------------- /output/erkan-template1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/erkan-template1.png -------------------------------------------------------------------------------- /output/erkan-template2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/erkan-template2.png -------------------------------------------------------------------------------- /output/furkan-template1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/furkan-template1.png -------------------------------------------------------------------------------- /output/furkan-template2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/furkan-template2.png -------------------------------------------------------------------------------- /output/oguzhan-template1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/oguzhan-template1.png -------------------------------------------------------------------------------- /output/oguzhan-template2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/output/oguzhan-template2.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | Go Türkiye Template Generator 12 | 13 | 14 |
15 |
16 |
17 | Go Türkiye Logo 18 |
19 |

Go Türkiye 🇹🇷

20 |

Event Cover Generator

21 |
22 | 23 | 27 |
28 |
29 | 30 | 34 |
35 |
36 | 37 | 39 |
40 |
41 | 42 | 45 |
46 |
47 | 48 | 50 |
51 |
52 | 53 | 55 |
56 |
57 | 58 |
59 |
60 |
61 | 62 | 63 | 64 | 65 | 68 | 71 | 74 | 75 | -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/static/logo.png -------------------------------------------------------------------------------- /templates/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/templates/1.png -------------------------------------------------------------------------------- /templates/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoTurkiye/cover-generator/74a4b0f93db2aa6e99a9b6a9995976be5d52919e/templates/2.png --------------------------------------------------------------------------------