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  }