play around with file preveiw in opengraph

wip
Carsten Kragelund Jørgensen 2023-05-03 01:40:51 +00:00 committed by Carsten Kragelund
parent 723598b803
commit b6fafb67d5
9 changed files with 1542 additions and 0 deletions

Binary file not shown.

@ -124,6 +124,8 @@ require (
xorm.io/xorm v1.3.3-0.20230219231735-056cecc97e9e
)
require github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
require (
cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
@ -135,6 +137,7 @@ require (
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/alecthomas/chroma v0.10.0
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
@ -208,7 +211,9 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jbuchbinder/gg v1.3.0
github.com/jessevdk/go-flags v1.5.0 // indirect
github.com/jiro4989/textimg/v3 v3.1.8
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect

@ -116,6 +116,8 @@ github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.5.0 h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk=
github.com/alecthomas/chroma/v2 v2.5.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
@ -509,6 +511,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -740,10 +744,14 @@ github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jbuchbinder/gg v1.3.0 h1:nfHEGrrXMCMIlLQIooBwPwn2IqEWhLQQ1s0WPmBpwdw=
github.com/jbuchbinder/gg v1.3.0/go.mod h1:V0Eu/AInMEKfU25ID6D/0FRIGj+Nz1EXId1Igjq1XI4=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jhillyerd/enmime v0.10.1 h1:3VP8gFhK7R948YJBrna5bOgnTXEuPAoICo79kKkBKfA=
github.com/jhillyerd/enmime v0.10.1/go.mod h1:Qpe8EEemJMFAF8+NZoWdpXvK2Yb9dRF0k/z6mkcDHsA=
github.com/jiro4989/textimg/v3 v3.1.8 h1:tfzkegBW59IRkpn/BLMUPTK0XRXW61xQQ4sYTzMCZDI=
github.com/jiro4989/textimg/v3 v3.1.8/go.mod h1:ohDSZdKqvQXFG9rdAAf/AVNxyvtjh32kj0nbW/zXo3k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=

@ -0,0 +1,61 @@
package image
import (
"bytes"
"encoding/base64"
"image/color"
"strings"
"github.com/jbuchbinder/gg"
"github.com/jiro4989/textimg/v3/token"
)
func Draw(tokens token.Tokens) (string, error) {
foreground := color.RGBA{205, 214, 244, 255}
background := color.RGBA{30, 30, 46, 255}
dc := gg.NewContext(1200, 630)
fgCol := foreground
bgCol := background
dc.SetColor(bgCol)
if err := dc.LoadFontFace("fonts/FiraCode-Regular.ttf", 14); err != nil {
return "", err
}
dc.Clear()
curX, curY := 0.0, 0.0
for _, t := range tokens {
switch t.Kind {
case token.KindColor:
switch t.ColorType {
case token.ColorTypeReset:
fgCol = foreground
bgCol = background
case token.ColorTypeResetForeground:
fgCol = foreground
case token.ColorTypeResetBackground:
bgCol = background
case token.ColorTypeReverse:
fgCol, bgCol = bgCol, fgCol
case token.ColorTypeForeground:
fgCol = color.RGBA(t.Color)
case token.ColorTypeBackground:
bgCol = color.RGBA(t.Color)
}
case token.KindText:
w, h := dc.MeasureMultilineString(t.Text, 1.0)
dc.SetColor(bgCol)
dc.DrawRectangle(curX, curY, w, h)
dc.SetColor(fgCol)
dc.DrawStringAnchored(strings.ReplaceAll(strings.ReplaceAll(t.Text, "\t", " "), "\n", ""), curX, curY, 0.0, 1.0)
curX += w
if strings.Contains(t.Text, "\n") {
curY += h
curX = 0
}
}
}
buffer := new(bytes.Buffer)
dc.EncodePNG(buffer)
return "data:image/png;base64," + base64.StdEncoding.EncodeToString(buffer.Bytes()), nil
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,74 @@
package parser
import (
"strconv"
"github.com/jiro4989/textimg/v3/color"
"github.com/jiro4989/textimg/v3/token"
)
type ParserFunc struct {
// pegが生成するTokensと名前が衝突するので別名にする
Tk token.Tokens
}
func Parse(s string) (token.Tokens, error) {
p := &Parser{Buffer: s}
if err := p.Init(); err != nil {
return nil, err
}
if err := p.Parse(); err != nil {
return nil, err
}
p.Execute()
return p.Tk, nil
}
func (p *ParserFunc) pushResetColor() {
p.Tk = append(p.Tk, token.NewResetColor())
}
func (p *ParserFunc) pushResetForegroundColor() {
p.Tk = append(p.Tk, token.NewResetForegroundColor())
}
func (p *ParserFunc) pushResetBackgroundColor() {
p.Tk = append(p.Tk, token.NewResetBackgroundColor())
}
func (p *ParserFunc) pushReverseColor() {
p.Tk = append(p.Tk, token.NewReverseColor())
}
func (p *ParserFunc) pushText(text string) {
p.Tk = append(p.Tk, token.NewText(text))
}
func (p *ParserFunc) pushStandardColorWithCategory(text string) {
p.Tk = append(p.Tk, token.NewStandardColorWithCategory(text))
}
func (p *ParserFunc) pushExtendedColor(text string) {
p.Tk = append(p.Tk, token.NewExtendedColor(text))
}
func (p *ParserFunc) setExtendedColor256(text string) {
n, _ := strconv.ParseUint(text, 10, 8)
p.Tk[len(p.Tk)-1].Color = color.Map256[int(n)]
}
func (p *ParserFunc) setExtendedColorR(text string) {
n, _ := strconv.ParseUint(text, 10, 8)
p.Tk[len(p.Tk)-1].Color.R = uint8(n)
}
func (p *ParserFunc) setExtendedColorG(text string) {
n, _ := strconv.ParseUint(text, 10, 8)
p.Tk[len(p.Tk)-1].Color.G = uint8(n)
}
func (p *ParserFunc) setExtendedColorB(text string) {
n, _ := strconv.ParseUint(text, 10, 8)
p.Tk[len(p.Tk)-1].Color.B = uint8(n)
}

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/formatters"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
@ -38,6 +39,7 @@ var (
cache *lru.TwoQueueCache
githubStyles = styles.Get("github")
mochaStyles = styles.Get("catppuccin-mocha")
)
// NewContext loads custom highlight map from local config
@ -197,6 +199,65 @@ func File(fileName, language string, code []byte) ([]string, string, error) {
return lines, lexerName, nil
}
func AnsiFile(fileName, language string, code []byte) ([]string, string, error) {
NewContext()
if len(code) > sizeLimit {
code = code[:sizeLimit]
}
var lexer chroma.Lexer
// provided language overrides everything
if language != "" {
lexer = lexers.Get(language)
}
if lexer == nil {
if val, ok := highlightMapping[filepath.Ext(fileName)]; ok {
lexer = lexers.Get(val)
}
}
if lexer == nil {
guessLanguage := analyze.GetCodeLanguage(fileName, code)
lexer = lexers.Get(guessLanguage)
if lexer == nil {
lexer = lexers.Match(fileName)
if lexer == nil {
lexer = lexers.Fallback
}
}
}
lexerName := formatLexerName(lexer.Config().Name)
iterator, err := lexer.Tokenise(nil, string(code))
if err != nil {
return nil, "", fmt.Errorf("can't tokenize code: %w", err)
}
tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens())
ansiBuf := &bytes.Buffer{}
formatter := formatters.TTY16m
lines := make([]string, 0, len(tokensLines))
for _, tokens := range tokensLines {
iterator = chroma.Literator(tokens...)
err = formatter.Format(ansiBuf, mochaStyles, iterator)
if err != nil {
return nil, "", fmt.Errorf("can't format code: %w", err)
}
lines = append(lines, strings.Replace(ansiBuf.String(), "\033[3m", "", -1))
ansiBuf.Reset()
}
return lines, lexerName, nil
}
// PlainText returns non-highlighted HTML for code
func PlainText(code []byte) []string {
r := bufio.NewReader(bytes.NewReader(code))

@ -27,6 +27,8 @@ import (
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/codeimage/image"
"code.gitea.io/gitea/modules/codeimage/parser"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
@ -515,6 +517,16 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
status = status.Or(statuses[i])
}
ansiContent, _, _ := highlight.AnsiFile(blob.Name(), language, buf)
log.Info("%s", ansiContent[0])
tks, _ := parser.Parse(strings.Join(ansiContent, ""))
imgStr, err := image.Draw(tks)
if err != nil {
log.Error("Drawing image failed: %v", err)
}
ctx.Data["OgImage"] = imgStr
ctx.Data["EscapeStatus"] = status
ctx.Data["FileContent"] = fileContent
ctx.Data["LineEscapeStatus"] = statuses
@ -988,6 +1000,11 @@ func renderCode(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplRepoHome)
}
func HighlightCodeToImage() error {
return nil
}
// RenderUserCards render a page show users according the input template
func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl base.TplName) {
page := ctx.FormInt("page")

@ -37,6 +37,15 @@
{{if .ContextUser.Description}}
<meta property="og:description" content="{{.ContextUser.Description}}">
{{end}}
{{else if .PageIsViewCode }}
<meta property="og:title" content="{{.Repository.Name}}">
<meta property="og:url" content="{{.Repository.HTMLURL}}">
<meta name="twitter:card" content="summary_large_card">
{{if .Repository.Description}}
<meta property="og:description" content="{{.Repository.Description}}">
{{end}}
<meta property="og:image" content="{{.OgImage}}">
<meta property="og:type" content="object">
{{else if .Repository}}
{{if .Issue}}
<meta property="og:title" content="{{.Issue.Title}}">