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)