mirror of
				https://github.com/Dadido3/go-typst.git
				synced 2025-11-04 05:09:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			470 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) 2024-2025 David Vogel
 | 
						|
//
 | 
						|
// This software is released under the MIT License.
 | 
						|
// https://opensource.org/licenses/MIT
 | 
						|
 | 
						|
package typst
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"cmp"
 | 
						|
	"encoding"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"math"
 | 
						|
	"reflect"
 | 
						|
	"slices"
 | 
						|
	"strconv"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// MarshalValue takes any Go type and returns a Typst markup representation as a byte slice.
 | 
						|
func MarshalValue(v any) ([]byte, error) {
 | 
						|
	var buf bytes.Buffer
 | 
						|
 | 
						|
	enc := NewValueEncoder(&buf)
 | 
						|
	if err := enc.Encode(v); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return buf.Bytes(), nil
 | 
						|
}
 | 
						|
 | 
						|
// ValueMarshaler can be implemented by types to support custom Typst marshaling.
 | 
						|
type ValueMarshaler interface {
 | 
						|
	MarshalTypstValue() ([]byte, error)
 | 
						|
}
 | 
						|
 | 
						|
type ValueEncoder struct {
 | 
						|
	indentLevel int
 | 
						|
 | 
						|
	writer io.Writer
 | 
						|
}
 | 
						|
 | 
						|
// NewValueEncoder returns a new encoder that writes into w.
 | 
						|
