From 86ae9ef54a6550a5c505ec84e878f1672dcc6bc3 Mon Sep 17 00:00:00 2001 From: Anis Ahmad Date: Mon, 12 Mar 2018 13:46:37 +0600 Subject: [PATCH] Added Image support with size, margin and scaling options. --- README.md | 42 ++++++++++++++++--- build.sh | 3 +- img.go | 84 +++++++++++++++++++++++++++++++++++++ main.go | 121 ++++++++++++++++++------------------------------------ pdf.go | 70 +++++++++++++++++++++++++++++++ 5 files changed, 232 insertions(+), 88 deletions(-) create mode 100644 img.go create mode 100644 pdf.go diff --git a/README.md b/README.md index d842b6b..748274a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# merge2pdf - Simplest tool for merging into PDF +# merge2pdf - Simplest tool for merging Images and PDFs into PDF -Merge Image (jpeg, png) and PDF files (optionally with selective pages) with lossless quality. +Merge Image and PDF files (optionally with selective pages) with lossless quality. It will not convert PDF pages (with texts, images, forms) into flat image, everything will remain as is. ### Install @@ -26,7 +26,31 @@ merge2pdf output.pdf input1.pdf input2.pdf path/to/other.pdf ... # Merge 1st page of input1.pdf, full input2.pdf and 2nd, 3rd, 4th page of input3.pdf merge2pdf output.pdf input1.pdf~1 input2.pdf input3.pdf~2,3,4 + +# Merge multiple Images +merge2pdf output.pdf image1.jpg image2.jpg path/to/other.png ... + +# Mixing up PDF, PDF Pages and Images +merge2pdf output.pdf doc1.pdf~1,2 image1.jpg image2.png path/to/other.pdf ... +``` + +### Fine tuning Image pages + +When adding Images, by default the size for image containing pages will be same to image size with 1 inch margin on each side. But you may set custom margins and resize to standard Print size. +```bash +# Set image page size to A4. Other Options are A3, Legal and Letter +merge2pdf -s A4 output.pdf image1.jpg image2.jpg + +# Set image page size and margin (left, right, top, bottom). +merge2pdf -s A3 -m .5,.5,1,1 output.pdf image1.jpg image2.jpg +# margin can be set without size + +# Scale images to page width, with aspect ratio +# To scale to height, use --scale-height +merge2pdf -s A3 -m .5,.5,1,1 --scale-width output.pdf image1.jpg image2.jpg ``` +_Note: When adding images and PDFs together, these options will effect ONLY Image pages._ + If your filename contains space or [some special characters](https://unix.stackexchange.com/a/270979), then quote the filepaths along with page numbers. For safety, you can quote them always. @@ -34,13 +58,19 @@ then quote the filepaths along with page numbers. For safety, you can quote them merge2pdf output.pdf "With Space.pdf" "without-space.pdf" "with space and pages.pdf~2,3,4" ``` +To see details of options, +```bash +merge2pdf -h +``` + ### Roadmap ✅ Merge multiple PDFs without loosing quality -✅ Merge multiple PDFs with selective pages -◻️ Adding Images -◻️ Mixing up Images and PDFs -◻️ Option to Resize Images to reduce filesize +✅ Merge multiple PDFs with **selective pages** +✅ Adding Images +✅ Mixing up Images and PDFs +◻️ Merge all (Image and PDF) files from directory +✅ Option to Resize Images to reduce filesize ◻️ Option to Greyscale Images to reduce filesize ◻️ Option to set files and pages as JSON config to make usages from other app more convenient diff --git a/build.sh b/build.sh index 95339b9..4b313a6 100644 --- a/build.sh +++ b/build.sh @@ -1,4 +1,5 @@ env GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o builds/merge2pdf_darwin-amd64 env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o builds/merge2pdf_linux-amd64 env GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o builds/merge2pdf_linux-arm64 -env GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o builds/merge2pdf_windows-386.exe \ No newline at end of file +env GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o builds/merge2pdf_windows-386.exe +upx builds/merge2pdf_* \ No newline at end of file diff --git a/img.go b/img.go new file mode 100644 index 0000000..265e66e --- /dev/null +++ b/img.go @@ -0,0 +1,84 @@ +package main + +import ( + "log" + "strconv" + "strings" + + "github.com/unidoc/unidoc/pdf/creator" +) + +var pageMargin [4]float64 +var pageSize creator.PageSize +var sizeHasSet, merginHasSet = false, false + +// func addImageToPdf(inputPath string, outputPath string, imagePath string, pageNum int, xPos float64, yPos float64, iwidth float64) error { +func addImage(filePath string, c *creator.Creator) error { + + img, err := creator.NewImageFromFile(filePath) + if err != nil { + return err + } + + setMargin(img, c) + setSize(img, c) + + c.NewPage() + err = c.Draw(img) + if err != nil { + return err + } + + return nil +} + +func setMargin(img *creator.Image, c *creator.Creator) { + + if !merginHasSet { + for i, m := range strings.Split(margin, ",") { + floatVal, err := strconv.ParseFloat(m, 64) + if err != nil { + log.Fatalln("Error: -m|--margin MUST be 4 comma separated int/float numbers. %s found.", m) + } + + pageMargin[i] = floatVal * creator.PPI + } + } + + c.SetPageMargins(pageMargin[0], pageMargin[1], pageMargin[2], pageMargin[3]) + img.SetPos(pageMargin[0], pageMargin[3]) +} + +func setSize(img *creator.Image, c *creator.Creator) { + if size == DefaultSize { + // sizeHasSet will remain false so that it changes for each image + + // Width height with adding margin + w := img.Width() + pageMargin[0] + pageMargin[1] + h := img.Height() + pageMargin[2] + pageMargin[3] + + pageSize = creator.PageSize{w, h} + } else { + sizeHasSet = true + switch size { + case "A4": + pageSize = creator.PageSizeA4 + case "A3": + pageSize = creator.PageSizeA3 + case "Legal": + pageSize = creator.PageSizeLegal + case "Letter": + pageSize = creator.PageSizeLetter + default: + log.Fatalln("Error: -s|--size MUST be one of A4, A3, Legal or Letter. %s given.", size) + } + + if scaleH { + img.ScaleToHeight(pageSize[1] - (pageMargin[2] + pageMargin[3])) + } else if scaleW { + img.ScaleToWidth(pageSize[0] - (pageMargin[0] + pageMargin[1])) + } + } + + c.SetPageSize(pageSize) +} diff --git a/main.go b/main.go index 69197af..5ed467d 100644 --- a/main.go +++ b/main.go @@ -3,33 +3,52 @@ package main import ( "errors" "fmt" - "io" "os" "strconv" "strings" + flag "github.com/ogier/pflag" unicommon "github.com/unidoc/unidoc/common" - pdf "github.com/unidoc/unidoc/pdf/model" + "github.com/unidoc/unidoc/pdf/creator" +) + +var size, margin string +var scaleH, scaleW bool + +const ( + DefaultSize = "IMG-SIZE" + DefaultMargin = "1,1,1,1" ) func init() { // Debug log level. unicommon.SetLogger(unicommon.NewConsoleLogger(unicommon.LogLevelDebug)) + + flag.StringVarP(&size, "size", "s", DefaultSize, "Resize image pages to print size. One of A4, A3, Legal or Letter.") + flag.StringVarP(&margin, "margin", "m", DefaultMargin, "Comma separated numbers for left, right, top, bottm side margin in inch.") + flag.BoolVarP(&scaleW, "scale-width", "w", false, "Scale Image to page width. Only if --size specified.") + flag.BoolVarP(&scaleH, "scale-height", "h", false, "Scale Image to page height. Only if --size specified.") + + flag.Usage = func() { + fmt.Println("Requires at least 3 arguments: output_path and 2 input paths (and optional page numbers)") + fmt.Println("Usage: merge2pdf output.pdf input1.pdf input2.pdf~1,2,3 input3.jpg /path/to/dir ...") + flag.PrintDefaults() + } } func main() { - if len(os.Args) < 3 { - fmt.Printf("Requires at least 3 arguments: output_path and 2 input paths (and optional page numbers) \n") - fmt.Printf("Usage: merge2pdf output.pdf input1.pdf input2.pdf~1,2,3 ...\n") + flag.Parse() + args := flag.Args() + if len(args) < 2 { + flag.Usage() os.Exit(0) } - outputPath := os.Args[1] + outputPath := args[0] inputPaths := []string{} inputPages := [][]int{} - // Sanity check the input arguments. - for _, arg := range os.Args[2:] { + for _, arg := range flag.Args()[1:] { //inputPaths = append(inputPaths, arg) fileInputParts := strings.Split(arg, "~") @@ -50,6 +69,7 @@ func main() { inputPages = append(inputPages, pages) } + // fmt.Println(inputPaths) // fmt.Println(inputPages) // os.Exit(1) @@ -63,7 +83,7 @@ func main() { } func mergePdf(inputPaths []string, inputPages [][]int, outputPath string) error { - pdfWriter := pdf.NewPdfWriter() + c := creator.New() for i, inputPath := range inputPaths { @@ -75,94 +95,33 @@ func mergePdf(inputPaths []string, inputPages [][]int, outputPath string) error fileType, typeError := getFileType(f) if typeError != nil { - return nil + return typeError } if fileType == "directory" { // @TODO : Read all files in directory return errors.New(inputPath + " is a drectory.") } else if fileType == "application/pdf" { - err := addPdfPages(f, inputPages[i], &pdfWriter) - if err != nil { - return err - } - } else if ok, _ := in_array(fileType, []string{"image/jpg", "image/jpeg", "image/png"}); ok { - return errors.New(inputPath + " Images is not supproted yet.") - } - - } - - fWrite, err := os.Create(outputPath) - if err != nil { - return err - } - defer fWrite.Close() + err = addPdfPages(f, inputPages[i], c) + } else if fileType[:6] == "image/" { + err = addImage(inputPath, c) - err = pdfWriter.Write(fWrite) - if err != nil { - return err - } + fmt.Println("%+v", pageMargin) + fmt.Println("%+v", pageSize) - return nil -} - -func getReader(rs io.ReadSeeker) (*pdf.PdfReader, error) { - - pdfReader, err := pdf.NewPdfReader(rs) - if err != nil { - return nil, err - } - - isEncrypted, err := pdfReader.IsEncrypted() - if err != nil { - return nil, err - } + } else { + err = errors.New("Unsupported type:" + inputPath) + } - if isEncrypted { - auth, err := pdfReader.Decrypt([]byte("")) if err != nil { - return nil, err - } - if !auth { - return nil, errors.New("Cannot merge encrypted, password protected document") + return err } } - return pdfReader, nil -} - -func addPdfPages(file io.ReadSeeker, pages []int, writer *pdf.PdfWriter) error { - pdfReader, err := getReader(file) + err := c.WriteToFile(outputPath) if err != nil { return err } - if len(pages) > 0 { - for _, pageNo := range pages { - if page, pageErr := pdfReader.GetPage(pageNo); pageErr != nil { - return pageErr - } else { - err = writer.AddPage(page) - } - } - } else { - numPages, err := pdfReader.GetNumPages() - if err != nil { - return err - } - for i := 0; i < numPages; i++ { - pageNum := i + 1 - - page, err := pdfReader.GetPage(pageNum) - if err != nil { - return err - } - - if err = writer.AddPage(page); err != nil { - return err - } - } - } - return nil } diff --git a/pdf.go b/pdf.go new file mode 100644 index 0000000..bb9db2b --- /dev/null +++ b/pdf.go @@ -0,0 +1,70 @@ +package main + +import ( + "errors" + "io" + + "github.com/unidoc/unidoc/pdf/creator" + pdf "github.com/unidoc/unidoc/pdf/model" +) + +func getReader(rs io.ReadSeeker) (*pdf.PdfReader, error) { + + pdfReader, err := pdf.NewPdfReader(rs) + if err != nil { + return nil, err + } + + 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 { + return nil, errors.New("Cannot merge encrypted, password protected document") + } + } + + return pdfReader, nil +} + +func addPdfPages(file io.ReadSeeker, pages []int, c *creator.Creator) error { + pdfReader, err := getReader(file) + if err != nil { + return err + } + + if len(pages) > 0 { + for _, pageNo := range pages { + if page, pageErr := pdfReader.GetPage(pageNo); pageErr != nil { + return pageErr + } else { + err = c.AddPage(page) + } + } + } else { + numPages, err := pdfReader.GetNumPages() + if err != nil { + return err + } + for i := 0; i < numPages; i++ { + pageNum := i + 1 + + page, err := pdfReader.GetPage(pageNum) + if err != nil { + return err + } + + if err = c.AddPage(page); err != nil { + return err + } + } + } + + return nil +}