diff --git a/cli.go b/cli.go index d9800b8..57d1442 100644 --- a/cli.go +++ b/cli.go @@ -1,4 +1,4 @@ -// Copyright (c) 2024 David Vogel +// Copyright (c) 2024-2025 David Vogel // // This software is released under the MIT License. // https://opensource.org/licenses/MIT @@ -80,22 +80,17 @@ func (c CLI) Compile(input io.Reader, output io.Writer, options *CLIOptions) err return nil } -// Compile takes a typst document from input, and renders it into the output writer. +// CompileWithVariables takes a typst document from input, and renders it into the output writer. // The options parameter is optional. // // Additionally this will inject the given map of variables into the global scope of the typst document. +// +// Deprecated: You should use InjectValues in combination with the normal Compile method instead. func (c CLI) CompileWithVariables(input io.Reader, output io.Writer, options *CLIOptions, variables map[string]any) error { varBuffer := bytes.Buffer{} - // TODO: Use io.pipe instead of a bytes.Buffer - - enc := NewVariableEncoder(&varBuffer) - for k, v := range variables { - varBuffer.WriteString("#let " + CleanIdentifier(k) + " = ") - if err := enc.Encode(v); err != nil { - return fmt.Errorf("failed to encode variables with key %q: %w", k, err) - } - varBuffer.WriteRune('\n') + if err := InjectValues(&varBuffer, variables); err != nil { + return fmt.Errorf("failed to inject values into Typst markup: %w", err) } reader := io.MultiReader(&varBuffer, input) diff --git a/image.go b/image.go index 3433439..476b2e2 100644 --- a/image.go +++ b/image.go @@ -1,3 +1,8 @@ +// Copyright (c) 2024-2025 David Vogel +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + package typst import ( @@ -22,8 +27,10 @@ func (i Image) MarshalTypstVariable() ([]byte, error) { // TODO: Make image encoding more efficient: Use reader/writer, baseXX encoding + // TODO: Consider using raw pixel encoding instead of PNG + var buf bytes.Buffer - buf.WriteString("image.decode(bytes((") + buf.WriteString("image.decode(bytes((") // TODO: Pass bytes directly to image once Typst 0.12.0 is not supported anymore for _, b := range buffer.Bytes() { buf.WriteString(strconv.FormatUint(uint64(b), 10) + ",") } diff --git a/image_test.go b/image_test.go index 1c6f8d3..90e4d6b 100644 --- a/image_test.go +++ b/image_test.go @@ -33,18 +33,24 @@ func (p *testImage) Opaque() bool { } func TestImage(t *testing.T) { - img := &testImage{image.Rect(0, 0, 255, 255)} + img := &testImage{image.Rect(0, 0, 256, 256)} // Wrap image. typstImage := typst.Image{img} cli := typst.CLI{} - r := bytes.NewBufferString(`= Image test + var r bytes.Buffer -#TestImage`) + if err := typst.InjectValues(&r, map[string]any{"TestImage": typstImage}); err != nil { + t.Fatalf("Failed to inject values into Typst markup: %v.", err) + } - if err := cli.CompileWithVariables(r, io.Discard, nil, map[string]any{"TestImage": typstImage}); err != nil { + r.WriteString(`= Image test + +#TestImage`) // TODO: Add assertion for the image width and height as soon as it's possible to query that + + if err := cli.Compile(&r, io.Discard, nil); err != nil { t.Fatalf("Failed to compile document: %v.", err) } } diff --git a/util.go b/util.go new file mode 100644 index 0000000..7479c23 --- /dev/null +++ b/util.go @@ -0,0 +1,42 @@ +// Copyright (c) 2025 David Vogel +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package typst + +import ( + "fmt" + "io" +) + +// InjectValues will write the given key-value pairs as Typst markup into output. +// This can be used to inject Go values into typst documents. +// +// Every key in values needs to be a valid identifier, otherwise this function will return an error. +// Every value in values will be marshaled according to VariableEncoder into equivalent Typst markup. +// +// Passing {"foo": 1, "bar": 60 * time.Second} as values will produce the following output: +// +// #let foo = 1 +// #let bar = duration(seconds: 60) +func InjectValues(output io.Writer, values map[string]any) error { + enc := NewVariableEncoder(output) + + for k, v := range values { + if !IsIdentifier(k) { + return fmt.Errorf("%q is not a valid identifier", k) + } + if _, err := output.Write([]byte("#let " + CleanIdentifier(k) + " = ")); err != nil { + return err + } + if err := enc.Encode(v); err != nil { + return fmt.Errorf("failed to encode variables with key %q: %w", k, err) + } + if _, err := output.Write([]byte("\n")); err != nil { + return err + } + } + + return nil +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..ffa5f2f --- /dev/null +++ b/util_test.go @@ -0,0 +1,36 @@ +package typst + +import ( + "bytes" + "testing" + "time" +) + +func TestInjectValues(t *testing.T) { + type args struct { + values map[string]any + } + tests := []struct { + name string + args args + wantOutput string + wantErr bool + }{ + {"empty", args{values: nil}, "", false}, + {"nil", args{values: map[string]any{"foo": nil}}, "#let foo = none\n", false}, + {"example", args{values: map[string]any{"foo": 1, "bar": 60 * time.Second}}, "#let foo = 1\n#let bar = duration(seconds: 60)\n", false}, + {"invalid identifier", args{values: map[string]any{"foo😀": 1}}, "", true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := &bytes.Buffer{} + if err := InjectValues(output, tt.args.values); (err != nil) != tt.wantErr { + t.Errorf("InjectValues() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotOutput := output.String(); gotOutput != tt.wantOutput { + t.Errorf("InjectValues() = %v, want %v", gotOutput, tt.wantOutput) + } + }) + } +}