github.com/pdfcpu/pdfcpu@v0.11.1/pkg/api/merge.go (about)

     1  /*
     2  	Copyright 2020 The pdfcpu Authors.
     3  
     4  	Licensed under the Apache License, Version 2.0 (the "License");
     5  	you may not use this file except in compliance with the License.
     6  	You may obtain a copy of the License at
     7  
     8  		http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  	Unless required by applicable law or agreed to in writing, software
    11  	distributed under the License is distributed on an "AS IS" BASIS,
    12  	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  	See the License for the specific language governing permissions and
    14  	limitations under the License.
    15  */
    16  
    17  package api
    18  
    19  import (
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  	"strconv"
    24  
    25  	"github.com/pdfcpu/pdfcpu/pkg/log"
    26  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
    27  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
    28  	"github.com/pkg/errors"
    29  )
    30  
    31  // appendTo appends rs to ctxDest's page tree.
    32  func appendTo(rs io.ReadSeeker, fName string, ctxDest *model.Context, dividerPage bool) error {
    33  	ctxSource, err := ReadAndValidate(rs, ctxDest.Configuration)
    34  	if err != nil {
    35  		return err
    36  	}
    37  
    38  	if ctxDest.XRefTable.Version() < model.V20 && ctxSource.XRefTable.Version() == model.V20 {
    39  		return pdfcpu.ErrUnsupportedVersion
    40  	}
    41  
    42  	// Merge source context into dest context.
    43  	return pdfcpu.MergeXRefTables(fName, ctxSource, ctxDest, false, dividerPage)
    44  }
    45  
    46  // MergeRaw merges a sequence of PDF streams and writes the result to w.
    47  func MergeRaw(rsc []io.ReadSeeker, w io.Writer, dividerPage bool, conf *model.Configuration) error {
    48  	if rsc == nil {
    49  		return errors.New("pdfcpu: MergeRaw: missing rsc")
    50  	}
    51  
    52  	if w == nil {
    53  		return errors.New("pdfcpu: MergeRaw: missing w")
    54  	}
    55  
    56  	if conf == nil {
    57  		conf = model.NewDefaultConfiguration()
    58  	}
    59  	conf.Cmd = model.MERGECREATE
    60  	conf.ValidationMode = model.ValidationRelaxed
    61  	conf.CreateBookmarks = false
    62  
    63  	ctxDest, err := ReadAndValidate(rsc[0], conf)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	ctxDest.EnsureVersionForWriting()
    69  
    70  	for i, f := range rsc[1:] {
    71  		if err = appendTo(f, strconv.Itoa(i), ctxDest, dividerPage); err != nil {
    72  			return err
    73  		}
    74  	}
    75  
    76  	if conf.OptimizeBeforeWriting {
    77  		if err = OptimizeContext(ctxDest); err != nil {
    78  			return err
    79  		}
    80  	}
    81  
    82  	return WriteContext(ctxDest, w)
    83  }
    84  
    85  func prepDestContext(destFile string, rs io.ReadSeeker, conf *model.Configuration) (*model.Context, error) {
    86  	ctxDest, err := ReadAndValidate(rs, conf)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	if conf.CreateBookmarks {
    92  		if err := pdfcpu.EnsureOutlines(ctxDest, filepath.Base(destFile), conf.Cmd == model.MERGEAPPEND); err != nil {
    93  			return nil, err
    94  		}
    95  	}
    96  
    97  	if ctxDest.XRefTable.Version() < model.V20 {
    98  		ctxDest.EnsureVersionForWriting()
    99  	}
   100  
   101  	return ctxDest, nil
   102  }
   103  
   104  func appendFile(fName string, ctxDest *model.Context, dividerPage bool) error {
   105  	f, err := os.Open(fName)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	defer f.Close()
   110  
   111  	if log.CLIEnabled() {
   112  		log.CLI.Println(fName)
   113  	}
   114  	return appendTo(f, filepath.Base(fName), ctxDest, dividerPage)
   115  }
   116  
   117  // Merge concatenates inFiles.
   118  // if destFile is supplied it appends the result to destfile (=MERGEAPPEND)
   119  // if no destFile supplied it writes the result to the first entry of inFiles (=MERGECREATE).
   120  func Merge(destFile string, inFiles []string, w io.Writer, conf *model.Configuration, dividerPage bool) error {
   121  	if w == nil {
   122  		return errors.New("pdfcpu: Merge: Please provide w")
   123  	}
   124  
   125  	if conf == nil {
   126  		conf = model.NewDefaultConfiguration()
   127  	}
   128  	conf.Cmd = model.MERGECREATE
   129  	conf.ValidationMode = model.ValidationRelaxed
   130  
   131  	if destFile != "" {
   132  		conf.Cmd = model.MERGEAPPEND
   133  	} else {
   134  		destFile = inFiles[0]
   135  		inFiles = inFiles[1:]
   136  	}
   137  
   138  	if conf.CreateBookmarks && log.CLIEnabled() {
   139  		log.CLI.Println("creating bookmarks...")
   140  	}
   141  
   142  	f, err := os.Open(destFile)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	defer f.Close()
   147  
   148  	if conf.Cmd == model.MERGECREATE {
   149  		if log.CLIEnabled() {
   150  			log.CLI.Println(destFile)
   151  		}
   152  	}
   153  
   154  	ctxDest, err := prepDestContext(destFile, f, conf)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	for _, fName := range inFiles {
   160  		if err := appendFile(fName, ctxDest, dividerPage); err != nil {
   161  			return err
   162  		}
   163  	}
   164  
   165  	if conf.OptimizeBeforeWriting {
   166  		if err := OptimizeContext(ctxDest); err != nil {
   167  			return err
   168  		}
   169  	}
   170  
   171  	return WriteContext(ctxDest, w)
   172  }
   173  
   174  // MergeCreateFile merges inFiles and writes the result to outFile.
   175  func MergeCreateFile(inFiles []string, outFile string, dividerPage bool, conf *model.Configuration) (err error) {
   176  	f, err := os.Create(outFile)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	defer func() {
   182  		if err != nil {
   183  			if err1 := f.Close(); err1 != nil {
   184  				return
   185  			}
   186  			os.Remove(outFile)
   187  			return
   188  		}
   189  		if err = f.Close(); err != nil {
   190  			return
   191  		}
   192  	}()
   193  
   194  	logWritingTo(outFile)
   195  	return Merge("", inFiles, f, conf, dividerPage)
   196  }
   197  
   198  // MergeAppendFile appends inFiles to outFile.
   199  func MergeAppendFile(inFiles []string, outFile string, dividerPage bool, conf *model.Configuration) (err error) {
   200  	tmpFile := outFile
   201  	overWrite := false
   202  	destFile := ""
   203  
   204  	if fileExists(outFile) {
   205  		overWrite = true
   206  		destFile = outFile
   207  		tmpFile += ".tmp"
   208  		if log.CLIEnabled() {
   209  			log.CLI.Printf("appending to %s...\n", outFile)
   210  		}
   211  	} else {
   212  		logWritingTo(outFile)
   213  	}
   214  
   215  	f, err := os.Create(tmpFile)
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	defer func() {
   221  		if err != nil {
   222  			if err1 := f.Close(); err1 != nil {
   223  				return
   224  			}
   225  			os.Remove(tmpFile)
   226  			return
   227  		}
   228  		if err = f.Close(); err != nil {
   229  			return
   230  		}
   231  		if overWrite {
   232  			err = os.Rename(tmpFile, outFile)
   233  		}
   234  	}()
   235  
   236  	err = Merge(destFile, inFiles, f, conf, dividerPage)
   237  	return err
   238  }
   239  
   240  // MergeCreateZip zips rs1 and rs2 into w.
   241  func MergeCreateZip(rs1, rs2 io.ReadSeeker, w io.Writer, conf *model.Configuration) error {
   242  	if rs1 == nil {
   243  		return errors.New("pdfcpu: MergeCreateZip: missing rs1")
   244  	}
   245  	if rs2 == nil {
   246  		return errors.New("pdfcpu: MergeCreateZip: missing rs2")
   247  	}
   248  	if w == nil {
   249  		return errors.New("pdfcpu: MergeCreateZip: missing w")
   250  	}
   251  
   252  	if conf == nil {
   253  		conf = model.NewDefaultConfiguration()
   254  	}
   255  	conf.Cmd = model.MERGECREATEZIP
   256  	conf.ValidationMode = model.ValidationRelaxed
   257  
   258  	ctxDest, err := ReadAndValidate(rs1, conf)
   259  	if err != nil {
   260  		return err
   261  	}
   262  	if ctxDest.XRefTable.Version() == model.V20 {
   263  		return pdfcpu.ErrUnsupportedVersion
   264  	}
   265  	ctxDest.EnsureVersionForWriting()
   266  
   267  	if _, err = pdfcpu.RemoveBookmarks(ctxDest); err != nil {
   268  		return err
   269  	}
   270  
   271  	ctxSrc, err := ReadAndValidate(rs2, conf)
   272  	if err != nil {
   273  		return err
   274  	}
   275  	if ctxSrc.XRefTable.Version() == model.V20 {
   276  		return pdfcpu.ErrUnsupportedVersion
   277  	}
   278  
   279  	if err := pdfcpu.MergeXRefTables("", ctxSrc, ctxDest, true, false); err != nil {
   280  		return err
   281  	}
   282  
   283  	if conf.OptimizeBeforeWriting {
   284  		if err := OptimizeContext(ctxDest); err != nil {
   285  			return err
   286  		}
   287  	}
   288  
   289  	return WriteContext(ctxDest, w)
   290  }
   291  
   292  // MergeCreateZipFile zips inFile1 and inFile2 into outFile.
   293  func MergeCreateZipFile(inFile1, inFile2, outFile string, conf *model.Configuration) (err error) {
   294  	f1, err := os.Open(inFile1)
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	f2, err := os.Open(inFile2)
   300  	if err != nil {
   301  		return err
   302  	}
   303  
   304  	f, err := os.Create(outFile)
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	defer func() {
   310  		cerr := f.Close()
   311  		if err == nil {
   312  			err = cerr
   313  		}
   314  	}()
   315  
   316  	logWritingTo(outFile)
   317  
   318  	err = MergeCreateZip(f1, f2, f, conf)
   319  	return err
   320  }