98e000e3be
Resolve GitHub issue #2 by aligning the Go module identity with the repository path so works. Changes made: - Updated go.mod module path from github.com/rcarmo/webterm-go-port to github.com/rcarmo/webterm. - Updated all internal import references to the new module path. - Updated version ldflags in Makefile and Dockerfile to use github.com/rcarmo/webterm/webterm.Version. - Added README quick-install section documenting the command. Validation: - Ran make check successfully after the rename/import updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
151 lines
4.5 KiB
Go
151 lines
4.5 KiB
Go
package webterm
|
|
|
|
import (
|
|
"fmt"
|
|
"html"
|
|
"strings"
|
|
|
|
"github.com/rcarmo/webterm/internal/terminalstate"
|
|
)
|
|
|
|
var ansiColors = map[string]string{
|
|
"black": "#000000",
|
|
"red": "#cc0000",
|
|
"green": "#4e9a06",
|
|
"yellow": "#c4a000",
|
|
"blue": "#3465a4",
|
|
"magenta": "#75507b",
|
|
"cyan": "#06989a",
|
|
"white": "#d3d7cf",
|
|
"brightblack": "#555753",
|
|
"brightred": "#ef2929",
|
|
"brightgreen": "#8ae234",
|
|
"brightyellow": "#fce94f",
|
|
"brightblue": "#729fcf",
|
|
"brightmagenta": "#ad7fa8",
|
|
"brightcyan": "#34e2e2",
|
|
"brightwhite": "#eeeeec",
|
|
"gray": "#555753",
|
|
"grey": "#555753",
|
|
"lightgray": "#d3d7cf",
|
|
"lightgrey": "#d3d7cf",
|
|
"brown": "#c4a000",
|
|
}
|
|
|
|
func RenderTerminalSVG(
|
|
buffer [][]terminalstate.Cell,
|
|
width, height int,
|
|
title, background, foreground string,
|
|
palette map[string]string,
|
|
) string {
|
|
if background == "" {
|
|
background = "#000000"
|
|
}
|
|
if foreground == "" {
|
|
foreground = "#d3d7cf"
|
|
}
|
|
if title == "" {
|
|
title = "webterm"
|
|
}
|
|
if palette == nil {
|
|
palette = ansiColors
|
|
}
|
|
fontSize := 14.0
|
|
charWidth := 8.0
|
|
lineHeight := 1.2
|
|
cellHeight := fontSize * lineHeight
|
|
svgWidth := float64(width)*charWidth + 20
|
|
svgHeight := float64(height)*cellHeight + 20
|
|
|
|
var b strings.Builder
|
|
b.WriteString(fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %.1f %.1f" class="terminal-svg">`, svgWidth, svgHeight))
|
|
b.WriteString("<title>" + html.EscapeString(title) + "</title>")
|
|
b.WriteString(`<defs><style>@font-face{font-family:"FiraCode Nerd Font";src:url("/static/fonts/FiraCodeNerdFont-Regular.ttf") format("truetype");font-style:normal;font-weight:400}@font-face{font-family:"FiraMono Nerd Font";src:url("/static/fonts/FiraCodeNerdFont-Regular.ttf") format("truetype");font-style:normal;font-weight:400}.terminal-bg{fill:` + background + `}.terminal-text{font-family:var(--webterm-mono,ui-monospace,"SFMono-Regular","FiraCode Nerd Font","FiraMono Nerd Font","Fira Code",Menlo,Monaco,Consolas,"Liberation Mono","DejaVu Sans Mono","Courier New",monospace);font-size:14px;fill:` + foreground + `;white-space:pre;text-rendering:optimizeLegibility}.bold{font-weight:bold}.italic{font-style:italic}.underline{text-decoration:underline}</style></defs>`)
|
|
b.WriteString(fmt.Sprintf(`<rect class="terminal-bg" x="0" y="0" width="%.1f" height="%.1f"/>`, svgWidth, svgHeight))
|
|
b.WriteString(`<g class="terminal-text">`)
|
|
for rowIdx := 0; rowIdx < len(buffer); rowIdx++ {
|
|
row := buffer[rowIdx]
|
|
rectY := 10 + float64(rowIdx)*cellHeight
|
|
textY := rectY + fontSize
|
|
var rowText strings.Builder
|
|
for col := 0; col < len(row); col++ {
|
|
cell := row[col]
|
|
charData := cell.Data
|
|
if charData == "" {
|
|
continue
|
|
}
|
|
x := 10 + float64(col)*charWidth
|
|
fg := colorToHex(cell.FG, true, palette, foreground, background)
|
|
bg := colorToHex(cell.BG, false, palette, foreground, background)
|
|
if cell.Reverse {
|
|
fg, bg = bg, fg
|
|
}
|
|
if bg != background {
|
|
b.WriteString(fmt.Sprintf(`<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="%s"/>`, x, rectY, charWidth+0.5, cellHeight+0.5, bg))
|
|
}
|
|
attrs := []string{fmt.Sprintf(`x="%.1f"`, x)}
|
|
if fg != foreground {
|
|
attrs = append(attrs, `fill="`+fg+`"`)
|
|
}
|
|
classes := []string{}
|
|
if cell.Bold {
|
|
classes = append(classes, "bold")
|
|
}
|
|
if cell.Italics {
|
|
classes = append(classes, "italic")
|
|
}
|
|
if cell.Underscore {
|
|
classes = append(classes, "underline")
|
|
}
|
|
if len(classes) > 0 {
|
|
attrs = append(attrs, `class="`+strings.Join(classes, " ")+`"`)
|
|
}
|
|
rowText.WriteString(`<tspan ` + strings.Join(attrs, " ") + `>` + html.EscapeString(charData) + `</tspan>`)
|
|
}
|
|
if rowText.Len() > 0 {
|
|
b.WriteString(fmt.Sprintf(`<text y="%.1f">%s</text>`, textY, rowText.String()))
|
|
}
|
|
}
|
|
b.WriteString(`</g></svg>`)
|
|
return b.String()
|
|
}
|
|
|
|
func colorToHex(color string, isFG bool, palette map[string]string, defaultFG, defaultBG string) string {
|
|
if color == "" || strings.EqualFold(color, "default") {
|
|
if isFG {
|
|
return defaultFG
|
|
}
|
|
return defaultBG
|
|
}
|
|
if strings.HasPrefix(color, "#") {
|
|
return color
|
|
}
|
|
if len(color) == 6 && isHex(color) {
|
|
return "#" + color
|
|
}
|
|
key := strings.ToLower(color)
|
|
if value, ok := palette[key]; ok {
|
|
return value
|
|
}
|
|
if value, ok := ansiColors[key]; ok {
|
|
return value
|
|
}
|
|
if isFG {
|
|
return defaultFG
|
|
}
|
|
return defaultBG
|
|
}
|
|
|
|
func isHex(value string) bool {
|
|
for _, ch := range value {
|
|
switch {
|
|
case ch >= '0' && ch <= '9':
|
|
case ch >= 'a' && ch <= 'f':
|
|
case ch >= 'A' && ch <= 'F':
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|