Skip to content

Commit 74e18c2

Browse files
committed
all: initial implementation
1 parent 93c427d commit 74e18c2

File tree

10 files changed

+363
-1
lines changed

10 files changed

+363
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
data/code/*.go
17+
data/images/*.png
18+
code2img

Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright 2020 The golang.design Initiative authors.
2+
# All rights reserved. Use of this source code is governed
3+
# by a GNU GPL-3.0 license that can be found in the LICENSE file.
4+
5+
FROM chromedp/headless-shell
6+
RUN apt update && apt install dumb-init
7+
# RUN apt-get update -y \
8+
# && apt-get install -y fonts-noto \
9+
# && apt-get install -y fonts-noto-cjk \
10+
# && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/
11+
ENTRYPOINT ["dumb-init", "--"]
12+
WORKDIR /app
13+
COPY . .
14+
CMD ["/app/code2img"]

Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright 2020 The golang.design Initiative authors.
2+
# All rights reserved. Use of this source code is governed
3+
# by a GNU GPL-3.0 license that can be found in the LICENSE file.
4+
5+
all:
6+
GOOS=linux go build
7+
docker build -t code2img .
8+
up: down
9+
docker-compose -f docker-compose.yml up -d
10+
down:
11+
docker-compose -f docker-compose.yml down

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,28 @@
11
# code2img
2-
a carbon service wrapper for Go code
2+
3+
a carbon service wrapper
4+
5+
## API
6+
7+
```
8+
POST golang.design/api/v1/code2img
9+
{
10+
"code": "code string"
11+
}
12+
```
13+
14+
Response pure text (better for iOS shortcut):
15+
16+
```
17+
https://golang.design/api/v1/code2img/data/images/06ad29c5-2989-4a8e-8cd2-1ce63e36167b.png
18+
```
19+
20+
## iOS Shortcut
21+
22+
Get the shortcut from here:
23+
24+
- https://www.icloud.com/shortcuts/dac1a52db1d64cd79b5baaacf262fb5b
25+
26+
## License
27+
28+
© 2020 The golang.design Initiative Authors.

data/code/.gitkeep

Whitespace-only changes.

data/images/.gitkeep

Whitespace-only changes.

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2020 The golang.design Initiative authors.
2+
# All rights reserved. Use of this source code is governed
3+
# by a GNU GPL-3.0 license that can be found in the LICENSE file.
4+
5+
version: "3"
6+
7+
services:
8+
polyred:
9+
container_name: code2img
10+
restart: always
11+
volumes:
12+
- ./data:/app/data
13+
image: code2img
14+
cap_add:
15+
- SYS_PTRACE # for debugging
16+
ports:
17+
- "5656:8080"

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module golang.design/x/code2img
2+
3+
go 1.15
4+
5+
require (
6+
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac
7+
github.com/chromedp/chromedp v0.5.3
8+
github.com/gin-gonic/gin v1.6.3
9+
github.com/google/uuid v1.1.2
10+
github.com/sirupsen/logrus v1.7.0
11+
)

go.sum

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4=
2+
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
3+
github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg=
4+
github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w=
5+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8+
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
9+
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
10+
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
11+
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
12+
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
13+
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
14+
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
15+
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
16+
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
17+
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
18+
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
19+
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
20+
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
21+
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
22+
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
23+
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
24+
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
25+
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
26+
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
27+
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
28+
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
29+
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
30+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
31+
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
32+
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
33+
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
34+
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
35+
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
36+
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
37+
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
38+
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
39+
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
40+
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
41+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
42+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
43+
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
44+
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
45+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
46+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
47+
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
48+
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
49+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
50+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
51+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
52+
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
53+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
54+
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
55+
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
56+
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
57+
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
58+
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
59+
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
60+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
61+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
62+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
63+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
64+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
65+
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
66+
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

main.go

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// Copyright 2020 The golang.design Initiative authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a GNU GPL-3.0 license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"io/ioutil"
11+
"math"
12+
"net/http"
13+
"net/url"
14+
"os"
15+
"os/signal"
16+
"syscall"
17+
"time"
18+
19+
"github.com/chromedp/cdproto/cdp"
20+
"github.com/chromedp/cdproto/dom"
21+
"github.com/chromedp/cdproto/emulation"
22+
"github.com/chromedp/cdproto/page"
23+
"github.com/chromedp/chromedp"
24+
"github.com/gin-gonic/gin"
25+
"github.com/google/uuid"
26+
"github.com/sirupsen/logrus"
27+
)
28+
29+
func main() {
30+
router := gin.Default()
31+
router.Static("/api/v1/code2img/data/images", "./data/images")
32+
router.POST("/api/v1/code2img", code2img)
33+
s := &http.Server{Addr: ":8080", Handler: router}
34+
35+
go func() {
36+
if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
37+
logrus.Fatalf("listen: %s\n", err)
38+
}
39+
}()
40+
41+
quit := make(chan os.Signal)
42+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
43+
<-quit
44+
logrus.Println("shutting down...")
45+
46+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
47+
defer cancel()
48+
if err := s.Shutdown(ctx); err != nil {
49+
logrus.Fatal("forced to shutdown: ", err)
50+
}
51+
logrus.Println("server exiting, good bye!")
52+
}
53+
54+
type form struct {
55+
Code string `json:"code"`
56+
}
57+
58+
func code2img(c *gin.Context) {
59+
b := form{}
60+
if err := c.ShouldBindJSON(&b); err != nil {
61+
c.String(http.StatusBadRequest, fmt.Sprintf("Error: %s", err))
62+
return
63+
}
64+
id := uuid.New().String()
65+
gofile := "./data/code/" + id + ".go"
66+
67+
err := ioutil.WriteFile(gofile, []byte(b.Code), os.ModePerm)
68+
if err != nil {
69+
logrus.Errorf("[%s]: write file error %v", gofile, err)
70+
c.String(http.StatusBadRequest, fmt.Sprintf("Error: %s", err))
71+
return
72+
}
73+
74+
err = render("./data/images/"+id+".png", b.Code)
75+
if err != nil {
76+
c.String(http.StatusBadRequest, fmt.Sprintf("Error: %s", err))
77+
return
78+
}
79+
80+
c.String(http.StatusOK, "https://golang.design/api/v1/code2img/data/images/"+id+".png")
81+
}
82+
83+
func render(imgfile, code string) error {
84+
// https://carbon.now.sh/?
85+
// bg=rgba(74%2C144%2C226%2C1)&
86+
// t=material&
87+
// wt=none&
88+
// l=auto&
89+
// ds=true&
90+
// dsyoff=0px&
91+
// dsblur=29px&
92+
// wc=true&
93+
// wa=true&
94+
// pv=28px&
95+
// ph=100px&
96+
// ln=true&
97+
// fl=1&
98+
// fm=Source%20Code%20Pro&
99+
// fs=13.5px&
100+
// lh=152%25&
101+
// si=false&
102+
// es=2x&
103+
// wm=false&
104+
// code=func%2520main()
105+
var carbonOptions = map[string]string{
106+
"bg": "rgba(74,144,226,1)", // backgroundColor
107+
"t": "material", // theme
108+
"wt": "none", // windowTheme
109+
"l": "auto", // language
110+
"ds": "true", // dropShadow
111+
"dsyoff": "0px", // dropShadowOffsetY
112+
"dsblur": "29px", // dropShadowBlurRadius
113+
"wc": "true", // windowControls
114+
"wa": "true", // widthAdjustment
115+
"pv": "28px", // paddingVertical
116+
"ph": "100px", // paddingHorizontal
117+
"ln": "true", // lineNumbers
118+
"fl": "1", // firstLineNumber
119+
"fm": "Source Code Pro", // fontFamily
120+
"fs": "13.5px", // fontSize
121+
"lh": "152%", // lineHeight
122+
"si": "false", //squaredImage
123+
"es": "2x", // exportSize
124+
"wm": "false", // watermark
125+
}
126+
127+
values := url.Values{}
128+
for k, v := range carbonOptions {
129+
values.Set(k, v)
130+
}
131+
codeparam := url.Values{}
132+
codeparam.Set("code", code)
133+
134+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
135+
defer cancel()
136+
ctx, cancel = chromedp.NewContext(ctx)
137+
defer cancel()
138+
139+
url := "https://carbon.now.sh/?" + values.Encode() + "&" + codeparam.Encode()
140+
141+
var picbuf []byte
142+
sel := "#export-container .container-bg"
143+
err := chromedp.Run(ctx, chromedp.Tasks{
144+
chromedp.Navigate(url),
145+
screenshot(sel, &picbuf, chromedp.NodeReady, chromedp.ByID),
146+
})
147+
if err != nil {
148+
return fmt.Errorf("render task error: %w", err)
149+
}
150+
151+
err = ioutil.WriteFile(imgfile, picbuf, os.ModePerm)
152+
if err != nil {
153+
logrus.Errorf("[%s]: write screenshot error %v", imgfile, err)
154+
return err
155+
}
156+
return nil
157+
}
158+
159+
func screenshot(sel interface{}, picbuf *[]byte, opts ...chromedp.QueryOption) chromedp.QueryAction {
160+
if picbuf == nil {
161+
panic("picbuf cannot be nil")
162+
}
163+
164+
return chromedp.QueryAfter(sel, func(ctx context.Context, nodes ...*cdp.Node) error {
165+
if len(nodes) < 1 {
166+
return fmt.Errorf("selector %q did not return any nodes", sel)
167+
}
168+
169+
// get layout metrics
170+
_, _, contentSize, err := page.GetLayoutMetrics().Do(ctx)
171+
if err != nil {
172+
return err
173+
}
174+
175+
width, height := int64(math.Ceil(contentSize.Width)), int64(math.Ceil(contentSize.Height))
176+
177+
// force viewport emulation
178+
err = emulation.SetDeviceMetricsOverride(width, height, 1, false).
179+
WithScreenOrientation(&emulation.ScreenOrientation{
180+
Type: emulation.OrientationTypePortraitPrimary,
181+
Angle: 0,
182+
}).
183+
Do(ctx)
184+
if err != nil {
185+
return err
186+
}
187+
188+
// get box model
189+
box, err := dom.GetBoxModel().WithNodeID(nodes[0].NodeID).Do(ctx)
190+
if err != nil {
191+
return err
192+
}
193+
if len(box.Margin) != 8 {
194+
return chromedp.ErrInvalidBoxModel
195+
}
196+
197+
// take screenshot of the box
198+
buf, err := page.CaptureScreenshot().
199+
WithFormat(page.CaptureScreenshotFormatPng).
200+
WithClip(&page.Viewport{
201+
X: math.Round(box.Margin[0]),
202+
Y: math.Round(box.Margin[1]),
203+
Width: math.Round(box.Margin[4] - box.Margin[0]),
204+
Height: math.Round(box.Margin[5] - box.Margin[1]),
205+
Scale: 1.0,
206+
}).Do(ctx)
207+
if err != nil {
208+
return err
209+
}
210+
211+
*picbuf = buf
212+
return nil
213+
}, append(opts, chromedp.NodeVisible)...)
214+
}

0 commit comments

Comments
 (0)