Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ freeze artichoke.hs --theme dracula

### Output

Change the output file location, defaults to `out.svg` or stdout if piped. This
Change the output file location, defaults to `freeze.png` or stdout if piped. This
value supports `.svg`, `.png`, `.webp`.

```bash
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ require (
github.com/charmbracelet/x/term v0.2.1
github.com/charmbracelet/x/xpty v0.1.2
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-runewidth v0.0.16
)

Expand All @@ -40,6 +39,7 @@ require (
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
Expand Down
62 changes: 22 additions & 40 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime/debug"
"strings"

Expand All @@ -19,7 +18,6 @@ import (
"github.com/charmbracelet/log"
"github.com/charmbracelet/x/ansi"
"github.com/charmbracelet/x/cellbuf"
"github.com/mattn/go-isatty"

in "github.com/charmbracelet/freeze/input"
"github.com/charmbracelet/freeze/svg"
Expand Down Expand Up @@ -129,10 +127,6 @@ func main() {
autoHeight := config.Height == 0
autoWidth := config.Width == 0

if config.Output == "" {
config.Output = defaultOutputFilename
}

scale = 1
if autoHeight && autoWidth && strings.HasSuffix(config.Output, ".png") {
scale = 4
Expand Down Expand Up @@ -392,10 +386,12 @@ func main() {
}
}

istty := isatty.IsTerminal(os.Stdout.Fd())

isOutputPiped := in.IsPipe(os.Stdout)
switch {
case strings.HasSuffix(config.Output, ".png"):
case strings.HasSuffix(config.Output, ".png") || (!isOutputPiped && config.Output == ""):
if config.Output == "" {
config.Output = defaultOutputFilename
}
// use libsvg conversion.
svgConversionErr := libsvgConvert(doc, imageWidth, imageHeight, config.Output)
if svgConversionErr == nil {
Expand All @@ -409,48 +405,34 @@ func main() {
printErrorFatal("Unable to convert SVG to PNG", svgConversionErr)
}
printFilenameOutput(config.Output)

default:
// output file specified.
if config.Output != "" {
err = doc.WriteToFile(config.Output)
if err != nil {
printErrorFatal("Unable to write output", err)
}
printFilenameOutput(config.Output)
return
case isOutputPiped && config.Output == "":
// Piping to stdout - convert to PNG and write to stdout
// use libsvg conversion.
svgConversionErr := libsvgConvertToWriter(doc, imageWidth, imageHeight, os.Stdout)
if svgConversionErr == nil {
break
}

// reading from stdin.
if config.Input == "" || config.Input == "-" {
if istty {
err = doc.WriteToFile(defaultOutputFilename)
printFilenameOutput(defaultOutputFilename)
} else {
_, err = doc.WriteTo(os.Stdout)
}
if err != nil {
printErrorFatal("Unable to write output", err)
}
return
// could not convert with libsvg, try resvg
svgConversionErr = resvgConvertToWriter(doc, imageWidth, imageHeight, os.Stdout)
if svgConversionErr != nil {
printErrorFatal("Unable to convert SVG to PNG", svgConversionErr)
}

// reading from file.
if istty {
config.Output = strings.TrimSuffix(filepath.Base(config.Input), filepath.Ext(config.Input)) + ".svg"
err = doc.WriteToFile(config.Output)
printFilenameOutput(config.Output)
} else {
_, err = doc.WriteTo(os.Stdout)
}
default:
// output file is always specified here.
err = doc.WriteToFile(config.Output)
if err != nil {
printErrorFatal("Unable to write output", err)
}
printFilenameOutput(config.Output)
return
}
}

var outputHeader = lipgloss.NewStyle().Foreground(lipgloss.Color("#F1F1F1")).Background(lipgloss.Color("#6C50FF")).Bold(true).Padding(0, 1).MarginRight(1).SetString("WROTE")

func printFilenameOutput(filename string) {
fmt.Println(lipgloss.JoinHorizontal(lipgloss.Center, outputHeader.String(), filename))
// Print log to stderr to use stdout for the Image output when piped
fmt.Fprintln(os.Stderr, lipgloss.JoinHorizontal(lipgloss.Center, outputHeader.String(), filename))
}
56 changes: 45 additions & 11 deletions png.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
"io"
"os"
"os/exec"

Expand All @@ -11,7 +12,15 @@ import (
"github.com/kanrichan/resvg-go"
)

func libsvgConvert(doc *etree.Document, _, _ float64, output string) error {
func libsvgConvert(doc *etree.Document, w, h float64, output string) error {
return libsvgConvertToWriterOrFile(doc, nil, output)
}

func libsvgConvertToWriter(doc *etree.Document, _, _ float64, w io.Writer) error {
return libsvgConvertToWriterOrFile(doc, w, "")
}

func libsvgConvertToWriterOrFile(doc *etree.Document, w io.Writer, output string) error {
_, err := exec.LookPath("rsvg-convert")
if err != nil {
return err //nolint: wrapcheck
Expand All @@ -24,17 +33,46 @@ func libsvgConvert(doc *etree.Document, _, _ float64, output string) error {

// rsvg-convert is installed use that to convert the SVG to PNG,
// since it is faster.
rsvgConvert := exec.Command("rsvg-convert", "-o", output)
var rsvgConvert *exec.Cmd
if output != "" {
rsvgConvert = exec.Command("rsvg-convert", "-o", output)
} else {
rsvgConvert = exec.Command("rsvg-convert")
rsvgConvert.Stdout = w
}
rsvgConvert.Stdin = bytes.NewReader(svg)
err = rsvgConvert.Run()
return err //nolint: wrapcheck
}

func resvgConvert(doc *etree.Document, w, h float64, output string) error {
svg, err := doc.WriteToBytes()
png, err := resvgConvertToBytes(doc, w, h)
if err != nil {
return err
}

err = os.WriteFile(output, png, 0o600)
if err != nil {
return err //nolint: wrapcheck
}
return nil
}

func resvgConvertToWriter(doc *etree.Document, w, h float64, writer io.Writer) error {
png, err := resvgConvertToBytes(doc, w, h)
if err != nil {
return err
}

_, err = writer.Write(png)
return err //nolint: wrapcheck
}

func resvgConvertToBytes(doc *etree.Document, w, h float64) ([]byte, error) {
svg, err := doc.WriteToBytes()
if err != nil {
return nil, err //nolint: wrapcheck
}

worker, err := resvg.NewDefaultWorker(context.Background())
if err != nil {
Expand Down Expand Up @@ -79,20 +117,16 @@ func resvgConvert(doc *etree.Document, w, h float64, output string) error {

err = tree.ConvertText(fontdb)
if err != nil {
return err //nolint: wrapcheck
return nil, err //nolint: wrapcheck
}
err = tree.Render(resvg.TransformIdentity(), pixmap)
if err != nil {
return err //nolint: wrapcheck
return nil, err //nolint: wrapcheck
}
png, err := pixmap.EncodePNG()
if err != nil {
return err //nolint: wrapcheck
return nil, err //nolint: wrapcheck
}

err = os.WriteFile(output, png, 0o600)
if err != nil {
return err //nolint: wrapcheck
}
return err //nolint: wrapcheck
return png, nil
}