summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcus Downing <marcus@bang-on.net>2015-08-04 08:20:25 +0100
committerMarcus Downing <marcus@bang-on.net>2015-08-04 08:20:25 +0100
commit8f46928acb094c8838f75d663b2f560dbf934773 (patch)
tree3553df35e576ac4f05569d31403b715568082100
parentc00065ecf3d69b0999d803751618ffbeec7a0b56 (diff)
Templating
-rw-r--r--fpdf_test.go55
-rw-r--r--template.go209
-rw-r--r--template_impl.go131
3 files changed, 394 insertions, 1 deletions
diff --git a/fpdf_test.go b/fpdf_test.go
index 57a2dd4..c21b1ad 100644
--- a/fpdf_test.go
+++ b/fpdf_test.go
@@ -29,7 +29,7 @@ import (
"strconv"
"strings"
- "github.com/jung-kurt/gofpdf"
+ "github.com/marcusatbang/gofpdf"
)
// Absolute path needed for gocov tool; relative OK for test
@@ -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("template")
+ err := pdf.OutputFileAndClose(fileStr)
+ summary(err, fileStr)
+ // Output:
+ // Successfully generated pdf/template.pdf
+}
diff --git a/template.go b/template.go
new file mode 100644
index 0000000..17cea11
--- /dev/null
+++ b/template.go
@@ -0,0 +1,209 @@
+package gofpdf
+
+//
+// GoFPDI
+//
+// Copyright 2015 Marcus Downing
+//
+// FPDI
+//
+// Copyright 2004-2014 Setasign - Jan Slabon
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// 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..9879699
--- /dev/null
+++ b/template_impl.go
@@ -0,0 +1,131 @@
+package gofpdf
+
+//
+// GoFPDI
+//
+// Copyright 2015 Marcus Downing
+//
+// FPDI
+//
+// Copyright 2004-2014 Setasign - Jan Slabon
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import (
+// "bytes"
+)
+
+// 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) {
+}