├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── LICENSE ├── Makefile ├── README.md ├── all-colors.sh ├── font.go ├── font └── Cica-Regular.ttf ├── formatter.go ├── go.mod ├── go.sum ├── main.go ├── test.sh └── testdata ├── has_line.png ├── no_line.png └── test.go /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Setup Go 16 | uses: actions/setup-go@v1 17 | with: 18 | go-version: 1.x 19 | - name: Add $GOPATH/bin to $PATH 20 | run: echo "::add-path::$(go env GOPATH)/bin" 21 | - name: Test 22 | run: make test 23 | - name: Lint 24 | run: make lint 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Setup Go 16 | uses: actions/setup-go@v1 17 | with: 18 | go-version: 1.x 19 | - name: Add $GOPATH/bin to $PATH 20 | run: echo "::add-path::$(go env GOPATH)/bin" 21 | - name: Cross build 22 | run: make cross 23 | - name: Create Release 24 | id: create_release 25 | uses: actions/create-release@master 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | with: 29 | tag_name: ${{ github.ref }} 30 | release_name: Release ${{ github.ref }} 31 | - name: Upload 32 | run: make upload 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 skanehira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := code2img 2 | VERSION := $$(make -s show-version) 3 | VERSION_PATH := . 4 | CURRENT_REVISION := $(shell git rev-parse --short HEAD) 5 | BUILD_LDFLAGS := "-s -w -X main.revision=$(CURRENT_REVISION)" 6 | GOBIN ?= $(shell go env GOPATH)/bin 7 | export GO111MODULE=on 8 | 9 | .PHONY: all 10 | all: clean build 11 | 12 | .PHONY: build 13 | build: 14 | go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) 15 | 16 | .PHONY: install 17 | install: 18 | go install -ldflags=$(BUILD_LDFLAGS) ./... 19 | 20 | .PHONY: show-version 21 | show-version: $(GOBIN)/gobump 22 | @gobump show -r $(VERSION_PATH) 23 | 24 | $(GOBIN)/gobump: 25 | @cd && go get github.com/x-motemen/gobump/cmd/gobump 26 | 27 | .PHONY: cross 28 | cross: $(GOBIN)/goxz 29 | goxz -n $(BIN) -pv=v$(VERSION) -build-ldflags=$(BUILD_LDFLAGS) 30 | 31 | $(GOBIN)/goxz: 32 | cd && go get github.com/Songmu/goxz/cmd/goxz 33 | 34 | .PHONY: test 35 | test: build 36 | bash test.sh 37 | 38 | .PHONY: lint 39 | lint: $(GOBIN)/golint 40 | go vet ./... 41 | golint -set_exit_status ./... 42 | 43 | $(GOBIN)/golint: 44 | cd && go get golang.org/x/lint/golint 45 | 46 | .PHONY: clean 47 | clean: 48 | rm -rf $(BIN) goxz tmp 49 | go clean 50 | 51 | .PHONY: bump 52 | bump: $(GOBIN)/gobump 53 | ifneq ($(shell git status --porcelain),) 54 | $(error git workspace is dirty) 55 | endif 56 | ifneq ($(shell git rev-parse --abbrev-ref HEAD),master) 57 | $(error current branch is not master) 58 | endif 59 | @gobump up -w "$(VERSION_PATH)" 60 | git commit -am "bump up version to $(VERSION)" 61 | git tag "v$(VERSION)" 62 | git push origin master 63 | git push origin "refs/tags/v$(VERSION)" 64 | 65 | .PHONY: upload 66 | upload: $(GOBIN)/ghr 67 | ghr "v$(VERSION)" goxz 68 | 69 | $(GOBIN)/ghr: 70 | cd && go get github.com/tcnksm/ghr 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # code2img 2 | `code2img` can generate image of source code. 3 | This was inspired by [carbon](https://carbon.now.sh/) and [silicon](https://github.com/Aloxaf/silicon) 4 | 5 | ![](https://i.imgur.com/TjoOQct.gif) 6 | 7 | ## Features 8 | - Doesn't need browser & Internet 9 | - Copy image of source code to clipboard 10 | - Supported [some](https://xyproto.github.io/splash/docs/all.html) color schemes 11 | - Supported [some](https://github.com/alecthomas/chroma#supported-languages) languages 12 | 13 | ## Usage 14 | ```sh 15 | $ code2img 16 | code2img - generate image of source code 17 | 18 | Version: 1.2.0 19 | 20 | Usage: 21 | $ code2img -t monokai main.go main.png 22 | $ echo 'fmt.Println("Hello World")' | code2img -ext go -t native -o sample.png 23 | $ code2img -c main.go 24 | 25 | -t color theme(default: solarized-dark) 26 | -o output file name(default: out.png) 27 | -c copy to clipboard 28 | -l print line 29 | -ext file extension 30 | ``` 31 | 32 | ## Requirements 33 | - `xclip` (if copy image to clipboard in linux) 34 | 35 | ## Installtion 36 | 37 | ```sh 38 | $ git clone https://github.com/skanehira/code2img 39 | $ cd code2img && go install 40 | ``` 41 | 42 | ## Author 43 | skanehira 44 | -------------------------------------------------------------------------------- /all-colors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | colors="abap algol algol_nu api arduino autumn borland bw colorful dracula emacs friendly fruity github igor lovelace manni monokai monokailight murphy native paraiso-dark paraiso-light pastie perldoc pygments rainbow_dash rrt solarized-dark solarized-dark256 solarized-light swapoff tango trac vim vs xcode" 3 | 4 | if [ ! -e tmp ];then 5 | mkdir tmp 6 | fi 7 | 8 | code=testdata/test.go 9 | 10 | go build -o test 11 | 12 | for c in $colors;do 13 | ./test -l -t $c $code tmp/$c.png 14 | done 15 | 16 | rm ./test 17 | -------------------------------------------------------------------------------- /font/Cica-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skanehira/code2img/c32f0da14d77b708c7383687305ed203f330e277/font/Cica-Regular.ttf -------------------------------------------------------------------------------- /formatter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "image" 7 | "image/color" 8 | "image/draw" 9 | "image/png" 10 | "io" 11 | 12 | "github.com/alecthomas/chroma" 13 | "github.com/golang/freetype/truetype" 14 | "golang.org/x/image/font" 15 | "golang.org/x/image/math/fixed" 16 | ) 17 | 18 | type pngFormat struct { 19 | fontSize int 20 | width, height int 21 | lineWidth int 22 | printLine bool 23 | } 24 | 25 | func (p *pngFormat) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) error { 26 | f, err := Assets.Open("/font/Cica-Regular.ttf") 27 | defer f.Close() 28 | 29 | b := &bytes.Buffer{} 30 | if _, err := io.Copy(b, f); err != nil { 31 | return err 32 | } 33 | 34 | ft, err := truetype.Parse(b.Bytes()) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | opt := truetype.Options{ 40 | Size: float64(p.fontSize), 41 | } 42 | face := truetype.NewFace(ft, &opt) 43 | 44 | bg := style.Get(chroma.Background).Background 45 | bgColor := color.RGBA{R: bg.Red(), G: bg.Green(), B: bg.Blue(), A: 255} 46 | 47 | img := image.NewRGBA(image.Rect(0, 0, p.width, p.height)) 48 | draw.Draw(img, img.Bounds(), &image.Uniform{C: bgColor}, image.ZP, draw.Src) 49 | 50 | dr := &font.Drawer{ 51 | Dst: img, 52 | Src: image.White, 53 | Face: face, 54 | } 55 | 56 | // draw line 57 | if p.printLine { 58 | i := 1 59 | if bg.Brightness() < 0.5 { 60 | dr.Src = image.NewUniform(color.RGBA{200, 200, 200, 200}) 61 | } else { 62 | dr.Src = image.NewUniform(color.RGBA{50, 50, 50, 255}) 63 | } 64 | 65 | lx := fixed.Int26_6(2) 66 | 67 | lm := p.height/p.fontSize - 1 // remove font size 68 | format := fmt.Sprintf("%%%dd", p.lineWidth) 69 | for i < lm { 70 | dr.Dot.X = fixed.I(10) * lx 71 | dr.Dot.Y = fixed.I(p.fontSize) * fixed.Int26_6(i+1) 72 | dr.DrawString(fmt.Sprintf(format, i)) 73 | i++ 74 | } 75 | } 76 | 77 | // draw source code 78 | ox := fixed.Int26_6(p.lineWidth + 3) 79 | x := ox 80 | y := fixed.Int26_6(2) 81 | 82 | for _, t := range iterator.Tokens() { 83 | s := style.Get(t.Type) 84 | if s.Colour.IsSet() { 85 | c := s.Colour 86 | dr.Src = image.NewUniform(color.RGBA{R: c.Red(), G: c.Green(), B: c.Blue(), A: 255}) 87 | } else { 88 | c := s.Colour 89 | if c.Brightness() < 0.5 { 90 | dr.Src = image.NewUniform(color.White) 91 | } else { 92 | dr.Src = image.NewUniform(color.Black) 93 | } 94 | } 95 | 96 | for _, c := range t.String() { 97 | if c == '\n' { 98 | x = ox 99 | y++ 100 | continue 101 | } else if c == '\t' { 102 | x += fixed.Int26_6(4) 103 | continue 104 | } 105 | dr.Dot.X = fixed.I(10) * x 106 | dr.Dot.Y = fixed.I(p.fontSize) * y 107 | s := fmt.Sprintf("%c", c) 108 | dr.DrawString(s) 109 | 110 | // if mutibyte 111 | if len(s) > 2 { 112 | x = x + 2 113 | } else { 114 | x++ 115 | } 116 | } 117 | } 118 | 119 | return png.Encode(w, img) 120 | } 121 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/skanehira/code2img 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/alecthomas/chroma v0.8.0 7 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 8 | github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 9 | github.com/skanehira/clipboard-image/v2 v2.0.0 10 | golang.org/x/image v0.0.0-20200618115811-c13761719519 11 | golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= 2 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 3 | github.com/alecthomas/chroma v0.8.0 h1:HS+HE97sgcqjQGu5uVr8jIE55Mmh5UeQ7kckAhHg2pY= 4 | github.com/alecthomas/chroma v0.8.0/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= 5 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= 6 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 7 | github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= 8 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= 9 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 10 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= 11 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= 16 | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 17 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 18 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 19 | github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 h1:cW/amwGEJK5MSKntPXRjX4dxs/nGxGT8gXKIsKFmHGc= 20 | github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15/go.mod h1:Fdm/oWRW+CH8PRbLntksCNtmcCBximKPkVQYvmMl80k= 21 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 22 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 23 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 24 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 25 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 26 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 29 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 30 | github.com/skanehira/clipboard-image/v2 v2.0.0 h1:Kp+RNOgIlgzDkP3EskwuBnM0Fk4sc+HgcWE5RC+PnNI= 31 | github.com/skanehira/clipboard-image/v2 v2.0.0/go.mod h1:NXSYl4FJinIUFKJfeP1lGz8DIEUYjnEqwdMZ777S1E0= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 34 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 35 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 36 | golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34= 37 | golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 38 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= 42 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407 h1:5zh5atpUEdIc478E/ebrIaHLKcfVvG6dL/fGv7BcMoM= 44 | golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 45 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 46 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/alecthomas/chroma" 15 | "github.com/alecthomas/chroma/formatters" 16 | "github.com/alecthomas/chroma/lexers" 17 | "github.com/alecthomas/chroma/styles" 18 | "github.com/skanehira/clipboard-image/v2" 19 | "golang.org/x/term" 20 | ) 21 | 22 | var version = "1.2.0" 23 | 24 | func exitErr(err error) { 25 | fmt.Fprintln(os.Stderr, err) 26 | os.Exit(1) 27 | } 28 | 29 | type options struct { 30 | useClipboard bool 31 | source string 32 | output string 33 | ext string 34 | theme string 35 | } 36 | 37 | func main() { 38 | name := "code2img" 39 | fs := flag.NewFlagSet(name, flag.ContinueOnError) 40 | fs.SetOutput(os.Stderr) 41 | fs.Usage = func() { 42 | fs.SetOutput(os.Stdout) 43 | fmt.Printf(`%[1]s - generate image of source code 44 | 45 | Version: %s 46 | 47 | Usage: 48 | $ %[1]s -t monokai main.go main.png 49 | $ echo 'fmt.Println("Hello World")' | %[1]s -ext go -t native -o sample.png 50 | $ %[1]s -c main.go 51 | 52 | -t color theme(default: solarized-dark) 53 | -o output file name(default: out.png) 54 | -c copy to clipboard 55 | -l print line 56 | -ext file extension 57 | `, name, version) 58 | } 59 | 60 | theme := fs.String("t", "solarized-dark", "") 61 | ext := fs.String("ext", "", "") 62 | output := fs.String("o", "out.png", "") 63 | useClipboard := fs.Bool("c", false, "") 64 | printLine := fs.Bool("l", false, "") 65 | 66 | if err := fs.Parse(os.Args[1:]); err != nil { 67 | if err == flag.ErrHelp { 68 | return 69 | } 70 | os.Exit(1) 71 | } 72 | 73 | var src io.Reader 74 | 75 | // if use stdin, then require those argments 76 | if !term.IsTerminal(int(os.Stdin.Fd())) { 77 | if *ext == "" { 78 | fs.Usage() 79 | os.Exit(1) 80 | } 81 | src = os.Stdin 82 | } else { 83 | args := fs.Args() 84 | if len(args) < 1 { 85 | fs.Usage() 86 | os.Exit(1) 87 | } 88 | 89 | in := args[0] 90 | *ext = filepath.Ext(in)[1:] 91 | 92 | var err error 93 | src, err = os.Open(in) 94 | if err != nil { 95 | exitErr(err) 96 | } 97 | *ext = filepath.Ext(args[0])[1:] 98 | 99 | if !*useClipboard && len(args) > 1 { 100 | *output = args[1] 101 | } 102 | } 103 | 104 | buf := &bytes.Buffer{} 105 | if _, err := io.Copy(buf, src); err != nil { 106 | exitErr(err) 107 | } 108 | source := buf.String() 109 | 110 | fontSize := 20 111 | w, h, lw := getSize(*printLine, source, fontSize) 112 | formatters.Register("png", &pngFormat{ 113 | width: w, 114 | height: h, 115 | lineWidth: lw, 116 | fontSize: fontSize, 117 | printLine: *printLine, 118 | }) 119 | 120 | opts := options{ 121 | useClipboard: *useClipboard, 122 | source: source, 123 | output: *output, 124 | ext: *ext, 125 | theme: *theme, 126 | } 127 | if err := drawImage(opts); err != nil { 128 | exitErr(err) 129 | } 130 | } 131 | 132 | func drawImage(opt options) error { 133 | buf, err := highlight(opt) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | if opt.useClipboard { 139 | return clipboard.Write(buf) 140 | } 141 | 142 | tmp, err := ioutil.TempFile("", "") 143 | if err != nil { 144 | return err 145 | } 146 | 147 | if _, err := io.Copy(tmp, buf); err != nil { 148 | return err 149 | } 150 | tmp.Close() 151 | 152 | return os.Rename(tmp.Name(), opt.output) 153 | } 154 | 155 | func highlight(opt options) (io.Reader, error) { 156 | l := lexers.Get(opt.ext) 157 | if l == nil { 158 | l = lexers.Analyse(opt.source) 159 | } 160 | if l == nil { 161 | l = lexers.Fallback 162 | } 163 | l = chroma.Coalesce(l) 164 | 165 | f := formatters.Get("png") 166 | if f == nil { 167 | f = formatters.Fallback 168 | } 169 | 170 | s := styles.Get(opt.theme) 171 | if s == nil { 172 | s = styles.Fallback 173 | } 174 | 175 | it, err := l.Tokenise(nil, opt.source) 176 | if err != nil { 177 | return nil, err 178 | } 179 | 180 | buf := new(bytes.Buffer) 181 | if err := f.Format(buf, s, it); err != nil { 182 | return nil, err 183 | } 184 | return buf, nil 185 | } 186 | 187 | func getSize(printLine bool, s string, fontSize int) (w int, h int, lw int) { 188 | lines := strings.Split(s, "\n") 189 | for _, s := range lines { 190 | ww := len(s) + strings.Count(s, "\t")*3 191 | if ww > w { 192 | w = ww 193 | } 194 | h++ 195 | } 196 | 197 | if printLine { 198 | lw = len(strconv.Itoa(len(lines))) 199 | w = w + lw 200 | } 201 | return (w + 6) * 10, (h + 1) * fontSize, lw 202 | } 203 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ! -e tmp ];then 3 | mkdir tmp 4 | fi 5 | 6 | code=testdata/test.go 7 | 8 | ./code2img -l $code tmp/has_line.png 9 | ./code2img $code tmp/no_line.png 10 | 11 | files="has_line.png no_line.png" 12 | 13 | for f in $files;do 14 | result=$(cmp tmp/$f testdata/$f) 15 | if [ -n "$result" ];then 16 | echo $result 17 | exit 1 18 | fi 19 | done 20 | 21 | rm -rf tmp 22 | -------------------------------------------------------------------------------- /testdata/has_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skanehira/code2img/c32f0da14d77b708c7383687305ed203f330e277/testdata/has_line.png -------------------------------------------------------------------------------- /testdata/no_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skanehira/code2img/c32f0da14d77b708c7383687305ed203f330e277/testdata/no_line.png -------------------------------------------------------------------------------- /testdata/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | 10 | xj "github.com/basgys/goxml2json" 11 | ) 12 | 13 | func toJSON(xml io.Reader) string { 14 | json, err := xj.Convert(xml) 15 | if err != nil { 16 | return err.Error() 17 | } 18 | 19 | return json.String() 20 | } 21 | 22 | func main() { 23 | log.SetFlags(log.LstdFlags | log.Lshortfile) 24 | 25 | flag.Parse() 26 | 27 | if flag.NArg() < 1 { 28 | fmt.Println("Usage: xj file") 29 | os.Exit(0) 30 | } 31 | 32 | file, err := os.Open(flag.Args()[0]) 33 | if err != nil { 34 | log.Println(err) 35 | os.Exit(1) 36 | } 37 | defer file.Close() 38 | 39 | fmt.Println(toJSON(file)) 40 | } 41 | --------------------------------------------------------------------------------