From bb287515d3b5c5b4ca7665a64a9224e8b6038804 Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Wed, 31 Oct 2018 19:56:01 -0500 Subject: Added Ability to turn template into a byte string --- def.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fpdf_test.go | 20 ++++++++++-- template.go | 3 ++ template_impl.go | 59 ++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 3 deletions(-) diff --git a/def.go b/def.go index d53e627..4b19a06 100644 --- a/def.go +++ b/def.go @@ -18,6 +18,7 @@ package gofpdf import ( "bytes" + "encoding/gob" "io" "time" ) @@ -175,6 +176,101 @@ type ImageInfoType struct { dpi float64 } +func (info *ImageInfoType) GobEncode() ([]byte, error) { + w := new(bytes.Buffer) + encoder := gob.NewEncoder(w) + + err := encoder.Encode(info.data) + if err == nil { + err = encoder.Encode(info.smask) + } + if err == nil { + err = encoder.Encode(info.i) + } + if err == nil { + err = encoder.Encode(info.n) + } + if err == nil { + err = encoder.Encode(info.w) + } + if err == nil { + err = encoder.Encode(info.h) + } + if err == nil { + err = encoder.Encode(info.cs) + } + if err == nil { + err = encoder.Encode(info.pal) + } + if err == nil { + err = encoder.Encode(info.bpc) + } + if err == nil { + err = encoder.Encode(info.f) + } + if err == nil { + err = encoder.Encode(info.dp) + } + if err == nil { + err = encoder.Encode(info.trns) + } + if err == nil { + err = encoder.Encode(info.scale) + } + if err == nil { + err = encoder.Encode(info.dpi) + } + + return w.Bytes(), err +} + +func (info *ImageInfoType) GobDecode(buf []byte) error { + r := bytes.NewBuffer(buf) + decoder := gob.NewDecoder(r) + + err := decoder.Decode(&info.data) + if err == nil { + err = decoder.Decode(&info.smask) + } + if err == nil { + err = decoder.Decode(&info.i) + } + if err == nil { + err = decoder.Decode(&info.n) + } + if err == nil { + err = decoder.Decode(&info.w) + } + if err == nil { + err = decoder.Decode(&info.h) + } + if err == nil { + err = decoder.Decode(&info.cs) + } + if err == nil { + err = decoder.Decode(&info.pal) + } + if err == nil { + err = decoder.Decode(&info.bpc) + } + if err == nil { + err = decoder.Decode(&info.f) + } + if err == nil { + err = decoder.Decode(&info.dp) + } + if err == nil { + err = decoder.Decode(&info.trns) + } + if err == nil { + err = decoder.Decode(&info.scale) + } + if err == nil { + err = decoder.Decode(&info.dpi) + } + return err +} + // PointConvert returns the value of pt, expressed in points (1/72 inch), as a // value expressed in the unit of measure specified in New(). Since font // management in Fpdf uses points, this method can help with line height diff --git a/fpdf_test.go b/fpdf_test.go index 4774ec6..e212d95 100644 --- a/fpdf_test.go +++ b/fpdf_test.go @@ -19,6 +19,7 @@ package gofpdf_test import ( "bufio" "bytes" + "encoding/gob" "fmt" "io" "io/ioutil" @@ -1950,10 +1951,23 @@ func ExampleFpdf_CreateTemplate() { pdf.SetLineWidth(2.5) pdf.SetFont("Arial", "B", 16) + template3 := new(gofpdf.FpdfTpl) + b := new(bytes.Buffer) + enc := gob.NewEncoder(b) + + if err := enc.Encode(template); err != nil { + pdf.SetError(err) + } + + dec := gob.NewDecoder(b) + if err := dec.Decode(template3); err != nil { + pdf.SetError(err) + } + pdf.AddPage() - pdf.UseTemplate(template) - pdf.UseTemplateScaled(template, gofpdf.PointType{X: 0, Y: 30}, tplSize) - pdf.UseTemplateScaled(template, gofpdf.PointType{X: 0, Y: 60}, tplSize.ScaleBy(1.4)) + pdf.UseTemplate(template3) + pdf.UseTemplateScaled(template3, gofpdf.PointType{X: 0, Y: 30}, tplSize) + pdf.UseTemplateScaled(template3, gofpdf.PointType{X: 0, Y: 60}, tplSize.ScaleBy(1.4)) pdf.Line(40, 210, 60, 210) pdf.Text(40, 200, "Template example page 1") diff --git a/template.go b/template.go index 60b076b..2b886ed 100644 --- a/template.go +++ b/template.go @@ -18,6 +18,7 @@ package gofpdf */ import ( + "encoding/gob" "sort" ) @@ -107,6 +108,8 @@ type Template interface { Bytes() []byte Images() map[string]*ImageInfoType Templates() []Template + gob.GobDecoder + gob.GobEncoder } func (f *Fpdf) templateFontCatalog() { diff --git a/template_impl.go b/template_impl.go index 01bb040..9e5d496 100644 --- a/template_impl.go +++ b/template_impl.go @@ -1,5 +1,10 @@ package gofpdf +import ( + "bytes" + "encoding/gob" +) + /* * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung), * Marcus Downing, Jan Slabon (Setasign) @@ -80,6 +85,60 @@ func (t *FpdfTpl) Templates() []Template { return t.templates } +func (t *FpdfTpl) GobEncode() ([]byte, error) { + w := new(bytes.Buffer) + encoder := gob.NewEncoder(w) + + err := encoder.Encode(t.id) + if err == nil { + err = encoder.Encode(t.corner) + } + if err == nil { + err = encoder.Encode(t.size) + } + if err == nil { + err = encoder.Encode(t.bytes) + } + if err == nil { + err = encoder.Encode(t.images) + } + if err == nil { + err = encoder.Encode(t.templates) + } + + return w.Bytes(), err +} + +func (t *FpdfTpl) GobDecode(buf []byte) error { + r := bytes.NewBuffer(buf) + decoder := gob.NewDecoder(r) + + err := decoder.Decode(&t.id) + if err == nil { + err = decoder.Decode(&t.corner) + } + if err == nil { + err = decoder.Decode(&t.size) + } + if err == nil { + err = decoder.Decode(&t.bytes) + } + if err == nil { + err = decoder.Decode(&t.images) + } + if err == nil { + templates := make([]*FpdfTpl, 0) + err = decoder.Decode(&templates) + t.templates = make([]Template, len(templates)) + + for x := 0; x < len(templates); x++ { + t.templates[x] = templates[x] + } + } + + return err +} + // Tpl is an Fpdf used for writing a template. It has most of the facilities of // an Fpdf, but cannot add more pages. Tpl is used directly only during the // limited time a template is writable. -- cgit v1.2.1-24-ge1ad From 6993fe6ea4223dca30d0cae9fa0bd9a3cdfdb12d Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Mon, 5 Nov 2018 15:50:58 -0600 Subject: fixed pointer values when mutltiple templates are used --- template_impl.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/template_impl.go b/template_impl.go index 9e5d496..b1b2382 100644 --- a/template_impl.go +++ b/template_impl.go @@ -3,6 +3,7 @@ package gofpdf import ( "bytes" "encoding/gob" + "reflect" ) /* @@ -136,9 +137,26 @@ func (t *FpdfTpl) GobDecode(buf []byte) error { } } + t.relinkPointers(t.images) + return err } +func (t *FpdfTpl) relinkPointers(images map[string]*ImageInfoType) { + for gKey, _ := range images { + for key, _ := range t.images { + if reflect.DeepEqual(t.images[key], images[gKey]) { + t.images[key] = images[gKey] + } + } + } + + for x := 0; x < len(t.templates); x++ { + innerT := t.templates[x].(*FpdfTpl) + innerT.relinkPointers(t.images) + } +} + // Tpl is an Fpdf used for writing a template. It has most of the facilities of // an Fpdf, but cannot add more pages. Tpl is used directly only during the // limited time a template is writable. -- cgit v1.2.1-24-ge1ad From a90e8ab8a5ab8b8bb484c51df7c29e721f31e184 Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Wed, 7 Nov 2018 08:03:04 -0600 Subject: Changed the byte encoding so we are no longer saving redundant data from pointers --- template_impl.go | 119 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 36 deletions(-) diff --git a/template_impl.go b/template_impl.go index b1b2382..174e893 100644 --- a/template_impl.go +++ b/template_impl.go @@ -3,7 +3,7 @@ package gofpdf import ( "bytes" "encoding/gob" - "reflect" + "fmt" ) /* @@ -86,25 +86,60 @@ func (t *FpdfTpl) Templates() []Template { return t.templates } +// returns the next layer of children images, it doesn't dig into +// children of children. Applies template namespace to keys to ensure +// no collisions. See UseTemplateScaled +func (t *FpdfTpl) childrenImages() map[string]*ImageInfoType { + childrenImgs := make(map[string]*ImageInfoType) + + for x := 0; x < len(t.templates); x++ { + imgs := t.templates[x].Images() + for key, val := range imgs { + name := sprintf("t%d-%s", t.templates[x].ID(), key) + childrenImgs[name] = val + } + } + + return childrenImgs +} + func (t *FpdfTpl) GobEncode() ([]byte, error) { w := new(bytes.Buffer) encoder := gob.NewEncoder(w) - err := encoder.Encode(t.id) + err := encoder.Encode(t.templates) + childrenImgs := t.childrenImages() + if err == nil { - err = encoder.Encode(t.corner) + encoder.Encode(len(t.images)) + } + + for key, img := range t.images { + // if the image has already been saved as a child, then + // save nil so we don't duplicate data + err = encoder.Encode(key) + + if err != nil { + break + } + + if _, ok := childrenImgs[key]; ok { + err = encoder.Encode(nil) + } else { + err = encoder.Encode(img) + } } if err == nil { - err = encoder.Encode(t.size) + err = encoder.Encode(t.id) } if err == nil { - err = encoder.Encode(t.bytes) + err = encoder.Encode(t.corner) } if err == nil { - err = encoder.Encode(t.images) + err = encoder.Encode(t.size) } if err == nil { - err = encoder.Encode(t.templates) + err = encoder.Encode(t.bytes) } return w.Bytes(), err @@ -114,49 +149,61 @@ func (t *FpdfTpl) GobDecode(buf []byte) error { r := bytes.NewBuffer(buf) decoder := gob.NewDecoder(r) - err := decoder.Decode(&t.id) + templates := make([]*FpdfTpl, 0) + err := decoder.Decode(&templates) + t.templates = make([]Template, len(templates)) + + for x := 0; x < len(templates); x++ { + t.templates[x] = templates[x] + } + + var numImgs int if err == nil { - err = decoder.Decode(&t.corner) + err = decoder.Decode(&numImgs) + } + t.images = make(map[string]*ImageInfoType) + childrenImgs := t.childrenImages() + + for x := 0; x < numImgs; x++ { + var key string + var img *ImageInfoType + + if err == nil { + err = decoder.Decode(&key) + } + + if err == nil { + err = decoder.Decode(&img) + } + + if err == nil { + if img != nil { + t.images[key] = img + } else { + if _, ok := childrenImgs[key]; !ok { + err = fmt.Errorf("Encoded template is corrupt, could not find image %s", key) + } else { + t.images[key] = childrenImgs[key] + } + } + } } if err == nil { - err = decoder.Decode(&t.size) + err = decoder.Decode(&t.id) } if err == nil { - err = decoder.Decode(&t.bytes) + err = decoder.Decode(&t.corner) } if err == nil { - err = decoder.Decode(&t.images) + err = decoder.Decode(&t.size) } if err == nil { - templates := make([]*FpdfTpl, 0) - err = decoder.Decode(&templates) - t.templates = make([]Template, len(templates)) - - for x := 0; x < len(templates); x++ { - t.templates[x] = templates[x] - } + err = decoder.Decode(&t.bytes) } - t.relinkPointers(t.images) - return err } -func (t *FpdfTpl) relinkPointers(images map[string]*ImageInfoType) { - for gKey, _ := range images { - for key, _ := range t.images { - if reflect.DeepEqual(t.images[key], images[gKey]) { - t.images[key] = images[gKey] - } - } - } - - for x := 0; x < len(t.templates); x++ { - innerT := t.templates[x].(*FpdfTpl) - innerT.relinkPointers(t.images) - } -} - // Tpl is an Fpdf used for writing a template. It has most of the facilities of // an Fpdf, but cannot add more pages. Tpl is used directly only during the // limited time a template is writable. -- cgit v1.2.1-24-ge1ad From bc10d22b451bd5ecfdc2d06dc12c1c7d94adb2d2 Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Wed, 7 Nov 2018 08:21:00 -0600 Subject: Fixed nil encoding --- template_impl.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/template_impl.go b/template_impl.go index 174e893..001b2d9 100644 --- a/template_impl.go +++ b/template_impl.go @@ -124,9 +124,12 @@ func (t *FpdfTpl) GobEncode() ([]byte, error) { } if _, ok := childrenImgs[key]; ok { - err = encoder.Encode(nil) + err = encoder.Encode("p") } else { - err = encoder.Encode(img) + err = encoder.Encode("o") + if err == nil { + err = encoder.Encode(img) + } } } if err == nil { @@ -161,30 +164,37 @@ func (t *FpdfTpl) GobDecode(buf []byte) error { if err == nil { err = decoder.Decode(&numImgs) } + t.images = make(map[string]*ImageInfoType) childrenImgs := t.childrenImages() for x := 0; x < numImgs; x++ { var key string - var img *ImageInfoType + var tpe string if err == nil { err = decoder.Decode(&key) } if err == nil { - err = decoder.Decode(&img) + err = decoder.Decode(&tpe) } if err == nil { - if img != nil { - t.images[key] = img - } else { + switch tpe { + case "p": if _, ok := childrenImgs[key]; !ok { err = fmt.Errorf("Encoded template is corrupt, could not find image %s", key) } else { t.images[key] = childrenImgs[key] } + case "o": + var img *ImageInfoType + err = decoder.Decode(&img) + + if err == nil { + t.images[key] = img + } } } } -- cgit v1.2.1-24-ge1ad From 01381ea7603b12304064fcd01a49b1cc4434b4ca Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Wed, 7 Nov 2018 08:51:37 -0600 Subject: Created helper functions for serialiation --- fpdf_test.go | 16 +++------------- template.go | 1 + template_impl.go | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/fpdf_test.go b/fpdf_test.go index e212d95..19bc533 100644 --- a/fpdf_test.go +++ b/fpdf_test.go @@ -19,7 +19,6 @@ package gofpdf_test import ( "bufio" "bytes" - "encoding/gob" "fmt" "io" "io/ioutil" @@ -1951,18 +1950,9 @@ func ExampleFpdf_CreateTemplate() { pdf.SetLineWidth(2.5) pdf.SetFont("Arial", "B", 16) - template3 := new(gofpdf.FpdfTpl) - b := new(bytes.Buffer) - enc := gob.NewEncoder(b) - - if err := enc.Encode(template); err != nil { - pdf.SetError(err) - } - - dec := gob.NewDecoder(b) - if err := dec.Decode(template3); err != nil { - pdf.SetError(err) - } + // serialize and deserialize template + b, _ := template2.Serialize() + template3, _ := gofpdf.DeserializeTemplate(b) pdf.AddPage() pdf.UseTemplate(template3) diff --git a/template.go b/template.go index 2b886ed..9abdf62 100644 --- a/template.go +++ b/template.go @@ -108,6 +108,7 @@ type Template interface { Bytes() []byte Images() map[string]*ImageInfoType Templates() []Template + Serialize() ([]byte, error) gob.GobDecoder gob.GobEncoder } diff --git a/template_impl.go b/template_impl.go index 001b2d9..9c690c1 100644 --- a/template_impl.go +++ b/template_impl.go @@ -86,6 +86,23 @@ func (t *FpdfTpl) Templates() []Template { return t.templates } +// Turn a template into a byte string for later deserialization +func (t *FpdfTpl) Serialize() ([]byte, error) { + b := new(bytes.Buffer) + enc := gob.NewEncoder(b) + err := enc.Encode(t) + + return b.Bytes(), err +} + +// Create a template from a previously serialized template +func DeserializeTemplate(b []byte) (Template, error) { + tpl := new(FpdfTpl) + dec := gob.NewDecoder(bytes.NewBuffer(b)) + err := dec.Decode(tpl) + return tpl, err +} + // returns the next layer of children images, it doesn't dig into // children of children. Applies template namespace to keys to ensure // no collisions. See UseTemplateScaled -- cgit v1.2.1-24-ge1ad From 193187d077554b92944a4757e202d1300e1ed1fb Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 10 Nov 2018 12:06:51 -0500 Subject: Added some comments to new encoding/decoding methods --- README.md | 4 ++- def.go | 103 ++++++++++--------------------------------------------- doc.go | 5 ++- fpdf_test.go | 2 +- template_impl.go | 8 +++-- 5 files changed, 33 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 953857b..2a8d606 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,9 @@ account when calculating image size. Paulo Coutinho provided support for static embedded fonts. Dan Meyers added support for embedded JavaScript. David Fish added a generic alias-replacement function to enable, among other things, table of contents functionality. Andy Bakun identified and corrected a problem in -which the internal catalogs were not sorted stably. +which the internal catalogs were not sorted stably. d1ngd0 added encoding and +decoding functionality for templates, including images that are embedded in +templates; this allows templates to be stored independently of gofpdf. ## Roadmap diff --git a/def.go b/def.go index 3c5fa9d..605cdbc 100644 --- a/def.go +++ b/def.go @@ -158,7 +158,9 @@ func (p PointType) XY() (float64, float64) { return p.X, p.Y } -// ImageInfoType contains size, color and other information about an image +// ImageInfoType contains size, color and other information about an image. +// Changes to this structure should be reflected in its GobEncode and GobDecode +// methods. type ImageInfoType struct { data []byte smask []byte @@ -176,99 +178,32 @@ type ImageInfoType struct { dpi float64 } -func (info *ImageInfoType) GobEncode() ([]byte, error) { +// GobEncode encodes the receiving image to a byte slice. +func (info *ImageInfoType) GobEncode() (buf []byte, err error) { + fields := []interface{}{info.data, info.smask, info.i, info.n, info.w, info.h, info.cs, + info.pal, info.bpc, info.f, info.dp, info.trns, info.scale, info.dpi} w := new(bytes.Buffer) encoder := gob.NewEncoder(w) - - err := encoder.Encode(info.data) - if err == nil { - err = encoder.Encode(info.smask) - } - if err == nil { - err = encoder.Encode(info.i) - } - if err == nil { - err = encoder.Encode(info.n) - } - if err == nil { - err = encoder.Encode(info.w) - } - if err == nil { - err = encoder.Encode(info.h) - } - if err == nil { - err = encoder.Encode(info.cs) - } - if err == nil { - err = encoder.Encode(info.pal) - } - if err == nil { - err = encoder.Encode(info.bpc) - } - if err == nil { - err = encoder.Encode(info.f) - } - if err == nil { - err = encoder.Encode(info.dp) + for j := 0; j < len(fields) && err == nil; j++ { + err = encoder.Encode(fields[j]) } if err == nil { - err = encoder.Encode(info.trns) + buf = w.Bytes() } - if err == nil { - err = encoder.Encode(info.scale) - } - if err == nil { - err = encoder.Encode(info.dpi) - } - - return w.Bytes(), err + return } -func (info *ImageInfoType) GobDecode(buf []byte) error { +// GobDecode decodes the specified byte buffer (generated by GobEncode) into +// the receiving image. +func (info *ImageInfoType) GobDecode(buf []byte) (err error) { + fields := []interface{}{&info.data, &info.smask, &info.i, &info.n, &info.w, &info.h, + &info.cs, &info.pal, &info.bpc, &info.f, &info.dp, &info.trns, &info.scale, &info.dpi} r := bytes.NewBuffer(buf) decoder := gob.NewDecoder(r) - - err := decoder.Decode(&info.data) - if err == nil { - err = decoder.Decode(&info.smask) - } - if err == nil { - err = decoder.Decode(&info.i) - } - if err == nil { - err = decoder.Decode(&info.n) - } - if err == nil { - err = decoder.Decode(&info.w) - } - if err == nil { - err = decoder.Decode(&info.h) - } - if err == nil { - err = decoder.Decode(&info.cs) - } - if err == nil { - err = decoder.Decode(&info.pal) - } - if err == nil { - err = decoder.Decode(&info.bpc) - } - if err == nil { - err = decoder.Decode(&info.f) - } - if err == nil { - err = decoder.Decode(&info.dp) - } - if err == nil { - err = decoder.Decode(&info.trns) - } - if err == nil { - err = decoder.Decode(&info.scale) - } - if err == nil { - err = decoder.Decode(&info.dpi) + for j := 0; j < len(fields) && err == nil; j++ { + err = decoder.Decode(fields[j]) } - return err + return } // PointConvert returns the value of pt, expressed in points (1/72 inch), as a diff --git a/doc.go b/doc.go index 02733ba..534b1d7 100644 --- a/doc.go +++ b/doc.go @@ -235,7 +235,10 @@ account when calculating image size. Paulo Coutinho provided support for static embedded fonts. Dan Meyers added support for embedded JavaScript. David Fish added a generic alias-replacement function to enable, among other things, table of contents functionality. Andy Bakun identified and corrected a problem in -which the internal catalogs were not sorted stably. +which the internal catalogs were not sorted stably. d1ngd0 added encoding and +decoding functionality for templates, including images that are embedded in +templates; this allows templates to be stored independently of gofpdf. + Roadmap diff --git a/fpdf_test.go b/fpdf_test.go index 19bc533..68a26ad 100644 --- a/fpdf_test.go +++ b/fpdf_test.go @@ -1957,12 +1957,12 @@ func ExampleFpdf_CreateTemplate() { pdf.AddPage() pdf.UseTemplate(template3) pdf.UseTemplateScaled(template3, gofpdf.PointType{X: 0, Y: 30}, tplSize) - pdf.UseTemplateScaled(template3, gofpdf.PointType{X: 0, Y: 60}, tplSize.ScaleBy(1.4)) pdf.Line(40, 210, 60, 210) pdf.Text(40, 200, "Template example page 1") pdf.AddPage() pdf.UseTemplate(template2) + pdf.UseTemplateScaled(template3, gofpdf.PointType{X: 0, Y: 30}, tplSize.ScaleBy(1.4)) pdf.Line(60, 210, 80, 210) pdf.Text(40, 200, "Template example page 2") diff --git a/template_impl.go b/template_impl.go index 1a91112..c34e688 100644 --- a/template_impl.go +++ b/template_impl.go @@ -82,7 +82,7 @@ func (t *FpdfTpl) Templates() []Template { return t.templates } -// Turn a template into a byte string for later deserialization +// Serialize turns a template into a byte string for later deserialization func (t *FpdfTpl) Serialize() ([]byte, error) { b := new(bytes.Buffer) enc := gob.NewEncoder(b) @@ -91,7 +91,8 @@ func (t *FpdfTpl) Serialize() ([]byte, error) { return b.Bytes(), err } -// Create a template from a previously serialized template +// DeserializeTemplate creaties a template from a previously serialized +// template func DeserializeTemplate(b []byte) (Template, error) { tpl := new(FpdfTpl) dec := gob.NewDecoder(bytes.NewBuffer(b)) @@ -116,6 +117,8 @@ func (t *FpdfTpl) childrenImages() map[string]*ImageInfoType { return childrenImgs } +// GobEncode encodes the receiving template into a byte buffer. Use GobDecode +// to decode the byte buffer back to a template. func (t *FpdfTpl) GobEncode() ([]byte, error) { w := new(bytes.Buffer) encoder := gob.NewEncoder(w) @@ -161,6 +164,7 @@ func (t *FpdfTpl) GobEncode() ([]byte, error) { return w.Bytes(), err } +// GobDecode decodes the specified byte buffer into the receiving template. func (t *FpdfTpl) GobDecode(buf []byte) error { r := bytes.NewBuffer(buf) decoder := gob.NewDecoder(r) -- cgit v1.2.1-24-ge1ad