func NewValueEncoder(w io.Writer) *ValueEncoder {
 | 
						|
	return &ValueEncoder{
 | 
						|
		writer: w,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) Encode(v any) error {
 | 
						|
	return e.marshal(reflect.ValueOf(v))
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) writeString(s string) error {
 | 
						|
	return e.writeBytes([]byte(s))
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) writeRune(r rune) error {
 | 
						|
	return e.writeBytes([]byte{byte(r)})
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) writeStringLiteral(s []byte) error {
 | 
						|
	dst := make([]byte, 0, len(s)+5)
 | 
						|
 | 
						|
	dst = append(dst, '"')
 | 
						|
 | 
						|
	for _, r := range s {
 | 
						|
		switch r {
 | 
						|
		case '\\', '"':
 | 
						|
			dst = append(dst, '\\', r)
 | 
						|
		case '\n':
 | 
						|
			dst = append(dst, '\\', 'n')
 | 
						|
		case '\r':
 | 
						|
			dst = append(dst, '\\', 'r')
 | 
						|
		case '\t':
 | 
						|
			dst = append(dst, '\\', 't')
 | 
						|
		default:
 | 
						|
			dst = append(dst, r)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	dst = append(dst, '"')
 | 
						|
 | 
						|
	return e.writeBytes(dst)
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) writeBytes(b []byte) error {
 | 
						|
	if _, err := e.writer.Write(b); err != nil {
 | 
						|
		return fmt.Errorf("failed to write into writer: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) writeIndentationCharacters() error {
 | 
						|
	return e.writeBytes(slices.Repeat([]byte{' ', ' '}, e.indentLevel))
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) marshal(v reflect.Value) error {
 | 
						|
	if !v.IsValid() {
 | 
						|
		return e.writeString("none")
 | 
						|
		//return fmt.Errorf("invalid reflect.Value %v", v)
 | 
						|
	}
 | 
						|
 | 
						|
	t := v.Type()
 | 
						|
 | 
						|
	switch i := v.Interface().(type) {
 | 
						|
	case time.Time:
 | 
						|
		if err := e.encodeTime(i); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	case *time.Time:
 | 
						|
		if i == nil {
 | 
						|
			return e.writeString("none")
 | 
						|
		}
 | 
						|
		if err := e.encodeTime(*i); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	case time.Duration:
 | 
						|
		if err := e.encodeDuration(i); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	case *time.Duration:
 | 
						|
		if i == nil {
 | 
						|
			return e.writeString("none")
 | 
						|
		}
 | 
						|
		if err := e.encodeDuration(*i); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if t.Implements(reflect.TypeFor[ValueMarshaler]()) {
 | 
						|
		if m, ok := v.Interface().(ValueMarshaler); ok {
 | 
						|
			bytes, err := m.MarshalTypstValue()
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("error calling MarshalTypstValue for type %s: %w", t.String(), err)
 | 
						|
			}
 | 
						|
			return e.writeBytes(bytes)
 | 
						|
		}
 | 
						|
		return e.writeString("none")
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO: Remove this in a future update, it's only here for compatibility reasons
 | 
						|
	if t.Implements(reflect.TypeFor[VariableMarshaler]()) {
 | 
						|
		if m, ok := v.Interface().(VariableMarshaler); ok {
 | 
						|
			bytes, err := m.MarshalTypstVariable()
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("error calling MarshalTypstVariable for type %s: %w", t.String(), err)
 | 
						|
			}
 | 
						|
			return e.writeBytes(bytes)
 | 
						|
		}
 | 
						|
		return e.writeString("none")
 | 
						|
	}
 | 
						|
 | 
						|
	if t.Implements(reflect.TypeFor[encoding.TextMarshaler]()) {
 | 
						|
		if m, ok := v.Interface().(encoding.TextMarshaler); ok {
 | 
						|
			b, err := m.MarshalText()
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("error calling MarshalText for type %s: %w", t.String(), err)
 | 
						|
			}
 | 
						|
			return e.writeStringLiteral(b)
 | 
						|
		}
 | 
						|
		return e.writeString("none")
 | 
						|
	}
 | 
						|
 | 
						|
	var err error
 | 
						|
	switch t.Kind() {
 | 
						|
	case reflect.Bool:
 | 
						|
		err = e.writeString(strconv.FormatBool(v.Bool()))
 | 
						|
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
						|
		if v.Int() >= 0 {
 | 
						|
			err = e.writeString(strconv.FormatInt(v.Int(), 10))
 | 
						|
		} else {
 | 
						|
			if err = e.writeRune('{'); err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			if err = e.writeString(strconv.FormatInt(v.Int(), 10)); err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			if err = e.writeRune('}'); err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 | 
						|
		err = e.writeString(strconv.FormatUint(v.Uint(), 10))
 | 
						|
	case reflect.Float32, reflect.Float64:
 | 
						|
		f := v.Float()
 | 
						|
		switch {
 | 
						|
		case math.IsNaN(f):
 | 
						|
			err = e.writeString("float.nan")
 | 
						|
		case math.IsInf(f, 1):
 | 
						|
			err = e.writeString("float.inf")
 | 
						|
		case math.IsInf(f, -1):
 | 
						|
			err = e.writeString("{-float.inf}")
 | 
						|
		case math.Signbit(f):
 | 
						|
			if err = e.writeRune('{'); err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			if err = e.writeString(strconv.FormatFloat(f, 'e', -1, 64)); err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
			if err = e.writeRune('}'); err != nil {
 | 
						|
				break
 | 
						|
			}
 | 
						|
		default:
 | 
						|
			err = e.writeString(strconv.FormatFloat(f, 'e', -1, 64))
 | 
						|
		}
 | 
						|
	case reflect.String:
 | 
						|
		return e.encodeString(v)
 | 
						|
	case reflect.Interface, reflect.Pointer:
 | 
						|
		if v.IsNil() {
 | 
						|
			return e.writeString("none")
 | 
						|
		}
 | 
						|
		return e.marshal(v.Elem())
 | 
						|
	case reflect.Map:
 | 
						|
		return e.encodeMap(v)
 | 
						|
	case reflect.Struct:
 | 
						|
		return e.encodeStruct(v, t)
 | 
						|
	case reflect.Slice:
 | 
						|
		return e.encodeSlice(v, t)
 | 
						|
	case reflect.Array:
 | 
						|
		return e.encodeArray(v)
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("unsupported type %q", t.String())
 | 
						|
	}
 | 
						|
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) encodeString(v reflect.Value) error {
 | 
						|
	return e.writeStringLiteral([]byte(v.String()))
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) encodeStruct(v reflect.Value, t reflect.Type) error {
 | 
						|
	if v.NumField() == 0 {
 | 
						|
		return e.writeString("()")
 | 
						|
	}
 | 
						|
 | 
						|
	if err := e.writeString("(\n"); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	e.indentLevel++
 | 
						|
 | 
						|
	for i := 0; i < t.NumField(); i++ {
 | 
						|
		ft, fv := t.Field(i), v.Field(i)
 | 
						|
		if ft.PkgPath == "" { // Ignore unexported fields.
 | 
						|
			fieldName := ft.Name
 | 
						|
			if name, ok := ft.Tag.Lookup("typst"); ok {
 | 
						|
				// Omit fields that have their name set to "-".
 | 
						|
				if name == "-" {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
				fieldName = name
 | 
						|
			}
 | 
						|
 | 
						|
			if err := e.writeIndentationCharacters(); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if err := e.writeStringLiteral([]byte(fieldName)); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if err := e.writeString(": "); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if err := e.marshal(fv); err != nil {
 | 
						|
				return fmt.Errorf("failed to encode value of struct field %q: %w", ft.Name, err)
 | 
						|
			}
 | 
						|
			if err := e.writeString(",\n"); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	e.indentLevel--
 | 
						|
 | 
						|
	if err := e.writeIndentationCharacters(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return e.writeRune(')')
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) resolveKeyName(v reflect.Value) (string, error) {
 | 
						|
	// From encoding/json/encode.go.
 | 
						|
	if v.Kind() == reflect.String {
 | 
						|
		return v.String(), nil
 | 
						|
	}
 | 
						|
	if tm, ok := v.Interface().(encoding.TextMarshaler); ok {
 | 
						|
		if v.Kind() == reflect.Pointer && v.IsNil() {
 | 
						|
			return "", nil
 | 
						|
		}
 | 
						|
		buf, err := tm.MarshalText()
 | 
						|
		return string(buf), err
 | 
						|
	}
 | 
						|
	switch v.Kind() {
 | 
						|
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 | 
						|
		return strconv.FormatInt(v.Int(), 10), nil
 | 
						|
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 | 
						|
		return strconv.FormatUint(v.Uint(), 10), nil
 | 
						|
	}
 | 
						|
	return "", fmt.Errorf("unsupported map key type %q", v.Type().String())
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) encodeMap(v reflect.Value) error {
 | 
						|
	if v.Len() == 0 {
 | 
						|
		return e.writeString("()")
 | 
						|
	}
 | 
						|
 | 
						|
	if err := e.writeString("(\n"); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	e.indentLevel++
 | 
						|
 | 
						|
	type pair struct {
 | 
						|
		key   string
 | 
						|
		value reflect.Value
 | 
						|
	}
 | 
						|
 | 
						|
	// Get all key value pairs as reflect.Value.
 | 
						|
	mi := v.MapRange()
 | 
						|
	pairs := make([]pair, 0, v.Len())
 | 
						|
	for mi.Next() {
 | 
						|
		mk, mv := mi.Key(), mi.Value()
 | 
						|
		key, err := e.resolveKeyName(mk)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		pairs = append(pairs, pair{key, mv})
 | 
						|
	}
 | 
						|
 | 
						|
	// Sort and then generate markup.
 | 
						|
	slices.SortFunc(pairs, func(a, b pair) int { return cmp.Compare(a.key, b.key) })
 | 
						|
	for _, pair := range pairs {
 | 
						|
		key, value := pair.key, pair.value
 | 
						|
 | 
						|
		if err := e.writeIndentationCharacters(); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if err := e.writeStringLiteral([]byte(key)); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if err := e.writeString(": "); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if err := e.marshal(value); err != nil {
 | 
						|
			return fmt.Errorf("failed to encode map field %q: %w", key, err)
 | 
						|
		}
 | 
						|
 | 
						|
		if err := e.writeString(",\n"); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	e.indentLevel--
 | 
						|
 | 
						|
	if err := e.writeIndentationCharacters(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return e.writeRune(')')
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) EncodeByteSlice(bb []byte) error {
 | 
						|
	if err := e.writeString("bytes(("); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO: Encode byte slice via base64 or similar and use a Typst package to convert it into the corresponding bytes type
 | 
						|
 | 
						|
	for i, b := range bb {
 | 
						|
		if i > 0 {
 | 
						|
			if err := e.writeString(", "); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if err := e.writeString(strconv.FormatUint(uint64(b), 10)); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(bb) == 1 {
 | 
						|
		if err := e.writeRune(','); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return e.writeString("))")
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) encodeSlice(v reflect.Value, t reflect.Type) error {
 | 
						|
 | 
						|
	// Special case for byte slices.
 | 
						|
	if t.Elem().Kind() == reflect.Uint8 {
 | 
						|
		return e.EncodeByteSlice(v.Bytes())
 | 
						|
	}
 | 
						|
 | 
						|
	if err := e.writeRune('('); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	n := v.Len()
 | 
						|
	for i := 0; i < n; i++ {
 | 
						|
		if i > 0 {
 | 
						|
			if err := e.writeString(", "); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if err := e.marshal(v.Index(i)); err != nil {
 | 
						|
			return fmt.Errorf("failed to encode slice element %d of %d: %w", i+1, n+1, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if n == 1 {
 | 
						|
		if err := e.writeRune(','); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return e.writeRune(')')
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) encodeArray(v reflect.Value) error {
 | 
						|
	if err := e.writeRune('('); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	n := v.Len()
 | 
						|
	for i := 0; i < n; i++ {
 | 
						|
		if i > 0 {
 | 
						|
			if err := e.writeString(", "); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if err := e.marshal(v.Index(i)); err != nil {
 | 
						|
			return fmt.Errorf("failed to encode array element %d of %d: %w", i+1, n+1, err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if n == 1 {
 | 
						|
		if err := e.writeRune(','); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return e.writeRune(')')
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) encodeTime(t time.Time) error {
 | 
						|
	return e.writeString(fmt.Sprintf("datetime(year: %d, month: %d, day: %d, hour: %d, minute: %d, second: %d)",
 | 
						|
		t.Year(),
 | 
						|
		t.Month(),
 | 
						|
		t.Day(),
 | 
						|
		t.Hour(),
 | 
						|
		t.Minute(),
 | 
						|
		t.Second(),
 | 
						|
	))
 | 
						|
}
 | 
						|
 | 
						|
func (e *ValueEncoder) encodeDuration(d time.Duration) error {
 | 
						|
	return e.writeString(fmt.Sprintf("duration(seconds: %d)", int(math.Round(d.Seconds()))))
 | 
						|
}
 |