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|