diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f9d0ad921..9daf1a68e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,6 +394,12 @@ endif() # Determine where to install our guile modules libraries. find_guile_dirs() +pkg_check_modules (QRENCODE IMPORTED_TARGET libqrencode>=3.4.4) + +if (QRENCODE_FOUND) + set (HAVE_QRENCODE TRUE) +endif () + # ############################################################ if (WITH_AQBANKING) pkg_check_modules (GWENHYWFAR REQUIRED gwenhywfar>=5.6.0) diff --git a/bindings/guile/options.scm b/bindings/guile/options.scm index 10bc85d034..c6ea772212 100644 --- a/bindings/guile/options.scm +++ b/bindings/guile/options.scm @@ -365,6 +365,8 @@ (export gnc:*company-url*) (export gnc:*company-email*) (export gnc:*company-contact*) +(export gnc:*company-iban*) +(export gnc:*company-bic*) (export gnc:*fancy-date-label*) (export gnc:*fancy-date-format*) (export gnc:*tax-label*) @@ -394,6 +396,8 @@ (define gnc:*company-url* (N_ "Company Website URL")) (define gnc:*company-email* (N_ "Company Email Address")) (define gnc:*company-contact* (N_ "Company Contact Person")) +(define gnc:*company-iban* (N_ "Company IBAN")) +(define gnc:*company-bic* (N_ "Company BIC")) (define gnc:*fancy-date-label* (N_ "Fancy Date Format")) (define gnc:*fancy-date-format* (N_ "custom")) (define gnc:*tax-label* (N_ "Tax")) diff --git a/gnucash/report/CMakeLists.txt b/gnucash/report/CMakeLists.txt index c70e13c561..20c7467063 100644 --- a/gnucash/report/CMakeLists.txt +++ b/gnucash/report/CMakeLists.txt @@ -17,9 +17,21 @@ set (report_SOURCES gnc-report.cpp ) +set (qrencode_HEADERS gnc-qrencode.h) + +if (HAVE_QRENCODE) + set (qrencode_SOURCES gnc-qrencode.c) + set (qrencode_LIBRARY PkgConfig::QRENCODE) +else () + set (qrencode_SOURCES gnc-qrencode-stub.c) + set (qrencode_LIBRARY) +endif () + add_library (gnc-report ${report_SOURCES} ${report_HEADERS} + ${qrencode_SOURCES} + ${qrencode_HEADERS} ${SWIG_REPORT_C} ) @@ -32,6 +44,7 @@ target_link_libraries(gnc-report gnucash-guile gnc-expressions-guile PkgConfig::GTK3 + ${qrencode_LIBRARY} ${GUILE_LDFLAGS}) target_include_directories (gnc-report @@ -58,6 +71,7 @@ install(FILES ${report_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gnucash) set (report_SCHEME_1 commodity-utilities.scm + gnc-qrencode.scm html-acct-table.scm html-chart.scm html-document.scm @@ -126,6 +140,7 @@ add_custom_target(scm-report ALL DEPENDS scm-report-2 scm-report-eguile) set_local_dist(report_DIST_local CMakeLists.txt report.i ${report_HEADERS} ${report_SOURCES} + gnc-qrencode.c gnc-qrencode-stub.c ${qrencode_HEADERS} ${report_SCHEME} ${report_SCHEME_1} ${report_SCHEME_2} ${report_eguile_parts_SCHEME} ${report_eguile_SCHEME}) diff --git a/gnucash/report/gnc-qrencode-stub.c b/gnucash/report/gnc-qrencode-stub.c new file mode 100644 index 0000000000..d1bef5b854 --- /dev/null +++ b/gnucash/report/gnc-qrencode-stub.c @@ -0,0 +1,33 @@ +/********************************************************************\ + * gnc-qrencode-stub.c -- stub if libqrencode is not available * + * Copyright (C) 2021 Christopher Lam * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +#include "gnc-qrencode.h" + +SCM gnc_qrcode_encodestring (const char *str) +{ + return SCM_EOL; +} + +SCM gnc_qrcode_available () +{ + return SCM_BOOL_F; +} diff --git a/gnucash/report/gnc-qrencode.c b/gnucash/report/gnc-qrencode.c new file mode 100644 index 0000000000..3d3ed3a8ae --- /dev/null +++ b/gnucash/report/gnc-qrencode.c @@ -0,0 +1,60 @@ +/********************************************************************\ + * gnc-qrencode.c -- link to libqrencode * + * Copyright (C) 2021 Christopher Lam * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +#include +#include +#include + +#include "gnc-qrencode.h" +#include "gnc-engine.h" + +/* This static indicates the debugging module that this .o belongs to. */ +static QofLogModule log_module = "gnc.qrencode"; + +SCM gnc_qrcode_encodestring (const char *str) +{ + QRcode *qr = QRcode_encodeString (str, 0, QR_ECLEVEL_M, QR_MODE_8, FALSE); + SCM output = SCM_EOL; + + if (!qr) + { + PERR ("QRcode_encodeString failed: %s", strerror (errno)); + return output; + } + + for (unsigned int i = (qr->width * qr->width); i != 0; ) + { + i--; + output = scm_cons ((qr->data[i] & 1) ? SCM_BOOL_T : SCM_BOOL_F, output); + } + + output = scm_cons (scm_from_uint (qr->width), output); + output = scm_cons (scm_from_uint (qr->version), output); + + QRcode_free (qr); + return output; +} + +SCM gnc_qrcode_available () +{ + return SCM_BOOL_T; +} diff --git a/gnucash/report/gnc-qrencode.h b/gnucash/report/gnc-qrencode.h new file mode 100644 index 0000000000..73410a681c --- /dev/null +++ b/gnucash/report/gnc-qrencode.h @@ -0,0 +1,40 @@ +/********************************************************************\ + * gnc-qrencode.h -- link to libqrencode or stub * + * Copyright (C) Christopher Lam * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +#ifndef GNC_QRENCODE_H +#define GNC_QRENCODE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +SCM gnc_qrcode_encodestring (const char *str); +SCM gnc_qrcode_available (void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gnucash/report/gnc-qrencode.scm b/gnucash/report/gnc-qrencode.scm new file mode 100644 index 0000000000..ca2006479b --- /dev/null +++ b/gnucash/report/gnc-qrencode.scm @@ -0,0 +1,89 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; gnc-qrencode.scm : link with gnc-qrencode.c +;; Copyright 2021 Christopher Lam +;; +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of +;; the License, or (at your option) any later version. +;; +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. +;; +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, contact: +;; +;; Free Software Foundation Voice: +1-617-542-5942 +;; 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 +;; Boston, MA 02110-1301, USA gnu@gnu.org +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define-module (gnucash report gnc-qrencode)) + +(eval-when (compile load eval expand) + (load-extension "libgnc-report" "scm_init_sw_report_module")) + +(use-modules (ice-9 match)) +(use-modules (sxml simple)) +(use-modules (gnucash core-utils)) +(use-modules (gnucash utilities)) +(use-modules (gnucash engine)) +(use-modules (gnucash app-utils)) +(use-modules (gnucash options)) +(use-modules (sw_report)) + +(export gnc:make-epc-qrcode) + +(define (data->svg data width size source) + (define (make-rect row col) + (format #f "M~a,~ah~av~ah~aZ " (* row size) (* col size) size size (- size))) + (let lp ((col 0) (row 0) (data data) (accum '())) + (match data + (() + (call-with-output-string + (lambda (port) + (sxml->xml + `(svg (@ (xmlns "http://www.w3.org/2000/svg") + (width ,(* width size)) + (height ,(* row size))) + (title ,source) (desc "QR code") + (path (@ (d ,(string-concatenate accum))))) + port)))) + ((datum . rest) + (let ((newcol (modulo (1+ col) width))) + (lp newcol (if (zero? newcol) (1+ row) row) rest + (if datum (cons (make-rect row col) accum) accum))))))) + +(define (string-max str maxlen) + (cond + ((<= (string-length str) maxlen) str) + (else (gnc:warn "QR: truncated " str " at " maxlen " characters") + (string-take str maxlen)))) + +(define (gnc:make-epc-qrcode invoice) + (define (quit msg) (gnc:warn "QR Error:" msg) (list "qr-code-error" msg)) + (let* ((book (gncInvoiceGetBook invoice)) + (currency (gncInvoiceGetCurrency invoice)) + (amount (abs (gncInvoiceGetTotal invoice))) + (ref (gncInvoiceGetID invoice)) + (bic (or (gnc:company-info book gnc:*company-bic*) "")) + (name (or (gnc:company-info book gnc:*company-name*) "")) + (iban (or (gnc:company-info book gnc:*company-iban*) "")) + (print-info (gnc-commodity-print-info currency #f)) + (amount-str (string-append + (gnc-commodity-get-mnemonic currency) + (xaccPrintAmount amount print-info)))) + (cond + ((not (gnc-qrcode-available)) (quit "Error: qrencode not available")) + ((not (<= 4 (string-length iban) 34)) (quit "IBAN invalid")) + ((not (<= (string-length bic) 11)) (quit "BIC cannot exceed 11 characters")) + ((not (<= 1/100 amount 99999999999/100)) (quit "Amount out of bounds")) + (else (let ((QRC (string-join + (list "BCD" "001" "1" "SCT" bic (string-max name 70) + iban amount-str "" (string-max ref 140) "") "\n"))) + (match (gnc-qrcode-encodestring QRC) + ((version width . data) + (list "qr-code" (data->svg data width 3 QRC))) + (_ (quit "libqrencode error")))))))) diff --git a/gnucash/report/html-fonts.scm b/gnucash/report/html-fonts.scm index 5a18afcb0b..1ba759de4d 100644 --- a/gnucash/report/html-fonts.scm +++ b/gnucash/report/html-fonts.scm @@ -188,5 +188,7 @@ "td.centered-label-cell { text-align: center; " centered-label-cell-info " }\n" "sub { top: 0.4em; }\n" "sub, sup { vertical-align: baseline; position: relative; top: -0.4em; }\n" + ".qr-code-error { color: white; background-color: red }\n" + "@media print { .qr-code-error { display:none; }}\n" "@media print { html, body { height: unset; }}\n" (or (gnc:html-document-style-text doc) ""))))) diff --git a/gnucash/report/report.i b/gnucash/report/report.i index e11655a3ca..77760bc84f 100644 --- a/gnucash/report/report.i +++ b/gnucash/report/report.i @@ -23,6 +23,7 @@ /* Includes the header in the wrapper code */ #include #include +#include %} #if defined(SWIGGUILE) %{ @@ -42,3 +43,6 @@ gchar* gnc_get_default_report_font_family(); void gnc_saved_reports_backup (void); gboolean gnc_saved_reports_write_to_file (const gchar* report_def, gboolean overwrite); + +SCM gnc_qrcode_encodestring (gchar *str); +SCM gnc_qrcode_available (void); diff --git a/gnucash/report/report.scm b/gnucash/report/report.scm index c8083ab770..c4b29a7ada 100644 --- a/gnucash/report/report.scm +++ b/gnucash/report/report.scm @@ -36,6 +36,7 @@ (load-and-reexport (gnucash html) (gnucash report html-style-sheet) + (gnucash report gnc-qrencode) (gnucash report report-register-hooks) (gnucash report html-utilities) (gnucash report commodity-utilities) diff --git a/gnucash/report/reports/standard/invoice.scm b/gnucash/report/reports/standard/invoice.scm index 6036b89112..4c83927e0e 100644 --- a/gnucash/report/reports/standard/invoice.scm +++ b/gnucash/report/reports/standard/invoice.scm @@ -515,6 +515,18 @@ for styling the invoice. Please see the exported report for the CSS class names. "u" (N_ "Extra notes to put on the invoice.") (G_ "Thank you for your patronage!")) + (gnc-register-multichoice-callback-option + options + (N_ "Display") (N_ "QR Code") + "te" "QR Code" + "none" + (list + (vector 'none (N_ "None") (N_ "None")) + (vector 'epc (N_ "EPC QR Code") (N_ "EPC QR Code"))) + (lambda _ + (gnc-optiondb-set-option-selectable-by-name + options "Display" "QR Code" (gnc-qrcode-available)))) + (gnc-register-multichoice-option options (N_ "Layout") (N_ "Row 1 Left") "1a" "1st row, left" @@ -781,6 +793,7 @@ for styling the invoice. Please see the exported report for the CSS class names. (else (G_ "Invoice")))) (title (if (string-null? custom-title) default-title custom-title)) + (opt-qrcode (opt-val "Display" "QR Code")) ;; Translators: This is the format of the invoice ;; title. The first ~a is one of "Invoice", "Credit ;; Note", and so on and the second the number. Replace @@ -856,6 +869,17 @@ for styling the invoice. Please see the exported report for the CSS class names. (multiline-to-html-text (string-append contact-str ": " contact))))))) + (cond + ((eq? opt-qrcode 'none) #f) + ((not (gnc-qrcode-available)) + (gnc:warn "invoice QR: libqrencode not available")) + ((eq? opt-qrcode 'epc) + (gnc:html-table-append-row! + main-table + (gnc:make-html-table-cell/size + 1 2 (apply gnc:make-html-div/markup (gnc:make-epc-qrcode invoice))))) + (else (gnc:warn "QR option invalid"))) + (gnc:html-table-append-row! main-table (gnc:make-html-table-cell/size 1 2 (gnc:make-html-div/markup diff --git a/libgnucash/engine/gnc-optiondb.cpp b/libgnucash/engine/gnc-optiondb.cpp index 17d23d8fb6..c342212ae7 100644 --- a/libgnucash/engine/gnc-optiondb.cpp +++ b/libgnucash/engine/gnc-optiondb.cpp @@ -1335,6 +1335,12 @@ gnc_option_db_book_options(GncOptionDB* odb) OPTION_NAME_DEFAULT_INVOICE_REPORT_TIMEOUT, "e2", N_("Length of time to change the used invoice report. A value of 0 means disabled."), 0.0, 0.0, 20.0, 1.0); + gnc_register_string_option (odb, business_section, N_("Company IBAN"), "c7", + N_ ("The International Bank Account Number for receiving payments."), + empty_string); + gnc_register_string_option (odb, business_section, N_("Company BIC"), "c8", + N_ ("The Business Identifier Codes (or SWIFT) for your bank."), + empty_string); gnc_register_taxtable_option(odb, business_section, N_("Default Customer TaxTable"), "f1", N_("The default tax table to apply to customers."),