Skip to content

Commit e150213

Browse files
committed
Releasing the first working version
1 parent 053df92 commit e150213

File tree

4 files changed

+219
-1
lines changed

4 files changed

+219
-1
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,6 @@
1212

1313
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
1414
.glide/
15+
16+
# Project specific
17+
merge2pdf

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ If you fix a bug or want to add/improve a feature,
4141
and it's alligned to the focus (merging with ease) of this tool,
4242
I will be glad to accept your PR.
4343

44-
Thanks
44+
### Thanks
45+
46+
This tool was made using the beautiful [Unidoc](https://unidoc.io/) library. Thanks to **Unidoc**.
4547

4648
---
4749
> "This is the Book about which there is no doubt, a guidance for those conscious of Allah" - [Al-Quran](http://quran.com)

main.go

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
"strconv"
9+
"strings"
10+
11+
unicommon "github.com/unidoc/unidoc/common"
12+
pdf "github.com/unidoc/unidoc/pdf/model"
13+
)
14+
15+
func init() {
16+
// Debug log level.
17+
unicommon.SetLogger(unicommon.NewConsoleLogger(unicommon.LogLevelDebug))
18+
}
19+
20+
func main() {
21+
if len(os.Args) < 3 {
22+
fmt.Printf("Requires at least 3 arguments: output_path and 2 input paths(and optional page numbers) \n")
23+
fmt.Printf("Usage: merge2pdf output.pdf input1.pdf input2.pdf~1,2,3 ...\n")
24+
os.Exit(0)
25+
}
26+
27+
outputPath := os.Args[1]
28+
inputPaths := []string{}
29+
inputPages := [][]int{}
30+
31+
// Sanity check the input arguments.
32+
for _, arg := range os.Args[2:] {
33+
//inputPaths = append(inputPaths, arg)
34+
35+
fileInputParts := strings.Split(arg, "~")
36+
inputPaths = append(inputPaths, fileInputParts[0])
37+
pages := []int{}
38+
39+
if len(fileInputParts) > 1 {
40+
for _, e := range strings.Split(fileInputParts[1], ",") {
41+
pageNo, err := strconv.Atoi(strings.Trim(e, " \n"))
42+
if err != nil {
43+
fmt.Errorf("Invalid format! Example of a file input with page numbers: path/to/abc.pdf~1,2,3,5,6")
44+
os.Exit(1)
45+
}
46+
pages = append(pages, pageNo)
47+
}
48+
}
49+
50+
inputPages = append(inputPages, pages)
51+
}
52+
53+
// fmt.Println(inputPages)
54+
// os.Exit(1)
55+
56+
err := mergePdf(inputPaths, inputPages, outputPath)
57+
if err != nil {
58+
fmt.Printf("Error: %v\n", err)
59+
os.Exit(1)
60+
}
61+
62+
fmt.Printf("Complete, see output file: %s\n", outputPath)
63+
}
64+
65+
func mergePdf(inputPaths []string, inputPages [][]int, outputPath string) error {
66+
pdfWriter := pdf.NewPdfWriter()
67+
68+
for i, inputPath := range inputPaths {
69+
70+
f, err := os.Open(inputPath)
71+
if err != nil {
72+
return err
73+
}
74+
defer f.Close()
75+
76+
fileType, typeError := getFileType(f)
77+
if typeError != nil {
78+
return nil
79+
}
80+
81+
if fileType == "directory" {
82+
// @TODO : Read all files in directory
83+
return errors.New(inputPath + " is a drectory.")
84+
} else if fileType == "application/pdf" {
85+
err := addPdfPages(f, inputPages[i], &pdfWriter)
86+
if err != nil {
87+
return err
88+
}
89+
} else if ok, _ := in_array(fileType, []string{"image/jpg", "image/jpeg", "image/png"}); ok {
90+
return errors.New(inputPath + " Images is not supproted yet.")
91+
}
92+
93+
}
94+
95+
fWrite, err := os.Create(outputPath)
96+
if err != nil {
97+
return err
98+
}
99+
defer fWrite.Close()
100+
101+
err = pdfWriter.Write(fWrite)
102+
if err != nil {
103+
return err
104+
}
105+
106+
return nil
107+
}
108+
109+
func getReader(rs io.ReadSeeker) (*pdf.PdfReader, error) {
110+
111+
pdfReader, err := pdf.NewPdfReader(rs)
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
isEncrypted, err := pdfReader.IsEncrypted()
117+
if err != nil {
118+
return nil, err
119+
}
120+
121+
if isEncrypted {
122+
auth, err := pdfReader.Decrypt([]byte(""))
123+
if err != nil {
124+
return nil, err
125+
}
126+
if !auth {
127+
return nil, errors.New("Cannot merge encrypted, password protected document")
128+
}
129+
}
130+
131+
return pdfReader, nil
132+
}
133+
134+
func addPdfPages(file io.ReadSeeker, pages []int, writer *pdf.PdfWriter) error {
135+
pdfReader, err := getReader(file)
136+
if err != nil {
137+
return err
138+
}
139+
140+
if len(pages) > 0 {
141+
for _, pageNo := range pages {
142+
if page, pageErr := pdfReader.GetPage(pageNo); pageErr != nil {
143+
return pageErr
144+
} else {
145+
err = writer.AddPage(page)
146+
}
147+
}
148+
} else {
149+
numPages, err := pdfReader.GetNumPages()
150+
if err != nil {
151+
return err
152+
}
153+
for i := 0; i < numPages; i++ {
154+
pageNum := i + 1
155+
156+
page, err := pdfReader.GetPage(pageNum)
157+
if err != nil {
158+
return err
159+
}
160+
161+
if err = writer.AddPage(page); err != nil {
162+
return err
163+
}
164+
}
165+
}
166+
167+
return nil
168+
}

util.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"net/http"
5+
"os"
6+
"reflect"
7+
)
8+
9+
func in_array(val interface{}, array interface{}) (exists bool, index int) {
10+
exists = false
11+
index = -1
12+
13+
switch reflect.TypeOf(array).Kind() {
14+
case reflect.Slice:
15+
s := reflect.ValueOf(array)
16+
17+
for i := 0; i < s.Len(); i++ {
18+
if reflect.DeepEqual(val, s.Index(i).Interface()) == true {
19+
index = i
20+
exists = true
21+
return
22+
}
23+
}
24+
}
25+
26+
return
27+
}
28+
29+
func getFileType(file *os.File) (string, error) {
30+
// Only the first 512 bytes are used to sniff the content type.
31+
if info, stateErr := file.Stat(); stateErr != nil {
32+
return "error", stateErr
33+
} else if info.IsDir() {
34+
return "directory", nil
35+
} else {
36+
buffer := make([]byte, 512)
37+
_, readError := file.Read(buffer)
38+
if readError != nil {
39+
return "error", readError
40+
}
41+
42+
// Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
43+
return http.DetectContentType(buffer), nil
44+
}
45+
}

0 commit comments

Comments
 (0)