-
Notifications
You must be signed in to change notification settings - Fork 0
/
comic.go
146 lines (120 loc) · 2.86 KB
/
comic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package comics
import (
"bytes"
"encoding/json"
"fmt"
"image"
"io"
"io/ioutil"
"net/http"
"sync"
"github.com/fogleman/gg"
)
const lineSpacing = 1.5
var imageCache = sync.Map{}
type Bubble struct {
PosX float64 `json:"x"`
PosY float64 `json:"y"`
Width float64 `json:"width"`
Height float64 `json:"height"`
}
type Template struct {
Name string `json:"name"`
Width float64 `json:"width"`
Height float64 `json:"height"`
Bubbles []*Bubble `json:"bubbles"`
ImageURL string `json:"image_url"`
fontPath string `json:"-"`
}
func (t *Template) String() string {
out, _ := json.Marshal(t)
return string(out)
}
func (t *Template) getBaseImg() (image.Image, error) {
var imageBytes []byte
cacheItem, ok := imageCache.Load(t.ImageURL)
if ok {
imageBytes, ok = cacheItem.([]byte)
if !ok {
return nil, fmt.Errorf("error: invalid data in image cache")
}
} else {
resp, err := http.Get(t.ImageURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
imageBytes, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
imageCache.Store(t.ImageURL, imageBytes)
}
tempFile, err := ioutil.TempFile("/tmp", "comic-base-img")
if err != nil {
return nil, err
}
defer tempFile.Close()
_, err = io.Copy(tempFile, bytes.NewReader(imageBytes))
if err != nil {
return nil, err
}
return gg.LoadImage(tempFile.Name())
}
func (b *Bubble) setFontSize(dc *gg.Context, text, fontPath string) {
fontSize := float64(1)
renderedHeight := float64(0)
Outer:
for renderedHeight < b.Height-(dc.FontHeight()) {
dc.LoadFontFace(fontPath, fontSize)
wrappedText := dc.WordWrap(text, b.Width)
for _, t := range wrappedText {
w, _ := dc.MeasureString(t)
if w > b.Width {
break Outer
}
}
renderedHeight = dc.FontHeight() * lineSpacing * float64(len(wrappedText))
fontSize++
}
dc.LoadFontFace(fontPath, fontSize-1)
}
func (t *Template) Render(text []string) ([]byte, error) {
if len(text) < len(t.Bubbles) {
return nil, fmt.Errorf("error: not enough text for the template")
}
baseImg, err := t.getBaseImg()
if err != nil {
return nil, err
}
dc := gg.NewContextForImage(baseImg)
dc.SetRGB(0, 0, 0)
for i, bubble := range t.Bubbles {
bubble.setFontSize(dc, text[i], t.fontPath)
dc.DrawStringWrapped(text[i], bubble.PosX, bubble.PosY, 0, 0, bubble.Width, lineSpacing, gg.AlignLeft)
}
buf := bytes.NewBuffer(nil)
err = dc.EncodePNG(buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func NewTemplate(templateUrl string, fontPath string) (*Template, error) {
resp, err := http.Get(templateUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
templateBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
t := &Template{}
err = json.Unmarshal(templateBytes, t)
if err != nil {
return nil, err
}
t.fontPath = fontPath
return t, nil
}