summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-22 09:04:27 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-22 09:04:27 +0200
commit227d0422da0062410c85529bc999d7e509d9de78 (patch)
tree23102d1fb049b610ec154e7c136e3d92d941bd6d /src
parent8ceb1340a9030bb1030be61003dc65b444732b9e (diff)
downloadbokf-0.1.63.tar.gz
bokf-0.1.63.zip
documents: org name in the invoice and payslip headers, document_header_colorv0.1.63
Diffstat (limited to 'src')
-rw-r--r--src/cmd_invoices.c5
-rw-r--r--src/cmd_payroll.c4
-rw-r--r--src/cmd_settings.c17
-rw-r--r--src/invoice.c16
-rw-r--r--src/invoice.h1
-rw-r--r--src/payslip.c62
-rw-r--r--src/payslip.h1
-rw-r--r--src/pdf.c36
-rw-r--r--src/pdf.h6
-rw-r--r--src/util.c13
-rw-r--r--src/util.h1
11 files changed, 103 insertions, 59 deletions
diff --git a/src/cmd_invoices.c b/src/cmd_invoices.c
index 2a6a9c6..a9d3fb1 100644
--- a/src/cmd_invoices.c
+++ b/src/cmd_invoices.c
@@ -380,6 +380,7 @@ struct invoice_view {
char seller_org_nr[64];
char seller_vat_nr[64];
char bankgiro[64];
+ char header_color[8];
char customer_name[256];
char customer_address[1024];
char customer_postal[64];
@@ -467,6 +468,9 @@ static int invoice_view_fill(struct req *r, const struct invoice_draft *d,
bankgiro && *bankgiro ? bankgiro : "");
free(bankgiro);
+ db_setting_copy(r->db, r->org_id, "document_header_color", "",
+ v->header_color, sizeof v->header_color);
+
v->lines = xcalloc(d->nlines, sizeof *v->lines);
for (size_t i = 0; i < d->nlines; i++) {
v->lines[i].article_no = d->lines[i].article_no;
@@ -489,6 +493,7 @@ static int invoice_view_fill(struct req *r, const struct invoice_draft *d,
v->doc.seller.org_nr = v->seller_org_nr;
v->doc.seller.vat_nr = v->seller_vat_nr;
v->doc.seller.bankgiro = v->bankgiro;
+ v->doc.header_color = v->header_color;
v->doc.customer.name = v->customer_name;
v->doc.customer.address = v->customer_address;
v->doc.customer.postal_code = v->customer_postal;
diff --git a/src/cmd_payroll.c b/src/cmd_payroll.c
index 84ae323..6dea293 100644
--- a/src/cmd_payroll.c
+++ b/src/cmd_payroll.c
@@ -762,6 +762,7 @@ struct payslip_ctx {
char employer_org_nr[64];
char employer_phone[64];
char employer_email[256];
+ char header_color[8];
char filename[320];
int64_t voucher_id;
int64_t employee_id;
@@ -924,6 +925,8 @@ static int payslip_prepare(struct req *r, struct payslip_ctx *c)
struct payroll_cfg cfg;
payroll_cfg_load(r, &cfg);
snprintf(c->run_ref, sizeof c->run_ref, "Lönekörning %s", c->period);
+ db_setting_copy(r->db, r->org_id, "document_header_color", "",
+ c->header_color, sizeof c->header_color);
char safe_name[200];
snprintf(safe_name, sizeof safe_name, "%s", c->employee_name);
@@ -942,6 +945,7 @@ static int payslip_prepare(struct req *r, struct payslip_ctx *c)
c->d.employer.email = c->employer_email;
c->d.employee_name = c->employee_name;
c->d.personal_no_masked = c->personal_no;
+ c->d.header_color = c->header_color;
c->d.period = c->period;
c->d.pay_date = c->pay_date;
c->d.run_ref = c->run_ref;
diff --git a/src/cmd_settings.c b/src/cmd_settings.c
index f41f904..5bac3d9 100644
--- a/src/cmd_settings.c
+++ b/src/cmd_settings.c
@@ -22,7 +22,7 @@ static yyjson_mut_val *h_settings_get(struct req *r)
int have_default = 0, have_bank = 0, have_receivable = 0, have_revenue = 0;
int have_password = 0, have_security = 0;
int have_voucher = 0, have_invoice = 0, have_payroll = 0;
- int have_bokslut = 0, have_ib = 0;
+ int have_bokslut = 0, have_ib = 0, have_header_color = 0;
char legacy_series[16] = "A";
sqlite3_stmt *st = NULL;
if (sqlite3_prepare_v2(
@@ -59,6 +59,8 @@ static yyjson_mut_val *h_settings_get(struct req *r)
have_receivable = 1;
} else if (strcmp(k, "invoice_revenue_account") == 0) {
have_revenue = 1;
+ } else if (strcmp(k, "document_header_color") == 0) {
+ have_header_color = 1;
} else if (strcmp(k, "smtp_security") == 0) {
have_security = 1;
}
@@ -87,6 +89,9 @@ static yyjson_mut_val *h_settings_get(struct req *r)
if (!have_revenue)
yyjson_mut_obj_add_strcpy(r->rdoc, o, "invoice_revenue_account",
"3001");
+ if (!have_header_color)
+ yyjson_mut_obj_add_strcpy(r->rdoc, o, "document_header_color",
+ "#314c59");
if (!have_security)
yyjson_mut_obj_add_strcpy(r->rdoc, o, "smtp_security", "starttls");
yyjson_mut_obj_add_bool(r->rdoc, o, "smtp_password_set", have_password);
@@ -162,7 +167,7 @@ static yyjson_mut_val *h_settings_set(struct req *r)
return settings_result(r, key, "[redacted]", 0);
}
size_t maxlen;
- int digits_only = 0, bankgiro = 0, port = 0, security = 0;
+ int digits_only = 0, bankgiro = 0, port = 0, security = 0, color = 0;
if (strcmp(key, "default_series") == 0 ||
strcmp(key, "series_voucher") == 0 ||
strcmp(key, "series_invoice") == 0 ||
@@ -193,6 +198,9 @@ static yyjson_mut_val *h_settings_set(struct req *r)
bankgiro = 1;
} else if (strcmp(key, "invoice_our_ref") == 0) {
maxlen = 64;
+ } else if (strcmp(key, "document_header_color") == 0) {
+ maxlen = 7;
+ color = 1;
} else
return fail(r, "UNSUPPORTED", "unknown setting");
size_t len = strlen(value);
@@ -209,6 +217,8 @@ static yyjson_mut_val *h_settings_set(struct req *r)
return failf(r, "INVALID_ARGS",
"%s must not contain control characters", key);
}
+ if (color && !util_hex_color_valid(value))
+ return failf(r, "INVALID_ARGS", "%s must be #rrggbb", key);
if (port) {
long v = strtol(value, NULL, 10);
if (v < 1 || v > 65535)
@@ -252,7 +262,8 @@ static yyjson_mut_val *h_settings_set(struct req *r)
static const struct cmd_arg args_settings_set[] = {
{ "key", ARG_STR, 1, NULL, NULL,
"default_series, attachment_dir, bank_account,"
- " invoice_receivable_account, invoice_revenue_account, smtp_host,"
+ " invoice_receivable_account, invoice_revenue_account,"
+ " invoice_bankgiro, invoice_our_ref, document_header_color, smtp_host,"
" smtp_port, smtp_user, smtp_from, smtp_reply_to, smtp_security or"
" smtp_password" },
{ "value", ARG_STR, 0, NULL, NULL,
diff --git a/src/invoice.c b/src/invoice.c
index 34b2a85..1bdc0da 100644
--- a/src/invoice.c
+++ b/src/invoice.c
@@ -77,8 +77,12 @@
#define FOOT_CITY_Y 686.0238
#define FOOT_PAGE_Y 819.7954
-#define WORDMARK_X 21.742
+#define HEADER_NAME_X 21.742
#define FAKTURA_X 498.400
+#define HEADER_BASE_Y 69.1358
+#define HEADER_NAME_SIZE 16.0
+#define HEADER_NAME_MIN_SIZE 8.0
+#define HEADER_NAME_MAX_W 460.0
static void text_at(struct pdf *p, double x, double base, const char *font,
double size, const char *rgb, const char *s)
@@ -581,9 +585,13 @@ int invoice_render_pdf(const struct invoice_doc *d, unsigned char **out,
p = pdf_new();
pdf_page(p);
pdf_fill_rect(p, PAGE_LEFT, BAR_HEADER_Y, PAGE_RIGHT_X - PAGE_LEFT,
- BAR_HEADER_H, INK);
- draw_wordmark(p, WORDMARK_MAKANDRA, WORDMARK_X, 69.1358);
- draw_wordmark(p, WORDMARK_FAKTURA, FAKTURA_X, 69.1358);
+ BAR_HEADER_H,
+ util_hex_color_valid(d->header_color) ? d->header_color
+ : INK);
+ pdf_text_fit(p, HEADER_NAME_X, HEADER_BASE_Y, "HB", HEADER_NAME_SIZE,
+ HEADER_NAME_MIN_SIZE, HEADER_NAME_MAX_W, WHITE,
+ d->seller.name);
+ draw_wordmark(p, WORDMARK_FAKTURA, FAKTURA_X, HEADER_BASE_Y);
draw_info(p, d);
draw_customer(p, &d->customer);
draw_table(p, d);
diff --git a/src/invoice.h b/src/invoice.h
index 7d64f21..55d1e3b 100644
--- a/src/invoice.h
+++ b/src/invoice.h
@@ -41,6 +41,7 @@ struct invoice_doc {
struct invoice_customer customer;
int64_t number;
const char *ocr;
+ const char *header_color; /* "#rrggbb"; NULL or invalid = default */
const char *invoice_date;
const char *due_date;
const char *delivery_date;
diff --git a/src/payslip.c b/src/payslip.c
index 81ebded..ee8e008 100644
--- a/src/payslip.c
+++ b/src/payslip.c
@@ -6,7 +6,6 @@
#include "pdf.h"
#include "util.h"
-#include "wordmark.h"
#define INK "#314c59"
#define WHITE "#ffffff"
@@ -52,9 +51,12 @@
#define FOOT_PAGE_Y 819.7954
#define FOOT_PAGE_RIGHT 567.4275
-#define WORDMARK_X 21.742
+#define HEADER_NAME_X 21.742
#define TITLE_RIGHT 574.892
#define TITLE_BASE_Y 69.1358
+#define HEADER_NAME_SIZE 16.0
+#define HEADER_NAME_MIN_SIZE 8.0
+#define HEADER_NAME_MAX_W 460.0
static void text_at(struct pdf *p, double x, double base, const char *font,
double size, const char *rgb, const char *s)
@@ -72,54 +74,6 @@ static void text_right(struct pdf *p, double right, double base,
rgb, s);
}
-/* pdf_path consumes SVG-style y-down paths; wordmark.h stores y-up outline
- coordinates, so negate the y of every point before drawing. */
-static void draw_wordmark(struct pdf *p, const char *path, double x, double y)
-{
- size_t n = strlen(path);
- char *flipped = xmalloc(2 * n + 2);
- char op = 0;
- int num = 0, in_num = 0, y_coord = 0;
- size_t o = 0;
-
- for (size_t i = 0; i < n; i++) {
- char c = path[i];
-
- if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
- if (c == 'M' || c == 'L' || c == 'C') {
- op = c;
- num = 0;
- } else if (c == 'Z') {
- op = 0;
- }
- flipped[o++] = c;
- in_num = 0;
- continue;
- }
- if (!in_num && (c == '-' || c == '+' || c == '.' ||
- (c >= '0' && c <= '9'))) {
- in_num = 1;
- y_coord = op && (num % 2 == 1);
- num++;
- if (y_coord) {
- if (c == '-') {
- i++;
- c = path[i];
- } else {
- flipped[o++] = '-';
- }
- }
- } else if (!(c == '-' || c == '+' || c == '.' ||
- (c >= '0' && c <= '9'))) {
- in_num = 0;
- }
- flipped[o++] = c;
- }
- flipped[o] = '\0';
- pdf_path(p, flipped, x, y, WHITE);
- free(flipped);
-}
-
static void group_digits(char *out, size_t n, uint64_t v)
{
char digits[24];
@@ -284,8 +238,12 @@ int payslip_render(const struct payslip_data *d, unsigned char **out,
p = pdf_new();
pdf_page(p);
pdf_fill_rect(p, PAGE_LEFT, BAR_HEADER_Y, PAGE_RIGHT_X - PAGE_LEFT,
- BAR_HEADER_H, INK);
- draw_wordmark(p, WORDMARK_MAKANDRA, WORDMARK_X, TITLE_BASE_Y);
+ BAR_HEADER_H,
+ util_hex_color_valid(d->header_color) ? d->header_color
+ : INK);
+ pdf_text_fit(p, HEADER_NAME_X, TITLE_BASE_Y, "HB", HEADER_NAME_SIZE,
+ HEADER_NAME_MIN_SIZE, HEADER_NAME_MAX_W, WHITE,
+ d->employer.name);
text_right(p, TITLE_RIGHT, TITLE_BASE_Y, "HB", SIZE_TITLE, WHITE,
"LÖNEBESKED");
pdf_line(p, PAGE_LEFT, RULE_HEADER_Y, PAGE_RIGHT_X, RULE_HEADER_Y, RULE_W,
diff --git a/src/payslip.h b/src/payslip.h
index 291b35d..581cd81 100644
--- a/src/payslip.h
+++ b/src/payslip.h
@@ -16,6 +16,7 @@ struct payslip_data {
} employer;
const char *employee_name;
const char *personal_no_masked;
+ const char *header_color; /* "#rrggbb"; NULL or invalid = default */
const char *period;
const char *pay_date;
int tax_table;
diff --git a/src/pdf.c b/src/pdf.c
index c6ae876..5718ed7 100644
--- a/src/pdf.c
+++ b/src/pdf.c
@@ -353,6 +353,42 @@ double pdf_font_ascent(const char *font, double size)
return size > 0 ? size * PDF_ASCENT / 1000.0 : 0.0;
}
+void pdf_text_fit(struct pdf *p, double x, double baseline, const char *font,
+ double size, double min_size, double max_w, const char *rgb,
+ const char *utf8)
+{
+ char *truncated = NULL;
+ const char *text = utf8;
+ double w;
+
+ if (!utf8 || !*utf8 || max_w <= 0)
+ return;
+ w = pdf_text_width(font, size, utf8);
+ if (w > max_w) {
+ size = size * max_w / w;
+ if (size < min_size)
+ size = min_size;
+ w = pdf_text_width(font, size, utf8);
+ }
+ if (w > max_w) {
+ size_t n = strlen(utf8);
+
+ truncated = xmalloc(n + 4);
+ memcpy(truncated, utf8, n);
+ while (n > 0) {
+ memcpy(truncated + n, "...", 4);
+ if (pdf_text_width(font, size, truncated) <= max_w)
+ break;
+ n--;
+ while (n > 0 && ((unsigned char)truncated[n] & 0xC0) == 0x80)
+ n--;
+ }
+ text = truncated;
+ }
+ pdf_text(p, x, baseline, font, size, rgb, text);
+ free(truncated);
+}
+
static int parse_num(const char **sp, double *out)
{
const char *s = *sp;
diff --git a/src/pdf.h b/src/pdf.h
index c6f95ac..9ed4595 100644
--- a/src/pdf.h
+++ b/src/pdf.h
@@ -20,6 +20,12 @@ void pdf_text(struct pdf *p, double x, double baseline, const char *font,
double size, const char *rgb, const char *utf8);
double pdf_text_width(const char *font, double size, const char *utf8);
double pdf_font_ascent(const char *font, double size);
+/* Draws utf8 at x/baseline, shrinking the size (down to min_size) to fit
+ max_w; if it still does not fit, truncates at a whole UTF-8 character so
+ the text ends with "...". */
+void pdf_text_fit(struct pdf *p, double x, double baseline, const char *font,
+ double size, double min_size, double max_w, const char *rgb,
+ const char *utf8);
void pdf_path(struct pdf *p, const char *path, double x, double y,
const char *rgb);
unsigned char *pdf_finish(struct pdf *p, size_t *len);
diff --git a/src/util.c b/src/util.c
index 9543875..7882642 100644
--- a/src/util.c
+++ b/src/util.c
@@ -276,6 +276,19 @@ char *util_str_trim(char *s)
return s;
}
+int util_hex_color_valid(const char *s)
+{
+ if (!s || s[0] != '#' || strlen(s) != 7)
+ return 0;
+ for (int i = 1; i < 7; i++) {
+ char c = s[i];
+ if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F')))
+ return 0;
+ }
+ return 1;
+}
+
static int is_leap(int y)
{
return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
diff --git a/src/util.h b/src/util.h
index d854f76..a94cf16 100644
--- a/src/util.h
+++ b/src/util.h
@@ -32,6 +32,7 @@ int util_const_eq(const void *a, const void *b, size_t n);
int64_t util_now(void);
void util_iso8601(int64_t t, char *buf, size_t n);
char *util_str_trim(char *s);
+int util_hex_color_valid(const char *s);
int util_parse_iso_date(const char *s);
/* base64 (standard alphabet, padding required) */