From 513d2ae9063440ca1d44288f783732c2133f55f9 Mon Sep 17 00:00:00 2001 From: David Vogel Date: Tue, 4 Nov 2025 23:17:17 +0100 Subject: [PATCH] Add ability to encode raw byte slices as Typst images --- README.md | 3 ++- image.go | 25 ++++++++++++++++++++++++- image_test.go | 27 +++++++++++++++++++++++++++ test-files/test.png | Bin 0 -> 405 bytes 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 test-files/test.png diff --git a/README.md b/README.md index 5a88616..57762a6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ Use at your own discretion for production systems. - PDF, SVG and PNG generation. - All Typst parameters are discoverable and documented in [cli-options.go](cli-options.go). -- Go-to-Typst Value Encoder: Seamlessly inject any Go values (Including `image.Image` with a [wrapper](image.go)) into Typst documents via the provided encoder. +- Go-to-Typst Value Encoder: Seamlessly inject any Go values. +- Encode and inject images as a Typst markup simply by [wrapping](image.go) `image.Image` types or byte slices with raw JPEG or PNG data. - Errors from Typst CLI are returned as structured Go error objects with detailed information, such as line numbers and file paths. - Uses stdio; No temporary files will be created. - Good unit test coverage. diff --git a/image.go b/image.go index 4d0263c..7024a59 100644 --- a/image.go +++ b/image.go @@ -15,7 +15,10 @@ import ( // 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 MarshalValue or a ValueEncoder. +// For this, just wrap any image.Image with this type before passing it to MarshalValue or a ValueEncoder: +// +// typstImage := typst.Image{img} +// typst.InjectValues(&r, map[string]any{"TestImage": typstImage}) type Image struct{ image.Image } func (i Image) MarshalTypstValue() ([]byte, error) { @@ -38,3 +41,23 @@ func (i Image) MarshalTypstValue() ([]byte, error) { return buf.Bytes(), nil } + +// ImageRaw can be used to pass the raw data of any image to Typst. +// This will pass the raw byte values of a PNG, JPEG or any other image format that is supported by Typst. +// +// For this, just wrap any byte slice with this type before passing it to MarshalValue or a ValueEncoder: +// +// typstImage := typst.ImageRaw(bufferPNG) +// typst.InjectValues(&r, map[string]any{"TestImage": typstImage}) +type ImageRaw []byte + +func (i ImageRaw) MarshalTypstValue() ([]byte, error) { + var buf bytes.Buffer + buf.WriteString("image.decode(bytes((") // TODO: Pass bytes directly to image once Typst 0.12.0 is not supported anymore + for _, b := range i { + buf.WriteString(strconv.FormatUint(uint64(b), 10) + ",") + } + buf.WriteString(")))") + + return buf.Bytes(), nil +} diff --git a/image_test.go b/image_test.go index 1a35826..3089fad 100644 --- a/image_test.go +++ b/image_test.go @@ -7,6 +7,7 @@ package typst_test import ( "bytes" + _ "embed" "image" "image/color" "io" @@ -61,3 +62,29 @@ func TestImage(t *testing.T) { t.Fatalf("Failed to compile document: %v.", err) } } + +//go:embed test-files/test.png +var testPNG []byte + +func TestImageRaw(t *testing.T) { + // Wrap image. + typstImage := typst.ImageRaw(testPNG) + + cli := typst.CLI{} + + var r bytes.Buffer + + if err := typst.InjectValues(&r, map[string]any{"TestImage": typstImage}); err != nil { + t.Fatalf("Failed to inject values into Typst markup: %v.", err) + } + + r.WriteString(`= Image test + +#TestImage + +#assert(type(TestImage) == content, message: "TestImage is not of expected type: got " + str(type(TestImage)) + ", want content")`) // TODO: Add another 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/test-files/test.png b/test-files/test.png new file mode 100644 index 0000000000000000000000000000000000000000..0cfb06f13c02f9d78a094c2bb58a3f9894f16ff3 GIT binary patch literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFo-U3d5|@(`L|B*^ z*%;Xj5>f;Lf|AV4%+!uFXr~nx7AnqW+${2phoSQ}uP*ET$gMz444$rjF6*2UngAMx BO%(tD literal 0 HcmV?d00001