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

     1  /*
     2  	Copyright 2018 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 lets you integrate pdfcpu's operations into your Go backend.
    18  //
    19  // There are two api layers supporting all pdfcpu operations:
    20  //  1. The file based layer (used by pdfcpu's cli)
    21  //  2. The io.ReadSeeker/io.Writer based layer for backend integration.
    22  //
    23  // For any pdfcpu command there are two functions.
    24  //
    25  // The file based function always calls the io.ReadSeeker/io.Writer based function:
    26  //
    27  //	func CommandFile(inFile, outFile string, conf *pdf.Configuration) error
    28  //	func Command(rs io.ReadSeeker, w io.Writer, conf *pdf.Configuration) error
    29  //
    30  // eg. for optimization:
    31  //
    32  //	func OptimizeFile(inFile, outFile string, conf *pdf.Configuration) error
    33  //	func Optimize(rs io.ReadSeeker, w io.Writer, conf *pdf.Configuration) error
    34  package api
    35  
    36  import (
    37  	"bufio"
    38  	"io"
    39  	"os"
    40  	"sync"
    41  
    42  	"github.com/pdfcpu/pdfcpu/pkg/log"
    43  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu"
    44  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
    45  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/validate"
    46  	"github.com/pkg/errors"
    47  )
    48  
    49  func logDisclaimerPDF20() {
    50  	disclaimer := `
    51  ***************************** Disclaimer ****************************
    52  * PDF 2.0 features are supported on a need basis.                   *
    53  * (See ISO 32000:2 6.3.2 Conformance of PDF processors)             *
    54  * At the moment pdfcpu ships with basic PDF 2.0 support.            *
    55  * Please let us know which feature you would like to see supported, *
    56  * provide a sample PDF file and create an issue:                    *
    57  * https://github.com/pdfcpu/pdfcpu/issues/new/choose                *
    58  * Thank you for using pdfcpu <3                                     *
    59  *********************************************************************`
    60  
    61  	if log.ValidateEnabled() {
    62  		log.Validate.Println(disclaimer)
    63  	}
    64  	if log.CLIEnabled() {
    65  		log.CLI.Println(disclaimer)
    66  	}
    67  }
    68  
    69  // ReadContext uses an io.ReadSeeker to build an internal structure holding its cross reference table aka the Context.
    70  func ReadContext(rs io.ReadSeeker, conf *model.Configuration) (*model.Context, error) {
    71  	if rs == nil {
    72  		return nil, errors.New("pdfcpu: ReadContext: missing rs")
    73  	}
    74  	return pdfcpu.Read(rs, conf)
    75  }
    76  
    77  // ReadContextFile returns inFile's validated context.
    78  func ReadContextFile(inFile string) (*model.Context, error) {
    79  	f, err := os.Open(inFile)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	defer f.Close()
    84  
    85  	ctx, err := ReadContext(f, model.NewDefaultConfiguration())
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	if ctx.Conf.Version != model.VersionStr {
    91  		model.CheckConfigVersion(ctx.Conf.Version)
    92  	}
    93  
    94  	if ctx.XRefTable.Version() == model.V20 {
    95  		logDisclaimerPDF20()
    96  	}
    97  
    98  	if err = validate.XRefTable(ctx); err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return ctx, err
   103  }
   104  
   105  // ValidateContext validates ctx.
   106  func ValidateContext(ctx *model.Context) error {
   107  	if ctx.XRefTable.Version() == model.V20 {
   108  		logDisclaimerPDF20()
   109  	}
   110  	return validate.XRefTable(ctx)
   111  }
   112  
   113  // OptimizeContext optimizes ctx.
   114  func OptimizeContext(ctx *model.Context) error {
   115  	if log.CLIEnabled() {
   116  		log.CLI.Println("optimizing...")
   117  	}
   118  	return pdfcpu.OptimizeXRefTable(ctx)
   119  }
   120  
   121  // WriteContext writes ctx to w.
   122  func WriteContext(ctx *model.Context, w io.Writer) error {
   123  	if f, ok := w.(*os.File); ok {
   124  		// In order to retrieve the written file size.
   125  		ctx.Write.Fp = f
   126  	}
   127  	ctx.Write.Writer = bufio.NewWriter(w)
   128  	defer ctx.Write.Flush()
   129  	return pdfcpu.WriteContext(ctx)
   130  }
   131  
   132  // WriteIncrement writes a PDF increment for ctx to w.
   133  func WriteIncrement(ctx *model.Context, w io.Writer) error {
   134  	ctx.Write.Writer = bufio.NewWriter(w)
   135  	defer ctx.Write.Flush()
   136  	return pdfcpu.WriteIncrement(ctx)
   137  }
   138  
   139  // WriteContextFile writes ctx to outFile.
   140  func WriteContextFile(ctx *model.Context, outFile string) error {
   141  	f, err := os.Create(outFile)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	defer f.Close()
   146  	return WriteContext(ctx, f)
   147  }
   148  
   149  // ReadAndValidate returns a model.Context of rs ready for processing.
   150  func ReadAndValidate(rs io.ReadSeeker, conf *model.Configuration) (ctx *model.Context, err error) {
   151  	if ctx, err = ReadContext(rs, conf); err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	if err := ValidateContext(ctx); err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	return ctx, nil
   160  }
   161  
   162  func cmdAssumingOptimization(cmd model.CommandMode) bool {
   163  	return cmd == model.OPTIMIZE ||
   164  		cmd == model.FILLFORMFIELDS ||
   165  		cmd == model.RESETFORMFIELDS ||
   166  		cmd == model.LISTIMAGES ||
   167  		cmd == model.UPDATEIMAGES ||
   168  		cmd == model.EXTRACTIMAGES ||
   169  		cmd == model.EXTRACTFONTS
   170  }
   171  
   172  // ReadValidateAndOptimize returns an optimized model.Context of rs ready for processing a specific command.
   173  // conf.Cmd is expected to be configured properly.
   174  func ReadValidateAndOptimize(rs io.ReadSeeker, conf *model.Configuration) (ctx *model.Context, err error) {
   175  	if conf == nil {
   176  		return nil, errors.New("pdfcpu: ReadValidateAndOptimize: missing conf")
   177  	}
   178  
   179  	ctx, err = ReadAndValidate(rs, conf)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	// With the exception of commands utilizing structs provided the Optimize step
   185  	// command optimization of the cross reference table is optional but usually recommended.
   186  	// For large or complex files it may make sense to skip optimization and set conf.Optimize = false.
   187  	if cmdAssumingOptimization(conf.Cmd) || conf.Optimize {
   188  		if err = OptimizeContext(ctx); err != nil {
   189  			return nil, err
   190  		}
   191  	}
   192  
   193  	// TODO move to form related commands.
   194  	if err := pdfcpu.CacheFormFonts(ctx); err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	return ctx, nil
   199  }
   200  
   201  func logWritingTo(s string) {
   202  	if log.CLIEnabled() {
   203  		log.CLI.Printf("writing %s...\n", s)
   204  	}
   205  }
   206  
   207  func Write(ctx *model.Context, w io.Writer, conf *model.Configuration) error {
   208  	if log.StatsEnabled() {
   209  		log.Stats.Printf("XRefTable:\n%s\n", ctx)
   210  	}
   211  
   212  	// Note side effects of validation before writing!
   213  	// if conf.PostProcessValidate {
   214  	// 	if err := ValidateContext(ctx); err != nil {
   215  	// 		return err
   216  	// 	}
   217  	// }
   218  
   219  	return WriteContext(ctx, w)
   220  }
   221  
   222  func WriteIncr(ctx *model.Context, rws io.ReadWriteSeeker, conf *model.Configuration) error {
   223  	if log.StatsEnabled() {
   224  		log.Stats.Printf("XRefTable:\n%s\n", ctx)
   225  	}
   226  
   227  	if conf.PostProcessValidate {
   228  		if err := ValidateContext(ctx); err != nil {
   229  			return err
   230  		}
   231  	}
   232  
   233  	if _, err := rws.Seek(0, io.SeekEnd); err != nil {
   234  		return err
   235  	}
   236  
   237  	return WriteIncrement(ctx, rws)
   238  }
   239  
   240  // EnsureDefaultConfigAt switches to the pdfcpu config dir located at path.
   241  // If path/pdfcpu is not existent, it will be created including config.yml
   242  func EnsureDefaultConfigAt(path string) error {
   243  	// Call if you have specific requirements regarding the location of the pdfcpu config dir.
   244  	return model.EnsureDefaultConfigAt(path, false)
   245  }
   246  
   247  var (
   248  	// mutexDisableConfigDir protects DisableConfigDir from concurrent access.
   249  	// NOTE Not a guard for model.ConfigPath!
   250  	mutexDisableConfigDir sync.Mutex
   251  )
   252  
   253  // DisableConfigDir disables the configuration directory.
   254  // Any needed default configuration will be loaded from configuration.go
   255  // Since the config dir also contains the user font dir, this also limits font usage to the default core font set
   256  // No user fonts will be available.
   257  func DisableConfigDir() {
   258  	mutexDisableConfigDir.Lock()
   259  	defer mutexDisableConfigDir.Unlock()
   260  	// Call if you don't want to use a specific configuration
   261  	// and also do not need to use user fonts.
   262  	model.ConfigPath = "disable"
   263  }
   264  
   265  // LoadConfiguration locates and loads the default configuration
   266  // and also loads installed user fonts.
   267  func LoadConfiguration() *model.Configuration {
   268  	// Call if you don't have a specific config dir location
   269  	// and need to use user fonts for stamping or watermarking.
   270  	return model.NewDefaultConfiguration()
   271  }