codeberg.org/go-pdf/fpdf@v0.11.1/fpdf_test.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) 2013-2015 Kurt Jung (Gmail: kurt.w.jung) 7 * 8 * Permission to use, copy, modify, and distribute this software for any 9 * purpose with or without fee is hereby granted, provided that the above 10 * copyright notice and this permission notice appear in all copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 */ 20 21 package fpdf_test 22 23 import ( 24 "bytes" 25 "fmt" 26 "os" 27 "path/filepath" 28 "runtime" 29 "testing" 30 31 "codeberg.org/go-pdf/fpdf" 32 "codeberg.org/go-pdf/fpdf/internal/example" 33 ) 34 35 func init() { 36 cleanup() 37 } 38 39 func cleanup() { 40 _ = filepath.Walk( 41 example.PdfDir(), 42 func(path string, info os.FileInfo, err error) (reterr error) { 43 if info.Mode().IsRegular() { 44 dir, _ := filepath.Split(path) 45 if filepath.Base(dir) != "reference" { 46 if len(path) > 3 { 47 if path[len(path)-4:] == ".pdf" { 48 os.Remove(path) 49 } 50 } 51 } 52 } 53 return 54 }, 55 ) 56 } 57 58 var summaryCompare = example.SummaryCompare 59 60 func init() { 61 if runtime.GOOS == "windows" { 62 summaryCompare = example.Summary 63 } 64 } 65 66 func TestFpdfImplementPdf(t *testing.T) { 67 // this will not compile if Fpdf and Tpl 68 // do not implement Pdf 69 var _ fpdf.Pdf = (*fpdf.Fpdf)(nil) 70 var _ fpdf.Pdf = (*fpdf.Tpl)(nil) 71 } 72 73 // TestPagedTemplate ensures new paged templates work 74 func TestPagedTemplate(t *testing.T) { 75 pdf := fpdf.New("P", "mm", "A4", "") 76 tpl := pdf.CreateTemplate(func(t *fpdf.Tpl) { 77 // this will be the second page, as a page is already 78 // created by default 79 t.AddPage() 80 t.AddPage() 81 t.AddPage() 82 }) 83 84 if tpl.NumPages() != 4 { 85 t.Fatalf("The template does not have the correct number of pages %d", tpl.NumPages()) 86 } 87 88 tplPages := tpl.FromPages() 89 for x := 0; x < len(tplPages); x++ { 90 pdf.AddPage() 91 pdf.UseTemplate(tplPages[x]) 92 } 93 94 // get the last template 95 tpl2, err := tpl.FromPage(tpl.NumPages()) 96 if err != nil { 97 t.Fatal(err) 98 } 99 100 // the objects should be the exact same, as the 101 // template will represent the last page by default 102 // therefore no new id should be set, and the object 103 // should be the same object 104 if fmt.Sprintf("%p", tpl2) != fmt.Sprintf("%p", tpl) { 105 t.Fatal("Template no longer respecting initial template object") 106 } 107 } 108 109 // TestIssue0116 addresses issue 116 in which library silently fails after 110 // calling CellFormat when no font has been set. 111 func TestIssue0116(t *testing.T) { 112 pdf := fpdf.New("P", "mm", "A4", "") 113 pdf.AddPage() 114 pdf.SetFont("Arial", "B", 16) 115 pdf.Cell(40, 10, "OK") 116 if pdf.Error() != nil { 117 t.Fatalf("not expecting error when rendering text") 118 } 119 120 pdf = fpdf.New("P", "mm", "A4", "") 121 pdf.AddPage() 122 pdf.Cell(40, 10, "Not OK") // Font not set 123 if pdf.Error() == nil { 124 t.Fatalf("expecting error when rendering text without having set font") 125 } 126 } 127 128 // TestIssue0193 addresses issue 193 in which the error io.EOF is incorrectly 129 // assigned to the FPDF instance error. 130 func TestIssue0193(t *testing.T) { 131 var png []byte 132 var pdf *fpdf.Fpdf 133 var err error 134 var rdr *bytes.Reader 135 136 png, err = os.ReadFile(example.ImageFile("sweden.png")) 137 if err == nil { 138 rdr = bytes.NewReader(png) 139 pdf = fpdf.New("P", "mm", "A4", "") 140 pdf.AddPage() 141 _ = pdf.RegisterImageOptionsReader("sweden", fpdf.ImageOptions{ImageType: "png", ReadDpi: true}, rdr) 142 err = pdf.Error() 143 } 144 if err != nil { 145 t.Fatalf("issue 193 error: %s", err) 146 } 147 148 } 149 150 // TestIssue0209SplitLinesEqualMultiCell addresses issue 209 151 // make SplitLines and MultiCell split at the same place 152 func TestIssue0209SplitLinesEqualMultiCell(t *testing.T) { 153 pdf := fpdf.New("P", "mm", "A4", "") 154 pdf.AddPage() 155 pdf.SetFont("Arial", "", 8) 156 // this sentence should not be splited 157 str := "Guochin Amandine" 158 lines := pdf.SplitLines([]byte(str), 26) 159 _, FontSize := pdf.GetFontSize() 160 y_start := pdf.GetY() 161 pdf.MultiCell(26, FontSize, str, "", "L", false) 162 y_end := pdf.GetY() 163 164 if len(lines) != 1 { 165 t.Fatalf("expect SplitLines split in one line") 166 } 167 if int(y_end-y_start) != int(FontSize) { 168 t.Fatalf("expect MultiCell split in one line %.2f != %.2f", y_end-y_start, FontSize) 169 } 170 171 // this sentence should be splited in two lines 172 str = "Guiochini Amandine" 173 lines = pdf.SplitLines([]byte(str), 26) 174 y_start = pdf.GetY() 175 pdf.MultiCell(26, FontSize, str, "", "L", false) 176 y_end = pdf.GetY() 177 178 if len(lines) != 2 { 179 t.Fatalf("expect SplitLines split in two lines") 180 } 181 if int(y_end-y_start) != int(FontSize*2) { 182 t.Fatalf("expect MultiCell split in two lines %.2f != %.2f", y_end-y_start, FontSize*2) 183 } 184 } 185 186 // TestFooterFuncLpi tests to make sure the footer is not call twice and SetFooterFuncLpi can work 187 // without SetFooterFunc. 188 func TestFooterFuncLpi(t *testing.T) { 189 pdf := fpdf.New("P", "mm", "A4", "") 190 var ( 191 oldFooterFnc = "oldFooterFnc" 192 bothPages = "bothPages" 193 firstPageOnly = "firstPageOnly" 194 lastPageOnly = "lastPageOnly" 195 ) 196 197 // This set just for testing, only set SetFooterFuncLpi. 198 pdf.SetFooterFunc(func() { 199 pdf.SetY(-15) 200 pdf.SetFont("Arial", "I", 8) 201 pdf.CellFormat(0, 10, oldFooterFnc, 202 "", 0, "C", false, 0, "") 203 }) 204 pdf.SetFooterFuncLpi(func(lastPage bool) { 205 pdf.SetY(-15) 206 pdf.SetFont("Arial", "I", 8) 207 pdf.CellFormat(0, 10, bothPages, "", 0, "L", false, 0, "") 208 if !lastPage { 209 pdf.CellFormat(0, 10, firstPageOnly, "", 0, "C", false, 0, "") 210 } else { 211 pdf.CellFormat(0, 10, lastPageOnly, "", 0, "C", false, 0, "") 212 } 213 }) 214 pdf.AddPage() 215 pdf.SetFont("Arial", "B", 16) 216 for j := 1; j <= 40; j++ { 217 pdf.CellFormat(0, 10, fmt.Sprintf("Printing line number %d", j), 218 "", 1, "", false, 0, "") 219 } 220 if pdf.Error() != nil { 221 t.Fatalf("not expecting error when rendering text") 222 } 223 w := &bytes.Buffer{} 224 if err := pdf.Output(w); err != nil { 225 t.Errorf("unexpected err: %s", err) 226 } 227 b := w.Bytes() 228 if bytes.Contains(b, []byte(oldFooterFnc)) { 229 t.Errorf("not expecting %s render on pdf when FooterFncLpi is set", oldFooterFnc) 230 } 231 got := bytes.Count(b, []byte("bothPages")) 232 if got != 2 { 233 t.Errorf("footer %s should render on two page got:%d", bothPages, got) 234 } 235 got = bytes.Count(b, []byte(firstPageOnly)) 236 if got != 1 { 237 t.Errorf("footer %s should render only on first page got: %d", firstPageOnly, got) 238 } 239 got = bytes.Count(b, []byte(lastPageOnly)) 240 if got != 1 { 241 t.Errorf("footer %s should render only on first page got: %d", lastPageOnly, got) 242 } 243 f := bytes.Index(b, []byte(firstPageOnly)) 244 l := bytes.Index(b, []byte(lastPageOnly)) 245 if f > l { 246 t.Errorf("index %d (%s) should less than index %d (%s)", f, firstPageOnly, l, lastPageOnly) 247 } 248 } 249 250 func TestIssue0069PanicOnSplitTextWithUnicode(t *testing.T) { 251 var str string 252 253 defer func() { 254 if r := recover(); r != nil { 255 t.Errorf("%q make SplitText panic", str) 256 } 257 }() 258 259 pdf := fpdf.New("P", "mm", "A4", "") 260 pdf.AddPage() 261 pdf.SetFont("Arial", "", 8) 262 263 testChars := []string{"«", "»", "—"} 264 265 for _, str = range testChars { 266 _ = pdf.SplitText(str, 100) 267 } 268 269 } 270 271 func TestSplitTextHandleCharacterNotInFontRange(t *testing.T) { 272 var str string 273 274 defer func() { 275 if r := recover(); r != nil { 276 t.Errorf("%q text make SplitText panic", str) 277 } 278 }() 279 280 pdf := fpdf.New("P", "mm", "A4", "") 281 pdf.AddPage() 282 pdf.SetFont("Arial", "", 8) 283 284 // Test values in utf8 beyond the ascii range 285 // I assuming that if the function can handle values in this range 286 // it can handle others since the function basically use the rune codepoint 287 // as a index for the font char width and 1_000_000 elements must be 288 // enough (hopefully!) for the fonts used in the real world. 289 for i := 128; i < 1_000_000; i++ { 290 str = string(rune(i)) 291 _ = pdf.SplitText(str, 100) 292 } 293 294 } 295 296 func TestAFMFontParser(t *testing.T) { 297 const embed = true 298 err := fpdf.MakeFont( 299 example.FontFile("cmmi10.pfb"), 300 example.FontFile("cp1252.map"), 301 example.FontDir(), 302 nil, embed, 303 ) 304 if err != nil { 305 t.Fatalf("could not create cmmi10 font: %v", err) 306 } 307 308 } 309 310 func BenchmarkLineTo(b *testing.B) { 311 pdf := fpdf.New("P", "mm", "A4", "") 312 pdf.AddPage() 313 314 b.ResetTimer() 315 for i := 0; i < b.N; i++ { 316 pdf.LineTo(170, 20) 317 } 318 } 319 320 func BenchmarkCurveTo(b *testing.B) { 321 pdf := fpdf.New("P", "mm", "A4", "") 322 pdf.AddPage() 323 324 b.ResetTimer() 325 for i := 0; i < b.N; i++ { 326 pdf.CurveTo(190, 100, 105, 100) 327 } 328 }