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 }