diff --git a/README.md b/README.md index 407fb19..73a6ca2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The supported and tested versions right now are: - PDF, SVG and PNG generation. - All typst-cli parameters are [available as a struct](cli-options.go), which makes it easy to discover all available options. -- Encoder to convert go values into typst markup which can be injected into typst documents. +- Encoder to convert go values into typst markup which can be injected into typst documents. This includes image.Image by using the [Image wrapper](image.go). - Any stderr will be returned as go error value, including line number, column and file path of the error. - Uses stdio; No temporary files will be created. - Good unit test coverage. diff --git a/cli.go b/cli.go index bad70ac..d9800b8 100644 --- a/cli.go +++ b/cli.go @@ -12,7 +12,7 @@ import ( "os/exec" ) -// TODO: Add docker support to CLI +// TODO: Add docker support to CLI, by calling docker run instead type CLI struct { ExecutablePath string // The typst executable path can be overridden here. Otherwise the default path will be used. diff --git a/image.go b/image.go new file mode 100644 index 0000000..3433439 --- /dev/null +++ b/image.go @@ -0,0 +1,33 @@ +package typst + +import ( + "bytes" + "fmt" + "image" + "image/png" + "strconv" +) + +// Image can be used to encode any image.Image into a typst image. +// +// For this, just wrap any image.Image with this type before passing it to MarshalVariable or a VariableEncoder. +type Image struct{ image.Image } + +func (i Image) MarshalTypstVariable() ([]byte, error) { + var buffer bytes.Buffer + + if err := png.Encode(&buffer, i); err != nil { + return nil, fmt.Errorf("failed to encode image as PNG: %w", err) + } + + // TODO: Make image encoding more efficient: Use reader/writer, baseXX encoding + + var buf bytes.Buffer + buf.WriteString("image.decode(bytes((") + for _, b := range buffer.Bytes() { + buf.WriteString(strconv.FormatUint(uint64(b), 10) + ",") + } + buf.WriteString(")))") + + return buf.Bytes(), nil +} diff --git a/image_test.go b/image_test.go new file mode 100644 index 0000000..1c6f8d3 --- /dev/null +++ b/image_test.go @@ -0,0 +1,50 @@ +package typst_test + +import ( + "bytes" + "image" + "image/color" + "io" + "testing" + + "github.com/Dadido3/go-typst" +) + +type testImage struct { + Rect image.Rectangle +} + +func (p *testImage) ColorModel() color.Model { return color.RGBAModel } + +func (p *testImage) Bounds() image.Rectangle { return p.Rect } + +func (p *testImage) At(x, y int) color.Color { return p.RGBAAt(x, y) } + +func (p *testImage) RGBAAt(x, y int) color.RGBA { + if !(image.Point{x, y}.In(p.Rect)) { + return color.RGBA{} + } + return color.RGBA{uint8(x), uint8(y), uint8(x + y), 255} +} + +// Opaque scans the entire image and reports whether it is fully opaque. +func (p *testImage) Opaque() bool { + return true +} + +func TestImage(t *testing.T) { + img := &testImage{image.Rect(0, 0, 255, 255)} + + // Wrap image. + typstImage := typst.Image{img} + + cli := typst.CLI{} + + r := bytes.NewBufferString(`= Image test + +#TestImage`) + + if err := cli.CompileWithVariables(r, io.Discard, nil, map[string]any{"TestImage": typstImage}); err != nil { + t.Fatalf("Failed to compile document: %v.", err) + } +}