diff --git a/fillpdf.go b/fillpdf.go index 1254d1a..b8b2252 100644 --- a/fillpdf.go +++ b/fillpdf.go @@ -19,15 +19,14 @@ package fillpdf import ( - "bufio" + "bytes" + "errors" "fmt" - "io/ioutil" - "log" - "os" "os/exec" "path/filepath" "github.com/gdamore/encoding" + "google.golang.org/protobuf/types/known/wrapperspb" ) var ( @@ -42,132 +41,100 @@ type Form map[string]interface{} // Options represents the options to alter the PDF filling process type Options struct { - // Overwrite will overwrite any pre existing filled PDF - Overwrite bool // Flatten will flatten the document making the form fields no longer editable - Flatten bool + Flatten *wrapperspb.BoolValue + // Remove metadata + RemoveMetadata *wrapperspb.BoolValue +} + +func (o *Options) Override(opt Options) { + if opt.Flatten != nil { + o.Flatten = opt.Flatten + } + if opt.RemoveMetadata != nil { + o.RemoveMetadata = opt.RemoveMetadata + } } func defaultOptions() Options { return Options{ - Overwrite: true, - Flatten: true, + Flatten: wrapperspb.Bool(true), + RemoveMetadata: wrapperspb.Bool(false), } } // Fill a PDF form with the specified form values and create a final filled PDF file. // The options parameter alters few aspects of the generation. -func Fill(form Form, formPDFFile, destPDFFile string, options ...Options) (err error) { +func Fill(form Form, formPDFFile string, options ...Options) (out []byte, err error) { // If the user provided the options we overwrite the defaults with the given struct. opts := defaultOptions() - if len(options) > 0 { - opts = options[0] + for _, opt := range options { + opts.Override(opt) } // Get the absolute paths. formPDFFile, err = filepath.Abs(formPDFFile) if err != nil { - return fmt.Errorf("failed to create the absolute path: %v", err) - } - destPDFFile, err = filepath.Abs(destPDFFile) - if err != nil { - return fmt.Errorf("failed to create the absolute path: %v", err) + return nil, fmt.Errorf("failed to create the absolute path: %v", err) } // Check if the form file exists. e, err := exists(formPDFFile) if err != nil { - return fmt.Errorf("failed to check if form PDF file exists: %v", err) + return nil, fmt.Errorf("failed to check if form PDF file exists: %v", err) } else if !e { - return fmt.Errorf("form PDF file does not exists: '%s'", formPDFFile) + return nil, fmt.Errorf("form PDF file does not exists: '%s'", formPDFFile) } // Check if the pdftk utility exists. _, err = exec.LookPath("pdftk") if err != nil { - return fmt.Errorf("pdftk utility is not installed!") + return nil, errors.New("pdftk utility is not installed!") } - // Create a temporary directory. - tmpDir, err := ioutil.TempDir("", "fillpdf-") - if err != nil { - return fmt.Errorf("failed to create temporary directory: %v", err) - } - - // Remove the temporary directory on defer again. - defer func() { - errD := os.RemoveAll(tmpDir) - // Log the error only. - if errD != nil { - log.Printf("fillpdf: failed to remove temporary directory '%s' again: %v", tmpDir, errD) - } - }() - - // Create the temporary output file path. - outputFile := filepath.Clean(tmpDir + "/output.pdf") - - // Create the fdf data file. - fdfFile := filepath.Clean(tmpDir + "/data.fdf") - err = createFdfFile(form, fdfFile) + // Create the fdf content. + fdfContent, err := createFdfFile(form) if err != nil { - return fmt.Errorf("failed to create fdf form data file: %v", err) + return nil, fmt.Errorf("failed to create fdf form data file: %v", err) } // Create the pdftk command line arguments. args := []string{ formPDFFile, - "fill_form", fdfFile, - "output", outputFile, + "fill_form", "-", + "output", "-", } // If the user specified to flatten the output PDF we append the related parameter. - if opts.Flatten { + if opts.Flatten.GetValue() { args = append(args, "flatten") } // Run the pdftk utility. - err = runCommandInPath(tmpDir, "pdftk", args...) + output, err := runCommand("pdftk", bytes.NewBuffer([]byte(fdfContent)), args...) if err != nil { - return fmt.Errorf("pdftk error: %v", err) + return nil, fmt.Errorf("pdftk error: %v", err) } - // Check if the destination file exists. - e, err = exists(destPDFFile) - if err != nil { - return fmt.Errorf("failed to check if destination PDF file exists: %v", err) - } else if e { - if !opts.Overwrite { - return fmt.Errorf("destination PDF file already exists: '%s'", destPDFFile) + if opts.RemoveMetadata.GetValue() { + // Check if the exiftool utility exists. + _, err = exec.LookPath("exiftool") + if err != nil { + return nil, errors.New("exiftool utility is not installed!") } - - err = os.Remove(destPDFFile) + // exiftool -all:all= - -o - + output, err = runCommand("exiftool", output, "-all:all=", "-", "-o", "-") if err != nil { - return fmt.Errorf("failed to remove destination PDF file: %v", err) + return nil, fmt.Errorf("exiftool error: %v", err) } } - // On success, copy the output file to the final destination. - err = copyFile(outputFile, destPDFFile) - if err != nil { - return fmt.Errorf("failed to copy created output PDF to final destination: %v", err) - } - - return nil + return output.Bytes(), nil } -func createFdfFile(form Form, path string) error { - // Create the file. - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - - // Create a new writer. - w := bufio.NewWriter(file) - +func createFdfFile(form Form) (output string, err error) { // Write the fdf header. - fmt.Fprintln(w, fdfHeader) + output = fdfHeader // Write the form data. var valueStr string @@ -175,16 +142,14 @@ func createFdfFile(form Form, path string) error { // Convert to Latin-1. valueStr, err = latin1Encoder.String(fmt.Sprintf("%v", value)) if err != nil { - return fmt.Errorf("failed to convert string to Latin-1") + return "", fmt.Errorf("failed to convert string to Latin-1") } - fmt.Fprintf(w, "<< /T (%s) /V (%s)>>\n", key, valueStr) + output += fmt.Sprintf("<< /T (%s) /V (%s)>>\n", key, valueStr) } // Write the fdf footer. - fmt.Fprintln(w, fdfFooter) - - // Flush everything. - return w.Flush() + output += fdfFooter + return output, nil } const fdfHeader = `%FDF-1.2 diff --git a/go.mod b/go.mod index 61796dd..381c91c 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/desertbit/fillpdf go 1.16 -require github.com/gdamore/encoding v1.0.0 // indirect +require ( + github.com/gdamore/encoding v1.0.0 + google.golang.org/protobuf v1.30.0 +) diff --git a/go.sum b/go.sum index a0f78ee..ba197c8 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,12 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/sample/main.go b/sample/main.go index 0e2d258..e6c23ff 100644 --- a/sample/main.go +++ b/sample/main.go @@ -2,8 +2,10 @@ package main import ( "log" + "os" "github.com/desertbit/fillpdf" + "google.golang.org/protobuf/types/known/wrapperspb" ) func main() { @@ -14,8 +16,13 @@ func main() { } // Fill the form PDF with our values. - err := fillpdf.Fill(form, "form.pdf", "filled.pdf") + out, err := fillpdf.Fill(form, "form.pdf", fillpdf.Options{ + RemoveMetadata: wrapperspb.Bool(true), + }) if err != nil { log.Fatal(err) } + + os.WriteFile("filled.pdf", out, 0600) + } diff --git a/utils.go b/utils.go index 7ca645c..008ed89 100644 --- a/utils.go +++ b/utils.go @@ -39,48 +39,21 @@ func exists(path string) (bool, error) { return false, err } -// copyFile copies the contents of the file named src to the file named -// by dst. The file will be created if it does not already exist. If the -// destination file exists, all it's contents will be replaced by the contents -// of the source file. -func copyFile(src, dst string) (err error) { - in, err := os.Open(src) - if err != nil { - return - } - defer in.Close() - out, err := os.Create(dst) - if err != nil { - return - } - defer func() { - cerr := out.Close() - if err == nil { - err = cerr - } - }() - if _, err = io.Copy(out, in); err != nil { - return - } - err = out.Sync() - return -} - -// runCommandInPath runs a command and waits for it to exit. -// The working directory is also set. +// runCommand runs a command and waits for it to exit. // The stderr error message is returned on error. -func runCommandInPath(dir, name string, args ...string) error { +func runCommand(name string, stdin io.Reader, args ...string) (*bytes.Buffer, error) { // Create the command. - var stderr bytes.Buffer + var stdout, stderr bytes.Buffer cmd := exec.Command(name, args...) + cmd.Stdin = stdin + cmd.Stdout = &stdout cmd.Stderr = &stderr - cmd.Dir = dir // Start the command and wait for it to exit. err := cmd.Run() if err != nil { - return fmt.Errorf(strings.TrimSpace(stderr.String())) + return nil, fmt.Errorf(strings.TrimSpace(stderr.String())) } - return nil + return &stdout, nil }