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  }