Skip to content

Commit

Permalink
Added Image support with size, margin and scaling options.
Browse files Browse the repository at this point in the history
  • Loading branch information
ajaxray committed Mar 12, 2018
1 parent 76c6c4e commit 86ae9ef
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 88 deletions.
42 changes: 36 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -26,21 +26,51 @@ 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.
```bash
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

Expand Down
3 changes: 2 additions & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -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
env GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o builds/merge2pdf_windows-386.exe
upx builds/merge2pdf_*
84 changes: 84 additions & 0 deletions img.go
Original file line number Diff line number Diff line change
@@ -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)
}
121 changes: 40 additions & 81 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, "~")
Expand All @@ -50,6 +69,7 @@ func main() {
inputPages = append(inputPages, pages)
}

// fmt.Println(inputPaths)
// fmt.Println(inputPages)
// os.Exit(1)

Expand All @@ -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 {

Expand All @@ -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
}
Loading

0 comments on commit 86ae9ef

Please sign in to comment.