github.com/pdfcpu/pdfcpu@v0.11.1/pkg/api/cut.go (about) 1 /* 2 Copyright 2023 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 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 27 "github.com/pdfcpu/pdfcpu/pkg/log" 28 "github.com/pdfcpu/pdfcpu/pkg/pdfcpu" 29 "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" 30 "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types" 31 "github.com/pkg/errors" 32 ) 33 34 func prepareForCut(rs io.ReadSeeker, selectedPages []string, conf *model.Configuration) (*model.Context, types.IntSet, error) { 35 ctx, err := ReadValidateAndOptimize(rs, conf) 36 if err != nil { 37 return nil, nil, err 38 } 39 40 pages, err := PagesForPageSelection(ctx.PageCount, selectedPages, true, true) 41 if err != nil { 42 return nil, nil, err 43 } 44 45 return ctx, pages, nil 46 } 47 48 // Poster applies cut for selected pages of rs and generates corresponding poster tiles in outDir. 49 func Poster(rs io.ReadSeeker, outDir, fileName string, selectedPages []string, cut *model.Cut, conf *model.Configuration) error { 50 if rs == nil { 51 return errors.New("pdfcpu: Poster: missing rs") 52 } 53 54 if cut.PageSize == "" && !cut.UserDim { 55 return errors.New("pdfcpu: poster - please supply either dimensions or form size ") 56 } 57 58 if cut.Scale < 1 { 59 return errors.Errorf("pdfcpu: invalid scale factor %.2f: i >= 1.0\n", cut.Scale) 60 } 61 62 if conf == nil { 63 conf = model.NewDefaultConfiguration() 64 } 65 conf.Cmd = model.POSTER 66 67 ctxSrc, pages, err := prepareForCut(rs, selectedPages, conf) 68 if err != nil { 69 return err 70 } 71 72 if len(pages) == 0 { 73 log.CLI.Println("aborted: nothing to cut!") 74 return nil 75 } 76 77 for pageNr, v := range pages { 78 if !v { 79 continue 80 } 81 ctxDest, err := pdfcpu.PosterPage(ctxSrc, pageNr, cut) 82 if err != nil { 83 return err 84 } 85 86 outFile := filepath.Join(outDir, fmt.Sprintf("%s_page_%d.pdf", fileName, pageNr)) 87 logWritingTo(outFile) 88 89 if conf.PostProcessValidate { 90 if err = ValidateContext(ctxDest); err != nil { 91 return err 92 } 93 } 94 95 if err := WriteContextFile(ctxDest, outFile); err != nil { 96 return err 97 } 98 } 99 100 return nil 101 } 102 103 // PosterFile applies cut for selected pages of inFile and generates corresponding poster tiles in outDir. 104 func PosterFile(inFile, outDir, outFile string, selectedPages []string, cut *model.Cut, conf *model.Configuration) error { 105 f, err := os.Open(inFile) 106 if err != nil { 107 return err 108 } 109 defer f.Close() 110 111 log.CLI.Printf("ndown %s into %s/ ...\n", inFile, outDir) 112 113 if outFile == "" { 114 outFile = strings.TrimSuffix(filepath.Base(inFile), ".pdf") 115 } 116 117 return Poster(f, outDir, outFile, selectedPages, cut, conf) 118 } 119 120 // NDown applies n & cutConf for selected pages of rs and writes results to outDir. 121 func NDown(rs io.ReadSeeker, outDir, fileName string, selectedPages []string, n int, cut *model.Cut, conf *model.Configuration) error { 122 if rs == nil { 123 return errors.New("pdfcpu NDown: Please provide rs") 124 } 125 126 if conf == nil { 127 conf = model.NewDefaultConfiguration() 128 } 129 conf.Cmd = model.NDOWN 130 131 ctxSrc, pages, err := prepareForCut(rs, selectedPages, conf) 132 if err != nil { 133 return err 134 } 135 136 if len(pages) == 0 { 137 if log.CLIEnabled() { 138 log.CLI.Println("aborted: nothing to cut!") 139 } 140 return nil 141 } 142 143 for pageNr, v := range pages { 144 if !v { 145 continue 146 } 147 ctxDest, err := pdfcpu.NDownPage(ctxSrc, pageNr, n, cut) 148 if err != nil { 149 return err 150 } 151 152 if conf.PostProcessValidate { 153 if err = ValidateContext(ctxDest); err != nil { 154 return err 155 } 156 } 157 158 outFile := filepath.Join(outDir, fmt.Sprintf("%s_page_%d.pdf", fileName, pageNr)) 159 if log.CLIEnabled() { 160 log.CLI.Printf("writing %s\n", outFile) 161 } 162 if err := WriteContextFile(ctxDest, outFile); err != nil { 163 return err 164 } 165 } 166 167 return nil 168 } 169 170 // NDownFile applies n & cutConf for selected pages of inFile and writes results to outDir. 171 func NDownFile(inFile, outDir, outFile string, selectedPages []string, n int, cut *model.Cut, conf *model.Configuration) error { 172 f, err := os.Open(inFile) 173 if err != nil { 174 return err 175 } 176 defer f.Close() 177 178 if log.CLIEnabled() { 179 log.CLI.Printf("ndown %s into %s/ ...\n", inFile, outDir) 180 } 181 182 if outFile == "" { 183 outFile = strings.TrimSuffix(filepath.Base(inFile), ".pdf") 184 } 185 186 return NDown(f, outDir, outFile, selectedPages, n, cut, conf) 187 } 188 189 func validateCut(cut *model.Cut) error { 190 sort.Float64s(cut.Hor) 191 192 for _, f := range cut.Hor { 193 if f < 0 || f >= 1 { 194 return errors.New("pdfcpu: Invalid cut points. Please consult pdfcpu help cut") 195 } 196 } 197 if len(cut.Hor) == 0 || cut.Hor[0] > 0 { 198 cut.Hor = append([]float64{0}, cut.Hor...) 199 } 200 201 sort.Float64s(cut.Vert) 202 for _, f := range cut.Vert { 203 if f < 0 || f >= 1 { 204 return errors.New("pdfcpu: Invalid cut points. Please consult pdfcpu help cut") 205 } 206 } 207 if len(cut.Vert) == 0 || cut.Vert[0] > 0 { 208 cut.Vert = append([]float64{0}, cut.Vert...) 209 } 210 211 return nil 212 } 213 214 // Cut applies cutConf for selected pages of rs and writes results to outDir. 215 func Cut(rs io.ReadSeeker, outDir, fileName string, selectedPages []string, cut *model.Cut, conf *model.Configuration) error { 216 if rs == nil { 217 return errors.New("pdfcpu: Cut: missing rs") 218 } 219 220 if len(cut.Hor) == 0 && len(cut.Vert) == 0 { 221 return errors.New("pdfcpu: Invalid cut configuration string: missing hor/ver cutpoints. Please consult pdfcpu help cut") 222 } 223 224 if err := validateCut(cut); err != nil { 225 return err 226 } 227 228 if conf == nil { 229 conf = model.NewDefaultConfiguration() 230 } 231 conf.Cmd = model.CUT 232 233 ctxSrc, pages, err := prepareForCut(rs, selectedPages, conf) 234 if err != nil { 235 return err 236 } 237 238 if len(pages) == 0 { 239 log.CLI.Println("aborted: nothing to cut!") 240 return nil 241 } 242 243 for pageNr, v := range pages { 244 if !v { 245 continue 246 } 247 ctxDest, err := pdfcpu.CutPage(ctxSrc, pageNr, cut) 248 if err != nil { 249 return err 250 } 251 252 if conf.PostProcessValidate { 253 if err = ValidateContext(ctxDest); err != nil { 254 return err 255 } 256 } 257 258 outFile := filepath.Join(outDir, fmt.Sprintf("%s_page_%d.pdf", fileName, pageNr)) 259 logWritingTo(outFile) 260 261 if err := WriteContextFile(ctxDest, outFile); err != nil { 262 return err 263 } 264 } 265 266 return nil 267 } 268 269 // CutFile applies cutConf for selected pages of inFile and writes results to outDir. 270 func CutFile(inFile, outDir, outFile string, selectedPages []string, cut *model.Cut, conf *model.Configuration) error { 271 f, err := os.Open(inFile) 272 if err != nil { 273 return err 274 } 275 defer f.Close() 276 277 if log.CLIEnabled() { 278 log.CLI.Printf("cutting %s into %s/ ...\n", inFile, outDir) 279 } 280 281 if outFile == "" { 282 outFile = strings.TrimSuffix(filepath.Base(inFile), ".pdf") 283 } 284 285 return Cut(f, outDir, outFile, selectedPages, cut, conf) 286 }