From 0fb5126fefeb1a7be7ed1c4dac02539af931a237 Mon Sep 17 00:00:00 2001 From: Dave Barnes Date: Wed, 15 May 2019 10:45:22 -0500 Subject: Add support for imported objects and templates to version 1 of gofpdf. --- README.md | 3 +- contrib/gofpdi/gofpdi.go | 62 +++++++++++++++++++++++++++++++++ def.go | 4 +++ doc.go | 3 +- doc/document.md | 2 +- fpdf.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 contrib/gofpdi/gofpdi.go diff --git a/README.md b/README.md index 2d5d14e..fdd8980 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,8 @@ encoding and decoding functionality for templates, including images that are embedded in templates; this allows templates to be stored independently of gofpdf. Paul also added support for page boxes used in printing PDF documents. Wojciech Matusiak added supported for word -spacing. Artem Korotkiy added support of UTF-8 fonts. +spacing. Artem Korotkiy added support of UTF-8 fonts. Dave Barnes added +support for imported objects and templates. ## Roadmap diff --git a/contrib/gofpdi/gofpdi.go b/contrib/gofpdi/gofpdi.go new file mode 100644 index 0000000..c1f3df8 --- /dev/null +++ b/contrib/gofpdi/gofpdi.go @@ -0,0 +1,62 @@ +package gofpdi + +import ( + realgofpdi "github.com/phpdave11/gofpdi" +) + +// Create new gofpdi instance +var fpdi = realgofpdi.NewImporter() + +// gofpdiPdf is a partial interface that only implements the functions we need +// from the PDF generator to put the imported PDF templates on the PDF. +type gofpdiPdf interface { + ImportObjects(objs map[string][]byte) + ImportObjPos(objs map[string]map[int]string) + ImportTemplates(tpls map[string]string) + UseImportedTemplate(tplName string, x float64, y float64, w float64, h float64) + SetError(err error) +} + +// Imports a page of a PDF file with the specified box (/MediaBox, /TrimBox, /ArtBox, /CropBox, or /BleedBox). +// Returns a template id that can be used with UseImportedTemplate to draw the template onto the page. +func ImportPage(f gofpdiPdf, sourceFile string, pageno int, box string) int { + // Set source file for fpdi + fpdi.SetSourceFile(sourceFile) + + // Import page + tpl := fpdi.ImportPage(pageno, box) + + // Import objects into current pdf document + // Unordered means that the objects will be returned with a sha1 hash instead of an integer + // The objects themselves may have references to other hashes which will be replaced in ImportObjects() + tplObjIds := fpdi.PutFormXobjectsUnordered() + + // Set template names and ids (hashes) in gofpdf + f.ImportTemplates(tplObjIds) + + // Get a map[string]string of the imported objects. + // The map keys will be the ID of each object. + imported := fpdi.GetImportedObjectsUnordered() + + // Import gofpdi objects into gofpdf + f.ImportObjects(imported) + + // Get a map[string]map[int]string of the object hashes and their positions within each object, + // to be replaced with object ids (integers). + importedObjPos := fpdi.GetImportedObjHashPos() + + // Import gofpdi object hashes and their positions into gopdf + f.ImportObjPos(importedObjPos) + + return tpl +} + +// Draw the template onto the page at x,y +// If w is 0, the template will be scaled to fit based on h +// If h is 0, the template will be scaled to fit based on w +func UseImportedTemplate(f gofpdiPdf, tplid int, x float64, y float64, w float64, h float64) { + // Get values from fpdi + tplName, scaleX, scaleY, tX, tY := fpdi.UseTemplate(tplid, x, y, w, h) + + f.UseImportedTemplate(tplName, scaleX, scaleY, tX, tY) +} diff --git a/def.go b/def.go index ab4e81d..f725f01 100644 --- a/def.go +++ b/def.go @@ -504,6 +504,10 @@ type Fpdf struct { offsets []int // array of object offsets templates map[string]Template // templates used in this document templateObjects map[string]int // template object IDs within this document + importedObjs map[string][]byte // imported template objects (gofpdi) + importedObjPos map[string]map[int]string // imported template objects hashes and their positions (gofpdi) + importedTplObjs map[string]string // imported template names and IDs (hashed) (gofpdi) + importedTplIds map[string]int // imported template ids hash to object id int (gofpdi) buffer fmtBuffer // buffer holding in-memory PDF pages []*bytes.Buffer // slice[page] of page content; 1-based state int // current document state diff --git a/doc.go b/doc.go index 2d6f49b..3295200 100644 --- a/doc.go +++ b/doc.go @@ -253,7 +253,8 @@ encoding and decoding functionality for templates, including images that are embedded in templates; this allows templates to be stored independently of gofpdf. Paul also added support for page boxes used in printing PDF documents. Wojciech Matusiak added supported for word -spacing. Artem Korotkiy added support of UTF-8 fonts. +spacing. Artem Korotkiy added support of UTF-8 fonts. Dave Barnes added +support for imported objects and templates. Roadmap diff --git a/doc/document.md b/doc/document.md index 33f5dac..de39656 100644 --- a/doc/document.md +++ b/doc/document.md @@ -226,7 +226,7 @@ decoding functionality for templates, including images that are embedded in templates; this allows templates to be stored independently of gofpdf. Paul also added support for page boxes used in printing PDF documents. Wojciech Matusiak added supported for word spacing. Artem Korotkiy added support of -UTF-8 fonts. +UTF-8 fonts. Dave Barnes added support for imported objects and templates. ## Roadmap diff --git a/fpdf.go b/fpdf.go index d3366ce..e381459 100644 --- a/fpdf.go +++ b/fpdf.go @@ -85,6 +85,10 @@ func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) f.diffs = make([]string, 0, 8) f.templates = make(map[string]Template) f.templateObjects = make(map[string]int) + f.importedObjs = make(map[string][]byte, 0) + f.importedObjPos = make(map[string]map[int]string, 0) + f.importedTplObjs = make(map[string]string) + f.importedTplIds = make(map[string]int, 0) f.images = make(map[string]*ImageInfoType) f.pageLinks = make([][]linkType, 0, 8) f.pageLinks = append(f.pageLinks, make([]linkType, 0, 0)) // pageLinks[0] is unused (1-based) @@ -3101,6 +3105,86 @@ func (f *Fpdf) GetImageInfo(imageStr string) (info *ImageInfoType) { return f.images[imageStr] } +// Import objects from gofpdi into current document +func (f *Fpdf) ImportObjects(objs map[string][]byte) { + for k, v := range objs { + f.importedObjs[k] = v + } +} + +// Import object hash positions from gofpdi +func (f *Fpdf) ImportObjPos(objPos map[string]map[int]string) { + for k, v := range objPos { + f.importedObjPos[k] = v + } +} + +// putImportedTemplates writes the imported template objects to the PDF +func (f *Fpdf) putImportedTemplates() { + nOffset := f.n + 1 + + // keep track of list of sha1 hashes (to be replaced with integers) + objsIdHash := make([]string, len(f.importedObjs)) + + // actual object data with new id + objsIdData := make([][]byte, len(f.importedObjs)) + + // Populate hash slice and data slice + i := 0 + for k, v := range f.importedObjs { + objsIdHash[i] = k + objsIdData[i] = v + + i++ + } + + // Populate a lookup table to get an object id from a hash + hashToObjId := make(map[string]int, len(f.importedObjs)) + for i = 0; i < len(objsIdHash); i++ { + hashToObjId[objsIdHash[i]] = i + nOffset + } + + // Now, replace hashes inside data with %040d object id + for i = 0; i < len(objsIdData); i++ { + // get hash + hash := objsIdHash[i] + + for pos, h := range f.importedObjPos[hash] { + // Convert object id into a 40 character string padded with spaces + objIdPadded := fmt.Sprintf("%40s", fmt.Sprintf("%d", hashToObjId[h])) + + // Convert objIdPadded into []byte + objIdBytes := []byte(objIdPadded) + + // Replace sha1 hash with object id padded + for j := pos; j < pos+40; j++ { + objsIdData[i][j] = objIdBytes[j-pos] + } + } + + // Save objsIdHash so that procset dictionary has the correct object ids + f.importedTplIds[hash] = i + nOffset + } + + // Now, put objects + for i = 0; i < len(objsIdData); i++ { + f.newobj() + f.out(string(objsIdData[i])) + } +} + +// Use imported template from gofpdi - draws imported PDF page onto page +func (f *Fpdf) UseImportedTemplate(tplName string, scaleX float64, scaleY float64, tX float64, tY float64) { + f.outf("q 0 J 1 w 0 j 0 G 0 g q %.4F 0 0 %.4F %.4F %.4F cm %s Do Q Q\n", scaleX*f.k, scaleY*f.k, tX*f.k, (tY+f.h)*f.k, tplName) +} + +// Import gofpdi template names into importedTplObjs - to be included in the procset dictionary +func (f *Fpdf) ImportTemplates(tpls map[string]string) { + for tplName, tplId := range tpls { + f.importedTplObjs[tplName] = tplId + } +} + // GetConversionRatio returns the conversion ratio based on the unit given when // creating the PDF. func (f *Fpdf) GetConversionRatio() float64 { @@ -4173,6 +4257,12 @@ func (f *Fpdf) putxobjectdict() { } } } + { + for tplName, objID := range f.importedTplObjs { + // here replace obj id hash with n + f.outf("%s %d 0 R", tplName, f.importedTplIds[objID]) + } + } } func (f *Fpdf) putresourcedict() { @@ -4288,6 +4378,7 @@ func (f *Fpdf) putresources() { } f.putimages() f.putTemplates() + f.putImportedTemplates() // gofpdi // Resource dictionary f.offsets[2] = f.buffer.Len() f.out("2 0 obj") -- cgit v1.2.1-24-ge1ad From ce8a12ff5655c57989b925abc0b8a3a1e6535d59 Mon Sep 17 00:00:00 2001 From: Dave Barnes Date: Wed, 15 May 2019 11:25:39 -0500 Subject: Clean up code based on jung-kurt's feedback. --- contrib/gofpdi/gofpdi.go | 6 +++--- def.go | 2 +- fpdf.go | 50 ++++++++++++++++++++++++------------------------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/contrib/gofpdi/gofpdi.go b/contrib/gofpdi/gofpdi.go index c1f3df8..d1b98e9 100644 --- a/contrib/gofpdi/gofpdi.go +++ b/contrib/gofpdi/gofpdi.go @@ -29,10 +29,10 @@ func ImportPage(f gofpdiPdf, sourceFile string, pageno int, box string) int { // Import objects into current pdf document // Unordered means that the objects will be returned with a sha1 hash instead of an integer // The objects themselves may have references to other hashes which will be replaced in ImportObjects() - tplObjIds := fpdi.PutFormXobjectsUnordered() + tplObjIDs := fpdi.PutFormXobjectsUnordered() // Set template names and ids (hashes) in gofpdf - f.ImportTemplates(tplObjIds) + f.ImportTemplates(tplObjIDs) // Get a map[string]string of the imported objects. // The map keys will be the ID of each object. @@ -41,7 +41,7 @@ func ImportPage(f gofpdiPdf, sourceFile string, pageno int, box string) int { // Import gofpdi objects into gofpdf f.ImportObjects(imported) - // Get a map[string]map[int]string of the object hashes and their positions within each object, + // Get a map[string]map[int]string of the object hashes and their positions within each object, // to be replaced with object ids (integers). importedObjPos := fpdi.GetImportedObjHashPos() diff --git a/def.go b/def.go index f725f01..efa573e 100644 --- a/def.go +++ b/def.go @@ -507,7 +507,7 @@ type Fpdf struct { importedObjs map[string][]byte // imported template objects (gofpdi) importedObjPos map[string]map[int]string // imported template objects hashes and their positions (gofpdi) importedTplObjs map[string]string // imported template names and IDs (hashed) (gofpdi) - importedTplIds map[string]int // imported template ids hash to object id int (gofpdi) + importedTplIDs map[string]int // imported template ids hash to object id int (gofpdi) buffer fmtBuffer // buffer holding in-memory PDF pages []*bytes.Buffer // slice[page] of page content; 1-based state int // current document state diff --git a/fpdf.go b/fpdf.go index e381459..666f97f 100644 --- a/fpdf.go +++ b/fpdf.go @@ -88,7 +88,7 @@ func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) f.importedObjs = make(map[string][]byte, 0) f.importedObjPos = make(map[string]map[int]string, 0) f.importedTplObjs = make(map[string]string) - f.importedTplIds = make(map[string]int, 0) + f.importedTplIDs = make(map[string]int, 0) f.images = make(map[string]*ImageInfoType) f.pageLinks = make([][]linkType, 0, 8) f.pageLinks = append(f.pageLinks, make([]linkType, 0, 0)) // pageLinks[0] is unused (1-based) @@ -3105,14 +3105,14 @@ func (f *Fpdf) GetImageInfo(imageStr string) (info *ImageInfoType) { return f.images[imageStr] } -// Import objects from gofpdi into current document +// ImportObjects imports objects from gofpdi into current document func (f *Fpdf) ImportObjects(objs map[string][]byte) { for k, v := range objs { f.importedObjs[k] = v } } -// Import object hash positions from gofpdi +// ImportObjPos imports object hash positions from gofpdi func (f *Fpdf) ImportObjPos(objPos map[string]map[int]string) { for k, v := range objPos { f.importedObjPos[k] = v @@ -3124,64 +3124,64 @@ func (f *Fpdf) putImportedTemplates() { nOffset := f.n + 1 // keep track of list of sha1 hashes (to be replaced with integers) - objsIdHash := make([]string, len(f.importedObjs)) + objsIDHash := make([]string, len(f.importedObjs)) // actual object data with new id - objsIdData := make([][]byte, len(f.importedObjs)) + objsIDData := make([][]byte, len(f.importedObjs)) // Populate hash slice and data slice i := 0 for k, v := range f.importedObjs { - objsIdHash[i] = k - objsIdData[i] = v + objsIDHash[i] = k + objsIDData[i] = v i++ } // Populate a lookup table to get an object id from a hash - hashToObjId := make(map[string]int, len(f.importedObjs)) - for i = 0; i < len(objsIdHash); i++ { - hashToObjId[objsIdHash[i]] = i + nOffset + hashToObjID := make(map[string]int, len(f.importedObjs)) + for i = 0; i < len(objsIDHash); i++ { + hashToObjID[objsIDHash[i]] = i + nOffset } // Now, replace hashes inside data with %040d object id - for i = 0; i < len(objsIdData); i++ { + for i = 0; i < len(objsIDData); i++ { // get hash - hash := objsIdHash[i] + hash := objsIDHash[i] for pos, h := range f.importedObjPos[hash] { // Convert object id into a 40 character string padded with spaces - objIdPadded := fmt.Sprintf("%40s", fmt.Sprintf("%d", hashToObjId[h])) + objIDPadded := fmt.Sprintf("%40s", fmt.Sprintf("%d", hashToObjID[h])) - // Convert objIdPadded into []byte - objIdBytes := []byte(objIdPadded) + // Convert objIDPadded into []byte + objIDBytes := []byte(objIDPadded) // Replace sha1 hash with object id padded for j := pos; j < pos+40; j++ { - objsIdData[i][j] = objIdBytes[j-pos] + objsIDData[i][j] = objIDBytes[j-pos] } } - // Save objsIdHash so that procset dictionary has the correct object ids - f.importedTplIds[hash] = i + nOffset + // Save objsIDHash so that procset dictionary has the correct object ids + f.importedTplIDs[hash] = i + nOffset } // Now, put objects - for i = 0; i < len(objsIdData); i++ { + for i = 0; i < len(objsIDData); i++ { f.newobj() - f.out(string(objsIdData[i])) + f.out(string(objsIDData[i])) } } -// Use imported template from gofpdi - draws imported PDF page onto page +// UseImportedTemplate uses imported template from gofpdi - draws imported PDF page onto page func (f *Fpdf) UseImportedTemplate(tplName string, scaleX float64, scaleY float64, tX float64, tY float64) { f.outf("q 0 J 1 w 0 j 0 G 0 g q %.4F 0 0 %.4F %.4F %.4F cm %s Do Q Q\n", scaleX*f.k, scaleY*f.k, tX*f.k, (tY+f.h)*f.k, tplName) } -// Import gofpdi template names into importedTplObjs - to be included in the procset dictionary +// ImportTemplates imports gofpdi template names into importedTplObjs - to be included in the procset dictionary func (f *Fpdf) ImportTemplates(tpls map[string]string) { - for tplName, tplId := range tpls { - f.importedTplObjs[tplName] = tplId + for tplName, tplID := range tpls { + f.importedTplObjs[tplName] = tplID } } @@ -4260,7 +4260,7 @@ func (f *Fpdf) putxobjectdict() { { for tplName, objID := range f.importedTplObjs { // here replace obj id hash with n - f.outf("%s %d 0 R", tplName, f.importedTplIds[objID]) + f.outf("%s %d 0 R", tplName, f.importedTplIDs[objID]) } } } -- cgit v1.2.1-24-ge1ad From eca8e8f3216d7e07f60db6cea8fd5067d99e3c27 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 15 May 2019 12:43:04 -0400 Subject: Add PDF import as feature in documentation --- README.md | 1 + doc.go | 2 ++ doc/document.md | 1 + 3 files changed, 4 insertions(+) diff --git a/README.md b/README.md index fdd8980..811bbc3 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ support for text, drawing and images. - Templates - Barcodes - Charting facility + - Import PDFs as templates gofpdf has no dependencies other than the Go standard library. All tests pass on Linux, Mac and Windows platforms. diff --git a/doc.go b/doc.go index 3295200..6ad0461 100644 --- a/doc.go +++ b/doc.go @@ -42,6 +42,8 @@ Features - Charting facility +- Import PDFs as templates + gofpdf has no dependencies other than the Go standard library. All tests pass on Linux, Mac and Windows platforms. diff --git a/doc/document.md b/doc/document.md index de39656..fa544af 100644 --- a/doc/document.md +++ b/doc/document.md @@ -29,6 +29,7 @@ text, drawing and images. * Templates * Barcodes * Charting facility +* Import PDFs as templates gofpdf has no dependencies other than the Go standard library. All tests pass on Linux, Mac and Windows platforms. -- cgit v1.2.1-24-ge1ad