package main import ( "errors" "fmt" "io" "os" "path/filepath" "runtime" "strings" "time" "unsafe" "C" "github.com/pdfcpu/pdfcpu/pkg/api" "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" ) type TDLLVersionType int type TDLLCmdId int const ( E_DLLFULLNAME TDLLVersionType = iota E_DLLMODULNAME E_DLLGROUPNAME E_DLLFILEVERSION E_NEXUSVERSION E_COMPILERVERSION ) const ( E_DLLCmdExtract TDLLCmdId = iota E_DLLCmdAppend E_DLLCmdDebugEnable E_DLLCmdDebugDisable ) const ( ModuleName = "ZUGFERD" GroupName = "XRECHNUNG" NexusVersion = "" ) var debugEnabled = false var debugFile = "dll/debug.log" func extractFromPdf(inputFilePath string, outputFilePath string) error { if inputFilePath == "" { return errors.New("no input file specified") } if outputFilePath == "" { outputFilePath = strings.TrimSuffix(inputFilePath, filepath.Ext(inputFilePath)) + ".xml" } inputFile, err := os.Open(inputFilePath) if err != nil { return err } defer inputFile.Close() var ( attachments []model.Attachment rawData []byte ctx *model.Context attachment *model.Attachment ) ctx, err = api.ReadContextFile(inputFilePath) if err != nil { return err } attachments, err = api.Attachments(inputFile, model.NewDefaultConfiguration()) if err != nil { return err } attachment, err = ctx.ExtractAttachment(attachments[0]) if err != nil { return err } rawData, err = io.ReadAll(attachment) if err != nil { return err } var outputFile *os.File outputFile, err = os.Create(outputFilePath) if err != nil { return err } defer outputFile.Close() _, err = outputFile.Write(rawData) if err != nil { return err } return nil } func appendToPdf(inputPdfFilePath string, inputXmlFilePath string, outputFilePath string) error { if inputPdfFilePath == "" { return errors.New("no input pdf file specified") } if inputXmlFilePath == "" { inputXmlFilePath = strings.TrimSuffix(inputPdfFilePath, filepath.Ext(inputPdfFilePath)) + ".xml" } if outputFilePath == "" { outputFilePath = strings.TrimSuffix( inputPdfFilePath, filepath.Ext(inputPdfFilePath), ) + "_xml.pdf" } err := api.AddAttachmentsFile( inputPdfFilePath, outputFilePath, []string{inputXmlFilePath}, false, model.NewDefaultConfiguration(), ) if err != nil { return err } return nil } type DebugLogger struct { file *os.File } func getDebugLogger() *DebugLogger { result := &DebugLogger{} runtime.SetFinalizer( result, func(logger *DebugLogger) { logger.Close() }, ) if debugEnabled { var err error _, err = os.Stat(debugFile) if err != nil { result.file, _ = os.Create(debugFile) } else { result.file, _ = os.OpenFile(debugFile, os.O_RDWR, 0644) result.file.Seek(0, io.SeekEnd) } } return result } func (l *DebugLogger) Log(msg ...any) { if l.file == nil { return } l.file.WriteString(fmt.Sprintln(append([]any{time.Now().Format("2006-01-02 15:04:05")}, msg...)...)) } func (l *DebugLogger) Close() error { if l.file == nil { return nil } return l.file.Close() } //export DLLGETVERSION func DLLGETVERSION(versionTyp TDLLVersionType) *C.char { logger := getDebugLogger() defer logger.Close() defer logger.Log("Exit DLLGETVERSION") if debugEnabled { logger.Log("Enter DLLGETVERSION") } switch versionTyp { case E_DLLMODULNAME: return C.CString(ModuleName) case E_DLLGROUPNAME: return C.CString(GroupName) case E_DLLFILEVERSION: return C.CString("1.0.0") case E_NEXUSVERSION: return C.CString(NexusVersion) case E_COMPILERVERSION: return C.CString(runtime.Version()) default: return C.CString("Unknown type") } } //export DLLINTERFACE func DLLINTERFACE(cmdId TDLLCmdId, data unsafe.Pointer) bool { var err error logger := getDebugLogger() defer logger.Close() defer logger.Log("Exit DLLINTERFACE") if debugEnabled { logger.Log("Enter DLLINTERFACE") } switch cmdId { case E_DLLCmdDebugEnable: debugEnabled = true case E_DLLCmdDebugDisable: debugEnabled = false case E_DLLCmdExtract: args := strings.Split(C.GoString((*C.char)(data)), "|") if debugEnabled { logger.Log("Execute E_DLLCmdExtract: ", args[0]+", "+args[1]) } err = extractFromPdf(args[0], args[1]) if debugEnabled { logger.Log("Finished E_DLLCmdExtract") } if err != nil { logger.Log(err) return false } case E_DLLCmdAppend: args := strings.Split(C.GoString((*C.char)(data)), "|") if debugEnabled { logger.Log("Execute E_DLLCmdAppend: ", args[0]+", "+args[1]+", "+args[2]) } err = appendToPdf(args[0], args[1], args[2]) if debugEnabled { logger.Log("Finished E_DLLCmdAppend") } if err != nil { logger.Log(err) return false } default: return false } return false } //export DLLACTIVATE func DLLACTIVATE(activate bool) bool { logger := getDebugLogger() defer logger.Close() defer logger.Log("Exit DLLACTIVATE") if debugEnabled { logger.Log("Enter DLLACTIVATE") } if !activate { // Perform any necessary cleanup runtime.GC() // Force garbage collection runtime.UnlockOSThread() } return true } func main() { // Need a main function to make CGO compile package as C shared library DLLINTERFACE(E_DLLCmdDebugEnable, nil) DLLGETVERSION(E_DLLGROUPNAME) DLLACTIVATE(false) } // go build -o pdf.dll -buildmode=c-shared dll.go