summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurt Jung <kurt.w.jung@gmail.com>2015-08-12 16:16:26 -0400
committerKurt Jung <kurt.w.jung@gmail.com>2015-08-12 16:16:26 -0400
commit64e9d36a887ca3f36dc5880153e4b062e0416ac5 (patch)
treeb3e1222cdc4f7aca41b3ac39b4e2168ac77ee36e
parent798fdbd15806d1b27a36d47f394cb58b06a9af1e (diff)
parent0ab52d3f38a38a63fe1bba1a757fe4f28cd6a026 (diff)
Merge branch 'marcusatbang-templating'
-rw-r--r--def.go2
-rw-r--r--doc.go5
-rw-r--r--fpdf.go9
-rw-r--r--fpdf_test.go53
-rw-r--r--template.go204
-rw-r--r--template_impl.go122
-rw-r--r--util.go34
7 files changed, 428 insertions, 1 deletions
diff --git a/def.go b/def.go
index 437449c..f1c8d7b 100644
--- a/def.go
+++ b/def.go
@@ -160,6 +160,8 @@ type Fpdf struct {
page int // current page number
n int // current object number
offsets []int // array of object offsets
+ templates map[int64]Template // templates used in this document
+ templateObjects map[int64]int //template object IDs within this document
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 398ab9b..941f399 100644
--- a/doc.go
+++ b/doc.go
@@ -48,6 +48,8 @@ Features
• Layers
+• Templates
+
gofpdf has no dependencies other than the Go standard library. All tests pass
on Linux, Mac and Windows platforms. Like FPDF version 1.7, from which gofpdf
is derived, this package does not yet support UTF-8 fonts. However, support is
@@ -193,7 +195,8 @@ implementations for dashed line drawing and generalized font loading. Stani
Michiels provided support for multi-segment path drawing with smooth line
joins, line join styles, enhanced fill modes, and has helped greatly with
package presentation and tests. Bruno Michel has provided valuable assistance
-with the code.
+with the code. Templating is adapted from the FPDF_Tpl library created by
+Jan Slabon and Setasign.
Roadmap
diff --git a/fpdf.go b/fpdf.go
index 22de11d..dfd721f 100644
--- a/fpdf.go
+++ b/fpdf.go
@@ -72,6 +72,8 @@ func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType)
f.fonts = make(map[string]fontDefType)
f.fontFiles = make(map[string]fontFileType)
f.diffs = make([]string, 0, 8)
+ f.templates = make(map[int64]Template)
+ f.templateObjects = make(map[int64]int)
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)
@@ -3223,6 +3225,12 @@ func (f *Fpdf) putxobjectdict() {
// foreach($this->images as $image)
f.outf("/I%d %d 0 R", image.i, image.n)
}
+ for _, tpl := range f.templates {
+ id := tpl.ID()
+ if objID, ok := f.templateObjects[id]; ok {
+ f.outf("/TPL%d %d 0 R", id, objID)
+ }
+ }
}
func (f *Fpdf) putresourcedict() {
@@ -3305,6 +3313,7 @@ func (f *Fpdf) putresources() {
return
}
f.putimages()
+ f.putTemplates()
// Resource dictionary
f.offsets[2] = f.buffer.Len()
f.out("2 0 obj")
diff --git a/fpdf_test.go b/fpdf_test.go
index 57a2dd4..e4b8df4 100644
--- a/fpdf_test.go
+++ b/fpdf_test.go
@@ -1686,3 +1686,56 @@ func ExampleFpdf_DrawPath() {
// Output:
// Successfully generated pdf/Fpdf_DrawPath_fill.pdf
}
+
+// This example demonstrates creating and using templates
+func ExampleFpdf_CreateTemplate() {
+ pdf := gofpdf.New("P", "mm", "A4", "")
+ pdf.SetCompression(false)
+ // pdf.SetFont("Times", "", 12)
+ template := pdf.CreateTemplate(func(tpl *gofpdf.Tpl) {
+ tpl.Image(imageFile("logo.png"), 6, 6, 30, 0, false, "", 0, "")
+ tpl.SetFont("Arial", "B", 16)
+ tpl.Text(40, 20, "Template says hello")
+ tpl.SetDrawColor(0, 100, 200)
+ tpl.SetLineWidth(2.5)
+ tpl.Line(95, 12, 105, 22)
+ })
+ _, tplSize := template.Size()
+ // fmt.Println("Size:", tplSize)
+ // fmt.Println("Scaled:", tplSize.ScaleBy(1.5))
+
+ template2 := pdf.CreateTemplate(func(tpl *gofpdf.Tpl) {
+ tpl.UseTemplate(template)
+ subtemplate := tpl.CreateTemplate(func(tpl2 *gofpdf.Tpl) {
+ tpl2.Image(imageFile("logo.png"), 6, 86, 30, 0, false, "", 0, "")
+ tpl2.SetFont("Arial", "B", 16)
+ tpl2.Text(40, 100, "Subtemplate says hello")
+ tpl2.SetDrawColor(0, 200, 100)
+ tpl2.SetLineWidth(2.5)
+ tpl2.Line(102, 92, 112, 102)
+ })
+ tpl.UseTemplate(subtemplate)
+ })
+
+ pdf.SetDrawColor(200, 100, 0)
+ pdf.SetLineWidth(2.5)
+ pdf.SetFont("Arial", "B", 16)
+
+ pdf.AddPage()
+ pdf.UseTemplate(template)
+ pdf.UseTemplateScaled(template, gofpdf.PointType{0, 30}, tplSize)
+ pdf.UseTemplateScaled(template, gofpdf.PointType{0, 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.Line(60, 210, 80, 210)
+ pdf.Text(40, 200, "Template example page 2")
+
+ fileStr := exampleFilename("Fpdf_CreateTemplate")
+ err := pdf.OutputFileAndClose(fileStr)
+ summary(err, fileStr)
+ // Output:
+ // Successfully generated pdf/Fpdf_CreateTemplate.pdf
+}
diff --git a/template.go b/template.go
new file mode 100644
index 0000000..7561ae1
--- /dev/null
+++ b/template.go
@@ -0,0 +1,204 @@
+package gofpdf
+
+/*
+ * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung),
+ * Marcus Downing, Jan Slabon (Setasign)
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+// CreateTemplate defines a new template using the current page size.
+func (f *Fpdf) CreateTemplate(fn func(*Tpl)) Template {
+ return newTpl(PointType{0, 0}, f.curPageSize, f.unitStr, f.fontDirStr, fn, f)
+}
+
+// CreateTemplateCustom starts a template, using the given bounds.
+func (f *Fpdf) CreateTemplateCustom(corner PointType, size SizeType, fn func(*Tpl)) Template {
+ return newTpl(corner, size, f.unitStr, f.fontDirStr, fn, f)
+}
+
+// CreateTemplate creates a template not attached to any document
+func CreateTemplate(corner PointType, size SizeType, unitStr, fontDirStr string, fn func(*Tpl)) Template {
+ return newTpl(corner, size, unitStr, fontDirStr, fn, nil)
+}
+
+// UseTemplate adds a template to the current page or another template,
+// using the size and position at which it was originally written.
+func (f *Fpdf) UseTemplate(t Template) {
+ if t == nil {
+ f.SetErrorf("Template is nil")
+ return
+ }
+ corner, size := t.Size()
+ f.UseTemplateScaled(t, corner, size)
+}
+
+// UseTemplateScaled adds a template to the current page or another template,
+// using the given page coordinates.
+func (f *Fpdf) UseTemplateScaled(t Template, corner PointType, size SizeType) {
+ if t == nil {
+ f.SetErrorf("Template is nil")
+ return
+ }
+
+ // You have to add at least a page first
+ if f.page <= 0 {
+ f.SetErrorf("Cannot use a template without first adding a page")
+ return
+ }
+
+ // make a note of the fact that we actually use this template, as well as any other templates,
+ // images or fonts it uses
+ f.templates[t.ID()] = t
+ for _, tt := range t.Templates() {
+ f.templates[tt.ID()] = tt
+ }
+ for name, ti := range t.Images() {
+ name = sprintf("t%d-%s", t.ID(), name)
+ f.images[name] = ti
+ }
+
+ // template data
+ _, templateSize := t.Size()
+ scaleX := size.Wd / templateSize.Wd
+ scaleY := size.Ht / templateSize.Ht
+ tx := corner.X * f.k
+ ty := (f.curPageSize.Ht - corner.Y - size.Ht) * f.k
+
+ f.outf("q %.4F 0 0 %.4F %.4F %.4F cm", scaleX, scaleY, tx, ty) // Translate
+ f.outf("/TPL%d Do Q", t.ID())
+}
+
+var nextTemplateIDChannel = func() chan int64 {
+ ch := make(chan int64)
+ go func() {
+ var nextID int64 = 1
+ for {
+ ch <- nextID
+ nextID++
+ }
+ }()
+ return ch
+}()
+
+// GenerateTemplateID gives the next template ID. These numbers are global so that they can never clash.
+func GenerateTemplateID() int64 {
+ return <-nextTemplateIDChannel
+}
+
+// Template is an object that can be written to, then used and re-used any number of times within a document.
+type Template interface {
+ ID() int64
+ Size() (PointType, SizeType)
+ Bytes() []byte
+ Images() map[string]*ImageInfoType
+ Templates() []Template
+}
+
+// putTemplates writes the templates to the PDF
+func (f *Fpdf) putTemplates() {
+ filter := ""
+ if f.compress {
+ filter = "/Filter /FlateDecode "
+ }
+
+ templates := sortTemplates(f.templates)
+ var t Template
+ for _, t = range templates {
+ corner, size := t.Size()
+
+ f.newobj()
+ f.templateObjects[t.ID()] = f.n
+ f.outf("<<%s/Type /XObject", filter)
+ f.out("/Subtype /Form")
+ f.out("/Formtype 1")
+ f.outf("/BBox [%.2F %.2F %.2F %.2F]", corner.X*f.k, corner.Y*f.k, (corner.X+size.Wd)*f.k, (corner.Y+size.Ht)*f.k)
+ if corner.X != 0 || corner.Y != 0 {
+ f.outf("/Matrix [1 0 0 1 %.5F %.5F]", -corner.X*f.k*2, corner.Y*f.k*2)
+ }
+
+ // Template's resource dictionary
+ f.out("/Resources ")
+ f.out("<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")
+
+ tImages := t.Images()
+ tTemplates := t.Templates()
+ if len(tImages) > 0 || len(tTemplates) > 0 {
+ f.out("/XObject <<")
+ for _, ti := range tImages {
+ f.outf("/I%d %d 0 R", ti.i, ti.n)
+ }
+ for _, tt := range tTemplates {
+ id := tt.ID()
+ if objID, ok := f.templateObjects[id]; ok {
+ f.outf("/TPL%d %d 0 R", id, objID)
+ }
+ }
+ f.out(">>")
+ }
+
+ f.out(">>")
+
+ // Write the template's byte stream
+ buffer := t.Bytes()
+ // fmt.Println("Put template bytes", string(buffer[:]))
+ if f.compress {
+ buffer = sliceCompress(buffer)
+ }
+ f.outf("/Length %d >>", len(buffer))
+ f.putstream(buffer)
+ f.out("endobj")
+ }
+}
+
+// sortTemplates puts templates in a suitable order based on dependices
+func sortTemplates(templates map[int64]Template) []Template {
+ chain := make([]Template, 0, len(templates)*2)
+
+ // build a full set of dependency chains
+ for _, t := range templates {
+ tlist := templateChainDependencies(t)
+ for _, tt := range tlist {
+ if tt != nil {
+ chain = append(chain, tt)
+ }
+ }
+ }
+
+ // reduce that to make a simple list
+ sorted := make([]Template, 0, len(templates))
+chain:
+ for _, t := range chain {
+ for _, already := range sorted {
+ if t == already {
+ continue chain
+ }
+ }
+ sorted = append(sorted, t)
+ }
+
+ return sorted
+}
+
+// templateChainDependencies is a recursive function for determining the full chain of template dependencies
+func templateChainDependencies(template Template) []Template {
+ requires := template.Templates()
+ chain := make([]Template, len(requires)*2)
+ for _, req := range requires {
+ for _, sub := range templateChainDependencies(req) {
+ chain = append(chain, sub)
+ }
+ }
+ chain = append(chain, template)
+ return chain
+}
diff --git a/template_impl.go b/template_impl.go
new file mode 100644
index 0000000..093e1fd
--- /dev/null
+++ b/template_impl.go
@@ -0,0 +1,122 @@
+package gofpdf
+
+/*
+ * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung),
+ * Marcus Downing, Jan Slabon (Setasign)
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+// newTpl creates a template, copying graphics settings from a template if one is given
+func newTpl(corner PointType, size SizeType, unitStr, fontDirStr string, fn func(*Tpl), copyFrom *Fpdf) Template {
+ orientationStr := "p"
+ if size.Wd > size.Ht {
+ orientationStr = "l"
+ }
+ sizeStr := ""
+
+ fpdf := fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, size)
+ tpl := Tpl{*fpdf}
+ if copyFrom != nil {
+ tpl.loadParamsFromFpdf(copyFrom)
+ }
+ tpl.Fpdf.SetAutoPageBreak(false, 0)
+ tpl.Fpdf.AddPage()
+ fn(&tpl)
+ bytes := tpl.Fpdf.pages[tpl.Fpdf.page].Bytes()
+ templates := make([]Template, 0, len(tpl.Fpdf.templates))
+ for _, t := range tpl.Fpdf.templates {
+ templates = append(templates, t)
+ }
+ images := tpl.Fpdf.images
+
+ id := GenerateTemplateID()
+ template := FpdfTpl{id, corner, size, bytes, images, templates}
+ return &template
+}
+
+// FpdfTpl is a concrete implementation of the Template interface.
+type FpdfTpl struct {
+ id int64
+ corner PointType
+ size SizeType
+ bytes []byte
+ images map[string]*ImageInfoType
+ templates []Template
+}
+
+// ID returns the global template identifier
+func (t *FpdfTpl) ID() int64 {
+ return t.id
+}
+
+// Size gives the bounding dimensions of this template
+func (t *FpdfTpl) Size() (corner PointType, size SizeType) {
+ return t.corner, t.size
+}
+
+// Bytes returns the actual template data, not including resources
+func (t *FpdfTpl) Bytes() []byte {
+ return t.bytes
+}
+
+// Images returns a list of the images used in this template
+func (t *FpdfTpl) Images() map[string]*ImageInfoType {
+ return t.images
+}
+
+// Templates returns a list of templates used in this template
+func (t *FpdfTpl) Templates() []Template {
+ return t.templates
+}
+
+// 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.
+type Tpl struct {
+ Fpdf
+}
+
+func (t *Tpl) loadParamsFromFpdf(f *Fpdf) {
+ t.Fpdf.compress = false
+
+ t.Fpdf.k = f.k
+ t.Fpdf.x = f.x
+ t.Fpdf.y = f.y
+ t.Fpdf.lineWidth = f.lineWidth
+ t.Fpdf.capStyle = f.capStyle
+ t.Fpdf.joinStyle = f.joinStyle
+
+ t.Fpdf.color.draw = f.color.draw
+ t.Fpdf.color.fill = f.color.fill
+ t.Fpdf.color.text = f.color.text
+
+ t.Fpdf.currentFont = f.currentFont
+ t.Fpdf.fontFamily = f.fontFamily
+ t.Fpdf.fontSize = f.fontSize
+ t.Fpdf.fontSizePt = f.fontSizePt
+ t.Fpdf.fontStyle = f.fontStyle
+ t.Fpdf.ws = f.ws
+}
+
+// AddPage does nothing because you cannot add pages to a template
+func (t *Tpl) AddPage() {
+}
+
+// AddPageFormat does nothign becasue you cannot add pages to a template
+func (t *Tpl) AddPageFormat(orientationStr string, size SizeType) {
+}
+
+// SetAutoPageBreak does nothing because you cannot add pages to a template
+func (t *Tpl) SetAutoPageBreak(auto bool, margin float64) {
+}
diff --git a/util.go b/util.go
index ce391f5..11cb016 100644
--- a/util.go
+++ b/util.go
@@ -278,3 +278,37 @@ func (f *Fpdf) UnicodeTranslatorFromDescriptor(cpStr string) (rep func(string) s
}
return
}
+
+// Transform moves a point by given X, Y offset
+func (p *PointType) Transform(x, y float64) PointType {
+ return PointType{p.X + x, p.Y + y}
+}
+
+// Orientation returns the orientation of a given size:
+// "P" for portrait, "L" for landscape
+func (s *SizeType) Orientation() string {
+ if s == nil || s.Ht == s.Wd {
+ return ""
+ }
+ if s.Wd > s.Ht {
+ return "L"
+ }
+ return "P"
+}
+
+// ScaleBy expands a size by a certain factor
+func (s *SizeType) ScaleBy(factor float64) SizeType {
+ return SizeType{s.Wd * factor, s.Ht * factor}
+}
+
+// ScaleToWidth adjusts the height of a size to match the given width
+func (s *SizeType) ScaleToWidth(width float64) SizeType {
+ height := s.Ht * width / s.Wd
+ return SizeType{width, height}
+}
+
+// ScaleToHeight adjsuts the width of a size to match the given height
+func (s *SizeType) ScaleToHeight(height float64) SizeType {
+ width := s.Wd * height / s.Ht
+ return SizeType{width, height}
+}