mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-27 13:48:51 +08:00
183 lines
4.3 KiB
Go
183 lines
4.3 KiB
Go
/*
|
|
* This file is subject to the terms and conditions defined in
|
|
* file 'LICENSE.md', which is part of this source code package.
|
|
*/
|
|
|
|
package testutils
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Errors.
|
|
var (
|
|
ErrRenderNotSupported = errors.New("rendering PDF files is not supported on this system")
|
|
)
|
|
|
|
// CopyFile copies the `src` file to `dst`.
|
|
func CopyFile(src, dst string) error {
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
|
|
_, err = io.Copy(out, in)
|
|
return err
|
|
}
|
|
|
|
// ReadPNG reads and returns the specified PNG file.
|
|
func ReadPNG(file string) (image.Image, error) {
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
return png.Decode(f)
|
|
}
|
|
|
|
// HashFile generates an MD5 hash from the contents of the specified file.
|
|
func HashFile(file string) (string, error) {
|
|
f, err := os.Open(file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
h := md5.New()
|
|
if _, err = io.Copy(h, f); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(h.Sum(nil)), nil
|
|
}
|
|
|
|
// CompareImages compares the specified images at pixel level.
|
|
func CompareImages(img1, img2 image.Image) (bool, error) {
|
|
rect := img1.Bounds()
|
|
diff := 0
|
|
for x := 0; x < rect.Size().X; x++ {
|
|
for y := 0; y < rect.Size().Y; y++ {
|
|
r1, g1, b1, _ := img1.At(x, y).RGBA()
|
|
r2, g2, b2, _ := img2.At(x, y).RGBA()
|
|
if r1 != r2 || g1 != g2 || b1 != b2 {
|
|
diff++
|
|
}
|
|
}
|
|
}
|
|
|
|
diffFraction := float64(diff) / float64(rect.Dx()*rect.Dy())
|
|
if diffFraction > 0.0001 {
|
|
fmt.Printf("diff fraction: %v (%d)\n", diffFraction, diff)
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// ComparePNGFiles compares the specified PNG files.
|
|
func ComparePNGFiles(file1, file2 string) (bool, error) {
|
|
// Fast path - compare hashes.
|
|
h1, err := HashFile(file1)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
h2, err := HashFile(file2)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if h1 == h2 {
|
|
return true, nil
|
|
}
|
|
|
|
// Slow path - compare pixel by pixel.
|
|
img1, err := ReadPNG(file1)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
img2, err := ReadPNG(file2)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if img1.Bounds() != img2.Bounds() {
|
|
return false, nil
|
|
}
|
|
|
|
return CompareImages(img1, img2)
|
|
}
|
|
|
|
// RenderPDFToPNGs uses ghostscript (gs) to render the specified PDF file into
|
|
// a set of PNG images (one per page). PNG images will be named xxx-N.png where
|
|
// N is the number of page, starting from 1.
|
|
func RenderPDFToPNGs(pdfPath string, dpi int, outpathTpl string) error {
|
|
if dpi <= 0 {
|
|
dpi = 100
|
|
}
|
|
if _, err := exec.LookPath("gs"); err != nil {
|
|
return ErrRenderNotSupported
|
|
}
|
|
|
|
return exec.Command("gs", "-sDEVICE=pngalpha", "-o", outpathTpl, fmt.Sprintf("-r%d", dpi), pdfPath).Run()
|
|
}
|
|
|
|
// RunRenderTest renders the PDF file specified by `pdfPath` to the `outputDir`
|
|
// and compares the output PNG files to the golden files found at the
|
|
// `baselineRenderPath` location. If the specified PDF file is new (there are
|
|
// no golden files) and the `saveBaseline` parameter is set to true, the
|
|
// output render files are saved to the `baselineRenderPath`.
|
|
func RunRenderTest(t *testing.T, pdfPath, outputDir, baselineRenderPath string, saveBaseline bool) {
|
|
tplName := strings.TrimSuffix(filepath.Base(pdfPath), filepath.Ext(pdfPath))
|
|
t.Run("render", func(t *testing.T) {
|
|
imgPathPrefix := filepath.Join(outputDir, tplName)
|
|
imgPathTpl := imgPathPrefix + "-%d.png"
|
|
if err := RenderPDFToPNGs(pdfPath, 0, imgPathTpl); err != nil {
|
|
t.Skip(err)
|
|
}
|
|
|
|
for i := 1; true; i++ {
|
|
imgPath := fmt.Sprintf("%s-%d.png", imgPathPrefix, i)
|
|
expImgPath := filepath.Join(baselineRenderPath, fmt.Sprintf("%s-%d_exp.png", tplName, i))
|
|
if _, err := os.Stat(imgPath); err != nil {
|
|
break
|
|
}
|
|
t.Logf("%s", expImgPath)
|
|
if _, err := os.Stat(expImgPath); os.IsNotExist(err) {
|
|
if saveBaseline {
|
|
t.Logf("Copying %s -> %s", imgPath, expImgPath)
|
|
CopyFile(imgPath, expImgPath)
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
|
|
t.Run(fmt.Sprintf("page%d", i), func(t *testing.T) {
|
|
t.Logf("Comparing %s vs %s", imgPath, expImgPath)
|
|
ok, err := ComparePNGFiles(imgPath, expImgPath)
|
|
if os.IsNotExist(err) {
|
|
t.Fatal("image file missing")
|
|
} else if !ok {
|
|
t.Fatal("wrong page rendered")
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|