first commit
This commit is contained in:
11
internal/pdfmerge/consts.go
Normal file
11
internal/pdfmerge/consts.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package pdfmerge
|
||||
|
||||
var size, margin string
|
||||
var scaleH, scaleW, landscape bool
|
||||
var JPEGQuality int
|
||||
|
||||
const (
|
||||
DefaultSize = "IMG-SIZE"
|
||||
DefaultMargin = "0,0,0,0"
|
||||
VERSION = "1.2.0"
|
||||
)
|
||||
41
internal/pdfmerge/dir.go
Normal file
41
internal/pdfmerge/dir.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package pdfmerge
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"pdfmerge/internal/pdf/creator"
|
||||
)
|
||||
|
||||
type DirSource struct {
|
||||
path string
|
||||
files []Mergeable
|
||||
}
|
||||
|
||||
func (s DirSource) MergeTo(c *creator.Creator) (err error) {
|
||||
for _, mergeableFile := range s.files {
|
||||
err = mergeableFile.MergeTo(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DirSource) scanMergeables() error {
|
||||
filesInDir, err := os.ReadDir(s.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range filesInDir {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
mergableFiles, err := getMergeableFile(filepath.Join(s.path, file.Name()), []int{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.files = append(s.files, mergableFiles)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
129
internal/pdfmerge/img.go
Normal file
129
internal/pdfmerge/img.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package pdfmerge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"os"
|
||||
|
||||
"pdfmerge/internal/pdf/creator"
|
||||
|
||||
"pdfmerge/internal/pdf/core"
|
||||
|
||||
"golang.org/x/image/tiff"
|
||||
)
|
||||
|
||||
var pageMargin [4]float64
|
||||
var pageSize creator.PageSize
|
||||
var sizeHasSet, marginHasSet = false, false
|
||||
var tiffExts = []string{".tiff", ".TIFF", ".tif", ".TIF"}
|
||||
|
||||
type ImgSource struct {
|
||||
source
|
||||
}
|
||||
|
||||
func (s ImgSource) MergeTo(c *creator.Creator) error {
|
||||
return addImage(s.source, c)
|
||||
}
|
||||
|
||||
func addImage(s source, c *creator.Creator) error {
|
||||
img, err := createImage(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The following funcs must be called in sequence
|
||||
setEncoding(img, s)
|
||||
setMargin(img, c)
|
||||
setSize(img, c)
|
||||
|
||||
c.NewPage()
|
||||
err = c.Draw(img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createImage(s source) (*creator.Image, error) {
|
||||
if in_array(s.ext, tiffExts) {
|
||||
f, _ := os.Open(s.path)
|
||||
i, err := tiff.Decode(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return creator.NewImageFromGoImage(i)
|
||||
}
|
||||
return creator.NewImageFromFile(s.path)
|
||||
}
|
||||
|
||||
func setMargin(img *creator.Image, c *creator.Creator) error {
|
||||
if !marginHasSet {
|
||||
for i, m := range strings.Split(margin, ",") {
|
||||
floatVal, err := strconv.ParseFloat(m, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: -m|--margin MUST be 4 comma separated int/float numbers. %s found", m)
|
||||
}
|
||||
|
||||
pageMargin[i] = floatVal * creator.PPI
|
||||
}
|
||||
if len(pageMargin) != 4 {
|
||||
return fmt.Errorf("error: -m|--margin MUST be 4 comma separated int/float numbers. %s provided", margin)
|
||||
}
|
||||
marginHasSet = true
|
||||
}
|
||||
c.SetPageMargins(pageMargin[0], pageMargin[1], pageMargin[2], pageMargin[3])
|
||||
img.SetPos(pageMargin[0], pageMargin[3])
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSize(img *creator.Image, c *creator.Creator) error {
|
||||
if size == DefaultSize {
|
||||
// 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:
|
||||
return fmt.Errorf("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]))
|
||||
}
|
||||
}
|
||||
if landscape {
|
||||
c.SetPageSize(creator.PageSize{pageSize[1], pageSize[0]})
|
||||
} else {
|
||||
c.SetPageSize(pageSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set appropriate encoding for JPEG and TIFF
|
||||
// MUST be called before changing image size
|
||||
func setEncoding(img *creator.Image, s source) {
|
||||
switch s.mime {
|
||||
case "image/jpeg":
|
||||
encoder := core.NewDCTEncoder()
|
||||
encoder.Quality = JPEGQuality
|
||||
// Encoder dimensions must match the raw image data dimensions.
|
||||
encoder.Width = int(img.Width())
|
||||
encoder.Height = int(img.Height())
|
||||
img.SetEncoder(encoder)
|
||||
}
|
||||
}
|
||||
82
internal/pdfmerge/pdf.go
Normal file
82
internal/pdfmerge/pdf.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package pdfmerge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"pdfmerge/internal/pdf/creator"
|
||||
|
||||
pdf "pdfmerge/internal/pdf/model"
|
||||
)
|
||||
|
||||
type PDFSource struct {
|
||||
source
|
||||
}
|
||||
|
||||
func (s PDFSource) MergeTo(c *creator.Creator) error {
|
||||
f, _ := os.Open(s.path)
|
||||
defer f.Close()
|
||||
|
||||
return addPdfPages(f, s.pages, c)
|
||||
}
|
||||
|
||||
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 *os.File, 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 err
|
||||
}
|
||||
122
internal/pdfmerge/source.go
Normal file
122
internal/pdfmerge/source.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package pdfmerge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"pdfmerge/internal/pdf/creator"
|
||||
)
|
||||
|
||||
type Mergeable interface {
|
||||
MergeTo(c *creator.Creator) error
|
||||
}
|
||||
|
||||
type source struct {
|
||||
path, sourceType, mime, ext string
|
||||
pages []int
|
||||
}
|
||||
|
||||
// Initiate new source file from input argument
|
||||
func NewSource(input string) (Mergeable, error) {
|
||||
fileInputParts := strings.Split(input, "~")
|
||||
|
||||
path := fileInputParts[0]
|
||||
var inputSource Mergeable
|
||||
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch mode := info.Mode(); {
|
||||
case mode.IsDir():
|
||||
inputSource = getMergeableDir(path)
|
||||
case mode.IsRegular():
|
||||
pages := []int{}
|
||||
if len(fileInputParts) > 1 {
|
||||
pages = parsePageNums(fileInputParts[1])
|
||||
}
|
||||
inputSource, err = getMergeableFile(path, pages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return inputSource, nil
|
||||
}
|
||||
|
||||
func getMergeableDir(path string) Mergeable {
|
||||
dir := DirSource{path: path}
|
||||
dir.scanMergeables()
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
func getMergeableFile(path string, pages []int) (Mergeable, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read source file: %s", path)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
ext := filepath.Ext(f.Name())
|
||||
mime, err := getMimeType(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in getting mime type of file: %s", path)
|
||||
}
|
||||
|
||||
sourceType, err := getFileType(mime, ext)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error : %s (%s)", err.Error(), path)
|
||||
}
|
||||
|
||||
source := source{path, sourceType, mime, ext, pages}
|
||||
|
||||
var m Mergeable
|
||||
switch sourceType {
|
||||
case "image":
|
||||
m = ImgSource{source}
|
||||
case "pdf":
|
||||
m = PDFSource{source}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func getFileType(mime, ext string) (string, error) {
|
||||
pdfExts := []string{".pdf", ".PDF"}
|
||||
imgExts := []string{".jpg", ".jpeg", ".gif", ".png", ".tiff", ".tif", ".JPG", ".JPEG", ".GIF", ".PNG", ".TIFF", ".TIF"}
|
||||
|
||||
switch {
|
||||
case mime == "application/pdf":
|
||||
return "pdf", nil
|
||||
case mime[:6] == "image/":
|
||||
return "image", nil
|
||||
case mime == "application/octet-stream" && in_array(ext, pdfExts):
|
||||
return "pdf", nil
|
||||
case mime == "application/octet-stream" && in_array(ext, imgExts):
|
||||
return "image", nil
|
||||
}
|
||||
|
||||
return "error", errors.New("file type not acceptable. ")
|
||||
}
|
||||
|
||||
func parsePageNums(pagesInput string) []int {
|
||||
pages := []int{}
|
||||
|
||||
for _, e := range strings.Split(pagesInput, ",") {
|
||||
pageNo, err := strconv.Atoi(strings.Trim(e, " \n"))
|
||||
if err != nil {
|
||||
fmt.Printf("invalid format! Example of a file input with page numbers: path/to/abc.pdf~1,2,3,5,6")
|
||||
os.Exit(1)
|
||||
}
|
||||
pages = append(pages, pageNo)
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
41
internal/pdfmerge/util.go
Normal file
41
internal/pdfmerge/util.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package pdfmerge
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func in_array(val, array any) bool {
|
||||
return at_array(val, array) != -1
|
||||
}
|
||||
|
||||
func at_array(val, array any) (index int) {
|
||||
index = -1
|
||||
|
||||
switch reflect.TypeOf(array).Kind() {
|
||||
case reflect.Slice:
|
||||
s := reflect.ValueOf(array)
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
if reflect.DeepEqual(val, s.Index(i).Interface()) {
|
||||
index = i
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getMimeType(file *os.File) (string, error) {
|
||||
// Only the first 512 bytes are used to sniff the content type.
|
||||
buffer := make([]byte, 512)
|
||||
_, readError := file.Read(buffer)
|
||||
if readError != nil {
|
||||
return "error", readError
|
||||
}
|
||||
|
||||
// Always returns a valid content-type and "application/octet-stream" if no others seemed to match.
|
||||
return http.DetectContentType(buffer), nil
|
||||
}
|
||||
Reference in New Issue
Block a user