mirror of
https://github.com/unidoc/unipdf.git
synced 2025-05-01 22:17:29 +08:00

* Prepared skeleton and basic component implementations for the jbig2 encoding. * Added Bitset. Implemented Bitmap. * Decoder with old Arithmetic Decoder * Partly working arithmetic * Working arithmetic decoder. * MMR patched. * rebuild to apache. * Working generic * Working generic * Decoded full document * Update Jenkinsfile go version [master] (#398) * Update Jenkinsfile go version * Decoded AnnexH document * Minor issues fixed. * Update README.md * Fixed generic region errors. Added benchmark. Added bitmap unpadder. Added Bitmap toImage method. * Fixed endofpage error * Added integration test. * Decoded all test files without errors. Implemented JBIG2Global. * Merged with v3 version * Fixed the EOF in the globals issue * Fixed the JBIG2 ChocolateData Decode * JBIG2 Added license information * Minor fix in jbig2 encoding. * Applied the logging convention * Cleaned unnecessary imports * Go modules clear unused imports * checked out the README.md * Moved trace to Debug. Fixed the build integrate tag in the document_decode_test.go * Initial encoder skeleton * Applied UniPDF Developer Guide. Fixed lint issues. * Cleared documentation, fixed style issues. * Added jbig2 doc.go files. Applied unipdf guide style. * Minor code style changes. * Minor naming and style issues fixes. * Minor naming changes. Style issues fixed. * Review r11 fixes. * Added JBIG2 Encoder skeleton. * Moved Document and Page to jbig2/document package. Created decoder package responsible for decoding jbig2 stream. * Implemented raster functions. * Added raster uni low test funcitons. * Added raster low test functions * untracked files on jbig2-encoder: c869089 Added raster low test functions * index on jbig2-encoder: c869089 Added raster low test functions * Added morph files. * implemented jbig2 encoder basics * JBIG2 Encoder - Generic method * Added jbig2 image encode ttests, black/white image tests * cleaned and tested jbig2 package * unfinished jbig2 classified encoder * jbig2 minor style changes * minor jbig2 encoder changes * prepared JBIG2 Encoder * Style and lint fixes * Minor changes and lints * Fixed shift unsinged value build errors * Minor naming change * Added jbig2 encode, image gondels. Fixed jbig2 decode bug. * Provided jbig2 core.DecodeGlobals function. * Fixed JBIG2Encoder `r6` revision issues. * Removed public JBIG2Encoder document. * Minor style changes * added NewJBIG2Encoder function. * fixed JBIG2Encoder 'r9' revision issues. * Cleared 'r9' commented code. * Updated ACKNOWLEDGEMENETS. Fixed JBIG2Encoder 'r10' revision issues. Co-authored-by: Gunnsteinn Hall <gunnsteinn.hall@gmail.com>
315 lines
7.7 KiB
Go
315 lines
7.7 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 tests
|
|
|
|
import (
|
|
"archive/zip"
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"flag"
|
|
"fmt"
|
|
_ "image/gif"
|
|
"image/jpeg"
|
|
_ "image/jpeg"
|
|
_ "image/png"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/unidoc/unipdf/v3/common"
|
|
"github.com/unidoc/unipdf/v3/contentstream"
|
|
"github.com/unidoc/unipdf/v3/core"
|
|
"github.com/unidoc/unipdf/v3/model"
|
|
|
|
"github.com/unidoc/unipdf/v3/internal/jbig2/document"
|
|
)
|
|
|
|
// register basic image drivers - gif, jpeg, png
|
|
|
|
const (
|
|
// EnvJBIG2Directory is the environment variable that should contain directory path
|
|
// to the jbig2 encoded test files.
|
|
EnvJBIG2Directory = "UNIDOC_JBIG2_TESTDATA"
|
|
// EnvImageDirectory is the environment variable that should contain directory path
|
|
// to the images used for the encoding processes.
|
|
EnvImageDirectory = "UNIDOC_JBIG2_TEST_IMAGES"
|
|
)
|
|
|
|
var (
|
|
// updateGoldens is the runtime flag that states that the md5 hashes
|
|
// for each decoded test case image should be updated.
|
|
updateGoldens bool
|
|
// keepImageFiles is the runtime flag that is used to keep the decoded jbig2 images
|
|
// within the temporary directory: 'os.TempDir()/unipdf/jbig2'.
|
|
keepImageFiles bool
|
|
// logToFile is the flag that allows to log the trace values to the file.
|
|
logToFile bool
|
|
// keepEncodedFile is the runtime flag that is used to keep the jbig2 encoded images
|
|
// within the temporary directory: 'os.TempDir()/unipdf/jbig2'
|
|
keepEncodedFile bool
|
|
)
|
|
|
|
func init() {
|
|
flag.BoolVar(&updateGoldens, "jbig2-update-goldens", false, "updates the golden file hashes on the run")
|
|
flag.BoolVar(&keepImageFiles, "jbig2-store-images", false, "stores the images in the temporary `os.TempDir`/unipdf/jbig2 directory")
|
|
flag.BoolVar(&keepEncodedFile, "jbig2-store-encoded", false, "stores the jbig2 encoded images in the temporary `os.TempDir`/unipdf/jbig2 directory")
|
|
flag.BoolVar(&logToFile, "jbig2-log-to-file", false, "logs trace messages into file localized at `os.TempDir`/unipdf/jbig2 directory")
|
|
}
|
|
|
|
type extractedImage struct {
|
|
jbig2Data []byte
|
|
pdfImage *model.XObjectImage
|
|
name string
|
|
pageNo int
|
|
idx int
|
|
hash string
|
|
globals *document.Globals
|
|
}
|
|
|
|
func (e *extractedImage) fullName() string {
|
|
return fmt.Sprintf("%s_%d_%d", e.name, e.pageNo, e.idx)
|
|
}
|
|
|
|
func extractImages(dirName string, filename string) ([]*extractedImage, error) {
|
|
f, err := getFile(dirName, filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
reader, err := readPDF(f)
|
|
if err != nil && err.Error() != "EOF not found" {
|
|
return nil, err
|
|
}
|
|
|
|
var numPages int
|
|
numPages, err = reader.GetNumPages()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
page *model.PdfPage
|
|
images, tempImages []*extractedImage
|
|
)
|
|
for pageNo := 1; pageNo <= numPages; pageNo++ {
|
|
page, err = reader.GetPage(pageNo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tempImages, err = extractImagesOnPage(dirName, filename, page, pageNo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
images = append(images, tempImages...)
|
|
}
|
|
return images, nil
|
|
}
|
|
|
|
func extractImagesInContentStream(filename, contents string, resources *model.PdfPageResources) ([]*extractedImage, error) {
|
|
extractedImages := []*extractedImage{}
|
|
processedXObjects := make(map[string]bool)
|
|
cstreamParser := contentstream.NewContentStreamParser(contents)
|
|
|
|
operations, err := cstreamParser.Parse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Range through all the content stream operations.
|
|
for _, op := range *operations {
|
|
if !(op.Operand == "Do" && len(op.Params) == 1) {
|
|
continue
|
|
}
|
|
// Do: XObject.
|
|
name := op.Params[0].(*core.PdfObjectName)
|
|
|
|
// Only process each one once.
|
|
_, has := processedXObjects[string(*name)]
|
|
if has {
|
|
continue
|
|
}
|
|
processedXObjects[string(*name)] = true
|
|
|
|
xobj, xtype := resources.GetXObjectByName(*name)
|
|
if xtype == model.XObjectTypeImage {
|
|
filterObj := core.TraceToDirectObject(xobj.Get("Filter"))
|
|
if filterObj == nil {
|
|
continue
|
|
}
|
|
|
|
method, ok := filterObj.(*core.PdfObjectName)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if method.String() != core.StreamEncodingFilterNameJBIG2 {
|
|
continue
|
|
}
|
|
|
|
ximg, err := resources.GetXObjectImageByName(*name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
enc, ok := ximg.Filter.(*core.JBIG2Encoder)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Filter encoder should be a JBIG2Encoder but is: %T", ximg.Filter)
|
|
}
|
|
|
|
extracted := &extractedImage{
|
|
pdfImage: ximg,
|
|
jbig2Data: xobj.Stream,
|
|
globals: enc.Globals.ToDocumentGlobals(),
|
|
}
|
|
|
|
extractedImages = append(extractedImages, extracted)
|
|
} else if xtype == model.XObjectTypeForm {
|
|
// Go through the XObject Form content stream.
|
|
xform, err := resources.GetXObjectFormByName(*name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
formContent, err := xform.GetContentStream()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Process the content stream in the Form object too:
|
|
formResources := xform.Resources
|
|
if formResources == nil {
|
|
formResources = resources
|
|
}
|
|
|
|
// Process the content stream in the Form object too:
|
|
images, err := extractImagesInContentStream(filename, string(formContent), formResources)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
extractedImages = append(extractedImages, images...)
|
|
}
|
|
}
|
|
return extractedImages, nil
|
|
}
|
|
|
|
func extractImagesOnPage(dirname, filename string, page *model.PdfPage, pageNo int) ([]*extractedImage, error) {
|
|
contents, err := page.GetAllContentStreams()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
images, err := extractImagesInContentStream(filepath.Join(dirname, filename), contents, page.Resources)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rawName := rawFileName(filename)
|
|
for i, image := range images {
|
|
image.name = rawName
|
|
image.idx = i + 1
|
|
image.pageNo = pageNo
|
|
}
|
|
return images, nil
|
|
}
|
|
|
|
func getFile(dirName, filename string) (*os.File, error) {
|
|
return os.Open(filepath.Join(dirName, filename))
|
|
}
|
|
|
|
func rawFileName(filename string) string {
|
|
if i := strings.LastIndex(filename, "."); i != -1 {
|
|
return filename[:i]
|
|
}
|
|
return filename
|
|
}
|
|
|
|
func readFileNames(dirname, suffix string) ([]string, error) {
|
|
var files []string
|
|
err := filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
if suffix != "" && !strings.HasSuffix(strings.ToLower(info.Name()), suffix) {
|
|
return nil
|
|
}
|
|
files = append(files, info.Name())
|
|
}
|
|
return nil
|
|
})
|
|
return files, err
|
|
}
|
|
|
|
func readPDF(f *os.File, password ...string) (*model.PdfReader, error) {
|
|
pdfReader, err := model.NewPdfReader(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// check if is encrypted
|
|
isEncrypted, err := pdfReader.IsEncrypted()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if isEncrypted {
|
|
auth, err := pdfReader.Decrypt([]byte(""))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !auth {
|
|
if len(password) > 0 {
|
|
auth, err = pdfReader.Decrypt([]byte(password[0]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if !auth {
|
|
return nil, fmt.Errorf("reading the file: '%s' failed. Invalid password provided", f.Name())
|
|
}
|
|
}
|
|
}
|
|
return pdfReader, nil
|
|
}
|
|
|
|
func writeExtractedImages(zw *zip.Writer, images ...*extractedImage) (err error) {
|
|
h := md5.New()
|
|
|
|
for _, img := range images {
|
|
common.Log.Trace("Writing file: '%s'", img.fullName())
|
|
f, err := zw.Create(img.fullName() + ".jpg")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cimg, err := img.pdfImage.ToImage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gimg, err := cimg.ToGoImage()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
multiWriter := io.MultiWriter(f, h)
|
|
|
|
// write to file
|
|
q := &jpeg.Options{Quality: 100}
|
|
if err = jpeg.Encode(multiWriter, gimg, q); err != nil {
|
|
return err
|
|
}
|
|
|
|
img.hash = hex.EncodeToString(h.Sum(nil))
|
|
h.Reset()
|
|
}
|
|
return nil
|
|
}
|