go-hep.org/x/hep@v0.38.1/hplot/vgop/json.go (about)

     1  // Copyright ©2023 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package vgop // import "go-hep.org/x/hep/hplot/vgop"
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"sort"
    13  	"strconv"
    14  
    15  	"gonum.org/v1/plot/font"
    16  	"gonum.org/v1/plot/vg"
    17  )
    18  
    19  const (
    20  	typSetLineWidth = "SetLineWidth"
    21  	typSetLineDash  = "SetLineDash"
    22  	typSetColor     = "SetColor"
    23  	typRotate       = "Rotate"
    24  	typTranslate    = "Translate"
    25  	typScale        = "Scale"
    26  	typPush         = "Push"
    27  	typPop          = "Pop"
    28  	typStroke       = "Stroke"
    29  	typFill         = "Fill"
    30  	typFillString   = "FillString"
    31  	typDrawImage    = "DrawImage"
    32  )
    33  
    34  var (
    35  	_ vg.Canvas         = (*JSON)(nil)
    36  	_ vg.CanvasSizer    = (*JSON)(nil)
    37  	_ vg.CanvasWriterTo = (*JSON)(nil)
    38  
    39  	_ json.Marshaler   = (*JSON)(nil)
    40  	_ json.Unmarshaler = (*JSON)(nil)
    41  )
    42  
    43  // JSON implements JSON serialization for vg.Canvas.
    44  type JSON struct {
    45  	*Canvas
    46  }
    47  
    48  // NewJSON creates a new vg.Canvas for JSON serialization.
    49  func NewJSON(opts ...Option) *JSON {
    50  	return &JSON{New(opts...)}
    51  }
    52  
    53  func (c *JSON) WriteTo(w io.Writer) (int64, error) {
    54  	ww := cwriter{w: w}
    55  	enc := json.NewEncoder(&ww)
    56  	enc.SetIndent("", " ")
    57  	err := enc.Encode(c)
    58  	if err != nil {
    59  		return 0, fmt.Errorf("could not encode canvas to JSON: %w", err)
    60  	}
    61  	return int64(ww.n), nil
    62  }
    63  
    64  func (c *JSON) MarshalJSON() ([]byte, error) {
    65  	jc := jsonCanvas{
    66  		Size:  jsonSize{Width: c.w, Height: c.h},
    67  		Ops:   make([]jsonOp, len(c.ops)),
    68  		Fonts: make([]fontID, len(c.ctx.fonts)),
    69  	}
    70  
    71  	var err error
    72  	for i, op := range c.ops {
    73  		jc.Ops[i], err = jsonOpFrom(op)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  	}
    78  
    79  	keys := make([]fontID, 0, len(jc.Fonts))
    80  	for k := range c.ctx.fonts {
    81  		keys = append(keys, k)
    82  	}
    83  	sort.Slice(keys, func(i, j int) bool {
    84  		ii := font.Font(keys[i])
    85  		jj := font.Font(keys[j])
    86  		if ii.Name() != jj.Name() {
    87  			return ii.Name() < jj.Name()
    88  		}
    89  		return ii.Size < jj.Size
    90  	})
    91  	copy(jc.Fonts, keys)
    92  
    93  	return json.Marshal(jc)
    94  }
    95  
    96  func (c *JSON) UnmarshalJSON(p []byte) error {
    97  	var (
    98  		jc  jsonCanvas
    99  		err error
   100  	)
   101  
   102  	err = json.NewDecoder(bytes.NewReader(p)).Decode(&jc)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	c.w = jc.Size.Width
   108  	c.h = jc.Size.Height
   109  
   110  	c.ctx.fonts = make(map[fontID]font.Face, len(jc.Fonts))
   111  	for _, fnt := range jc.Fonts {
   112  		c.ctx.fonts[fnt] = font.Face{
   113  			Font: font.Font(fnt),
   114  			Face: nil,
   115  		}
   116  	}
   117  
   118  	c.ops = make([]op, 0, len(jc.Ops))
   119  	for _, jop := range jc.Ops {
   120  		var o op
   121  		switch jop.Type {
   122  		case typSetLineWidth:
   123  			var v opSetLineWidth
   124  			err = json.Unmarshal(jop.Value, &v)
   125  			o = v
   126  		case typSetLineDash:
   127  			var v opSetLineDash
   128  			err = json.Unmarshal(jop.Value, &v)
   129  			o = v
   130  		case typSetColor:
   131  			var v opSetColor
   132  			err = json.Unmarshal(jop.Value, &v)
   133  			o = v
   134  		case typRotate:
   135  			var v opRotate
   136  			err = json.Unmarshal(jop.Value, &v)
   137  			o = v
   138  		case typTranslate:
   139  			var v opTranslate
   140  			err = json.Unmarshal(jop.Value, &v)
   141  			o = v
   142  		case typScale:
   143  			var v opScale
   144  			err = json.Unmarshal(jop.Value, &v)
   145  			o = v
   146  		case typPush:
   147  			o = opPush{}
   148  		case typPop:
   149  			o = opPop{}
   150  		case typStroke:
   151  			var v opStroke
   152  			err = json.Unmarshal(jop.Value, &v)
   153  			o = v
   154  		case typFill:
   155  			var v opFill
   156  			err = json.Unmarshal(jop.Value, &v)
   157  			o = v
   158  		case typFillString:
   159  			var v opFillString
   160  			err = json.Unmarshal(jop.Value, &v)
   161  			o = v
   162  		case typDrawImage:
   163  			var v opDrawImage
   164  			err = json.Unmarshal(jop.Value, &v)
   165  			o = v
   166  		default:
   167  			return fmt.Errorf("invalid vgop type %q", jop.Type)
   168  		}
   169  		if err != nil {
   170  			return err
   171  		}
   172  		c.ops = append(c.ops, o)
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  type jsonCanvas struct {
   179  	Size  jsonSize `json:"size"`
   180  	Ops   []jsonOp `json:"ops"`
   181  	Fonts []fontID `json:"fonts,omitempty"`
   182  }
   183  
   184  type jsonSize struct {
   185  	Width  vg.Length `json:"width"`
   186  	Height vg.Length `json:"height"`
   187  }
   188  
   189  type jsonOp struct {
   190  	Type  string          `json:"type"`
   191  	Value json.RawMessage `json:"value"`
   192  }
   193  
   194  func jsonOpFrom(op op) (jsonOp, error) {
   195  
   196  	name := "???"
   197  	switch op := op.(type) {
   198  	case opSetLineWidth:
   199  		name = typSetLineWidth
   200  	case opSetLineDash:
   201  		name = typSetLineDash
   202  	case opSetColor:
   203  		name = typSetColor
   204  	case opRotate:
   205  		name = typRotate
   206  	case opTranslate:
   207  		name = typTranslate
   208  	case opScale:
   209  		name = typScale
   210  	case opPush:
   211  		name = typPush
   212  	case opPop:
   213  		name = typPop
   214  	case opStroke:
   215  		name = typStroke
   216  	case opFill:
   217  		name = typFill
   218  	case opFillString:
   219  		name = typFillString
   220  	case opDrawImage:
   221  		name = typDrawImage
   222  	default:
   223  		return jsonOp{}, fmt.Errorf("invalid vgop.op %T", op)
   224  	}
   225  
   226  	v, err := json.Marshal(op)
   227  	if err != nil {
   228  		return jsonOp{}, fmt.Errorf("could not encode %T: %w", op, err)
   229  	}
   230  	return jsonOp{Type: name, Value: json.RawMessage(v)}, nil
   231  }
   232  
   233  type jsonPoint struct {
   234  	X json.Number `json:"x"`
   235  	Y json.Number `json:"y"`
   236  }
   237  
   238  func jsonPointFrom(p vg.Point) jsonPoint {
   239  	return jsonPoint{
   240  		X: json.Number(strconv.FormatFloat(p.X.Points(), 'g', -1, 64)),
   241  		Y: json.Number(strconv.FormatFloat(p.Y.Points(), 'g', -1, 64)),
   242  	}
   243  }
   244  
   245  func (p jsonPoint) cnv() vg.Point {
   246  	x, err := p.X.Float64()
   247  	if err != nil {
   248  		panic(err)
   249  	}
   250  	y, err := p.Y.Float64()
   251  	if err != nil {
   252  		panic(err)
   253  	}
   254  	return vg.Point{X: vg.Length(x), Y: vg.Length(y)}
   255  }
   256  
   257  type jsonPath []jsonPathComp
   258  
   259  func jsonPathFrom(p vg.Path) jsonPath {
   260  	o := make(jsonPath, len(p))
   261  	for i := range p {
   262  		o[i] = jsonPathCompFrom(p[i])
   263  	}
   264  	return o
   265  }
   266  
   267  func (jp jsonPath) cnv() vg.Path {
   268  	o := make(vg.Path, len(jp))
   269  	for i := range jp {
   270  		o[i] = jp[i].cnv()
   271  	}
   272  
   273  	return o
   274  }
   275  
   276  type jsonPathComp struct {
   277  	Type   int         `json:"type"`
   278  	Pos    jsonPoint   `json:"pos,omitempty"`
   279  	Radius vg.Length   `json:"radius,omitempty"`
   280  	Start  float64     `json:"start,omitempty"`
   281  	Angle  float64     `json:"angle,omitempty"`
   282  	Ctl    []jsonPoint `json:"ctl,omitempty"`
   283  }
   284  
   285  func jsonPathCompFrom(p vg.PathComp) jsonPathComp {
   286  	o := jsonPathComp{
   287  		Type:   p.Type,
   288  		Pos:    jsonPointFrom(p.Pos),
   289  		Radius: p.Radius,
   290  		Start:  p.Start,
   291  		Angle:  p.Angle,
   292  	}
   293  	if len(p.Control) > 0 {
   294  		o.Ctl = make([]jsonPoint, len(p.Control))
   295  		for i, v := range p.Control {
   296  			o.Ctl[i] = jsonPointFrom(v)
   297  		}
   298  	}
   299  
   300  	return o
   301  }
   302  
   303  func (jp jsonPathComp) cnv() vg.PathComp {
   304  	o := vg.PathComp{
   305  		Type:   jp.Type,
   306  		Pos:    jp.Pos.cnv(),
   307  		Radius: jp.Radius,
   308  		Start:  jp.Start,
   309  		Angle:  jp.Angle,
   310  	}
   311  	if len(jp.Ctl) > 0 {
   312  		o.Control = make([]vg.Point, len(jp.Ctl))
   313  		for i, v := range jp.Ctl {
   314  			o.Control[i] = v.cnv()
   315  		}
   316  	}
   317  	return o
   318  }
   319  
   320  type cwriter struct {
   321  	w io.Writer
   322  	n int
   323  }
   324  
   325  func (w *cwriter) Write(p []byte) (int, error) {
   326  	n, err := w.w.Write(p)
   327  	w.n += n
   328  	return n, err
   329  }
   330  
   331  var _ io.Writer = (*cwriter)(nil)