diff options
| author | Marcus Downing <marcus@bang-on.net> | 2015-08-04 08:20:25 +0100 | 
|---|---|---|
| committer | Marcus Downing <marcus@bang-on.net> | 2015-08-04 08:20:25 +0100 | 
| commit | 8f46928acb094c8838f75d663b2f560dbf934773 (patch) | |
| tree | 3553df35e576ac4f05569d31403b715568082100 | |
| parent | c00065ecf3d69b0999d803751618ffbeec7a0b56 (diff) | |
Templating
| -rw-r--r-- | fpdf_test.go | 55 | ||||
| -rw-r--r-- | template.go | 209 | ||||
| -rw-r--r-- | template_impl.go | 131 | 
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) { +}  | 
