codeberg.org/go-pdf/fpdf@v0.11.1/template.go (about)

     1  // Copyright ©2023 The go-pdf Authors. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6   * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung),
     7   *   Marcus Downing, Jan Slabon (Setasign)
     8   *
     9   * Permission to use, copy, modify, and distribute this software for any
    10   * purpose with or without fee is hereby granted, provided that the above
    11   * copyright notice and this permission notice appear in all copies.
    12   *
    13   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    14   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    15   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    16   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    17   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    18   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    19   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    20   */
    21  
    22  package fpdf
    23  
    24  import (
    25  	"encoding/gob"
    26  	"sort"
    27  )
    28  
    29  // CreateTemplate defines a new template using the current page size.
    30  func (f *Fpdf) CreateTemplate(fn func(*Tpl)) Template {
    31  	return newTpl(PointType{0, 0}, f.curPageSize, f.defOrientation, f.unitStr, f.fontDirStr, fn, f)
    32  }
    33  
    34  // CreateTemplateCustom starts a template, using the given bounds.
    35  func (f *Fpdf) CreateTemplateCustom(corner PointType, size SizeType, fn func(*Tpl)) Template {
    36  	return newTpl(corner, size, f.defOrientation, f.unitStr, f.fontDirStr, fn, f)
    37  }
    38  
    39  // CreateTemplate creates a template that is not attached to any document.
    40  //
    41  // This function is deprecated; it incorrectly assumes that a page with a width
    42  // smaller than its height is oriented in portrait mode, otherwise it assumes
    43  // landscape mode. This causes problems when placing the template in a master
    44  // document where this condition does not apply. CreateTpl() is a similar
    45  // function that lets you specify the orientation to avoid this problem.
    46  func CreateTemplate(corner PointType, size SizeType, unitStr, fontDirStr string, fn func(*Tpl)) Template {
    47  	orientationStr := "p"
    48  	if size.Wd > size.Ht {
    49  		orientationStr = "l"
    50  	}
    51  
    52  	return CreateTpl(corner, size, orientationStr, unitStr, fontDirStr, fn)
    53  }
    54  
    55  // CreateTpl creates a template not attached to any document
    56  func CreateTpl(corner PointType, size SizeType, orientationStr, unitStr, fontDirStr string, fn func(*Tpl)) Template {
    57  	return newTpl(corner, size, orientationStr, unitStr, fontDirStr, fn, nil)
    58  }
    59  
    60  // UseTemplate adds a template to the current page or another template,
    61  // using the size and position at which it was originally written.
    62  func (f *Fpdf) UseTemplate(t Template) {
    63  	if t == nil {
    64  		f.SetErrorf("template is nil")
    65  		return
    66  	}
    67  	corner, size := t.Size()
    68  	f.UseTemplateScaled(t, corner, size)
    69  }
    70  
    71  // UseTemplateScaled adds a template to the current page or another template,
    72  // using the given page coordinates.
    73  func (f *Fpdf) UseTemplateScaled(t Template, corner PointType, size SizeType) {
    74  	if t == nil {
    75  		f.SetErrorf("template is nil")
    76  		return
    77  	}
    78  
    79  	// You have to add at least a page first
    80  	if f.page <= 0 {
    81  		f.SetErrorf("cannot use a template without first adding a page")
    82  		return
    83  	}
    84  
    85  	// make a note of the fact that we actually use this template, as well as any other templates,
    86  	// images or fonts it uses
    87  	f.templates[t.ID()] = t
    88  	for _, tt := range t.Templates() {
    89  		f.templates[tt.ID()] = tt
    90  	}
    91  
    92  	// Create a list of existing image SHA-1 hashes.
    93  	existingImages := map[string]bool{}
    94  	for _, image := range f.images {
    95  		existingImages[image.i] = true
    96  	}
    97  
    98  	// Add each template image to $f, unless already present.
    99  	for name, ti := range t.Images() {
   100  		if _, found := existingImages[ti.i]; found {
   101  			continue
   102  		}
   103  		name = sprintf("t%s-%s", t.ID(), name)
   104  		f.images[name] = ti
   105  	}
   106  
   107  	// template data
   108  	_, templateSize := t.Size()
   109  	scaleX := size.Wd / templateSize.Wd
   110  	scaleY := size.Ht / templateSize.Ht
   111  	tx := corner.X * f.k
   112  	ty := (f.curPageSize.Ht - corner.Y - size.Ht) * f.k
   113  
   114  	f.outf("q %.4f 0 0 %.4f %.4f %.4f cm", scaleX, scaleY, tx, ty) // Translate
   115  	f.outf("/TPL%s Do Q", t.ID())
   116  }
   117  
   118  // Template is an object that can be written to, then used and re-used any number of times within a document.
   119  type Template interface {
   120  	ID() string
   121  	Size() (PointType, SizeType)
   122  	Bytes() []byte
   123  	Images() map[string]*ImageInfoType
   124  	Templates() []Template
   125  	NumPages() int
   126  	FromPage(int) (Template, error)
   127  	FromPages() []Template
   128  	Serialize() ([]byte, error)
   129  	gob.GobDecoder
   130  	gob.GobEncoder
   131  }
   132  
   133  func (f *Fpdf) templateFontCatalog() {
   134  	var keyList []string
   135  	var font fontDefType
   136  	var key string
   137  	f.out("/Font <<")
   138  	for key = range f.fonts {
   139  		keyList = append(keyList, key)
   140  	}
   141  	if f.catalogSort {
   142  		sort.Strings(keyList)
   143  	}
   144  	for _, key = range keyList {
   145  		font = f.fonts[key]
   146  		f.outf("/F%s %d 0 R", font.i, font.N)
   147  	}
   148  	f.out(">>")
   149  }
   150  
   151  // putTemplates writes the templates to the PDF
   152  func (f *Fpdf) putTemplates() {
   153  	filter := ""
   154  	if f.compress {
   155  		filter = "/Filter /FlateDecode "
   156  	}
   157  
   158  	templates := sortTemplates(f.templates, f.catalogSort)
   159  	var t Template
   160  	for _, t = range templates {
   161  		corner, size := t.Size()
   162  
   163  		f.newobj()
   164  		f.templateObjects[t.ID()] = f.n
   165  		f.outf("<<%s/Type /XObject", filter)
   166  		f.out("/Subtype /Form")
   167  		f.out("/Formtype 1")
   168  		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)
   169  		if corner.X != 0 || corner.Y != 0 {
   170  			f.outf("/Matrix [1 0 0 1 %.5f %.5f]", -corner.X*f.k*2, corner.Y*f.k*2)
   171  		}
   172  
   173  		// Template's resource dictionary
   174  		f.out("/Resources ")
   175  		f.out("<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")
   176  
   177  		f.templateFontCatalog()
   178  
   179  		tImages := t.Images()
   180  		tTemplates := t.Templates()
   181  		if len(tImages) > 0 || len(tTemplates) > 0 {
   182  			f.out("/XObject <<")
   183  			{
   184  				var key string
   185  				var keyList []string
   186  				var ti *ImageInfoType
   187  				for key = range tImages {
   188  					keyList = append(keyList, key)
   189  				}
   190  				if gl.catalogSort {
   191  					sort.Strings(keyList)
   192  				}
   193  				for _, key = range keyList {
   194  					// for _, ti := range tImages {
   195  					ti = tImages[key]
   196  					f.outf("/I%s %d 0 R", ti.i, ti.n)
   197  				}
   198  			}
   199  			for _, tt := range tTemplates {
   200  				id := tt.ID()
   201  				if objID, ok := f.templateObjects[id]; ok {
   202  					f.outf("/TPL%s %d 0 R", id, objID)
   203  				}
   204  			}
   205  			f.out(">>")
   206  		}
   207  
   208  		f.out(">>")
   209  
   210  		//  Write the template's byte stream
   211  		buffer := t.Bytes()
   212  		var mem *membuffer
   213  		// fmt.Println("Put template bytes", string(buffer[:]))
   214  		if f.compress {
   215  			mem = xmem.compress(buffer)
   216  			buffer = mem.bytes()
   217  		}
   218  		f.outf("/Length %d >>", len(buffer))
   219  		f.putstream(buffer)
   220  		f.out("endobj")
   221  		if mem != nil {
   222  			mem.release()
   223  		}
   224  	}
   225  }
   226  
   227  func templateKeyList(mp map[string]Template, sort bool) (keyList []string) {
   228  	var key string
   229  	for key = range mp {
   230  		keyList = append(keyList, key)
   231  	}
   232  	if sort {
   233  		gensort(len(keyList),
   234  			func(a, b int) bool {
   235  				return keyList[a] < keyList[b]
   236  			},
   237  			func(a, b int) {
   238  				keyList[a], keyList[b] = keyList[b], keyList[a]
   239  			})
   240  	}
   241  	return
   242  }
   243  
   244  // sortTemplates puts templates in a suitable order based on dependices
   245  func sortTemplates(templates map[string]Template, catalogSort bool) []Template {
   246  	chain := make([]Template, 0, len(templates)*2)
   247  
   248  	// build a full set of dependency chains
   249  	var keyList []string
   250  	var key string
   251  	var t Template
   252  	keyList = templateKeyList(templates, catalogSort)
   253  	for _, key = range keyList {
   254  		t = templates[key]
   255  		tlist := templateChainDependencies(t)
   256  		for _, tt := range tlist {
   257  			if tt != nil {
   258  				chain = append(chain, tt)
   259  			}
   260  		}
   261  	}
   262  
   263  	// reduce that to make a simple list
   264  	sorted := make([]Template, 0, len(templates))
   265  chain:
   266  	for _, t := range chain {
   267  		for _, already := range sorted {
   268  			if t == already {
   269  				continue chain
   270  			}
   271  		}
   272  		sorted = append(sorted, t)
   273  	}
   274  
   275  	return sorted
   276  }
   277  
   278  // templateChainDependencies is a recursive function for determining the full chain of template dependencies
   279  func templateChainDependencies(template Template) []Template {
   280  	requires := template.Templates()
   281  	chain := make([]Template, len(requires)*2)
   282  	for _, req := range requires {
   283  		chain = append(chain, templateChainDependencies(req)...)
   284  	}
   285  	chain = append(chain, template)
   286  	return chain
   287  }
   288  
   289  // < 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 32 20 31  |1 12 0 R./TPL2 1|
   290  // < 0002650  35 20 30 20 52 0a 2f 54  50 4c 31 20 31 34 20 30  |5 0 R./TPL1 14 0|
   291  
   292  // > 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 31 20 31  |1 12 0 R./TPL1 1|
   293  // > 0002650  34 20 30 20 52 0a 2f 54  50 4c 32 20 31 35 20 30  |4 0 R./TPL2 15 0|