Files
webterm/webterm/svg_exporter.go
T
GitHub Copilot 98e000e3be Fix module path mismatch for go install
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>
2026-02-15 16:19:46 +00:00

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
}