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 }