#include "invoice.h" #include #include #include #include "pdf.h" #include "util.h" #include "wordmark.h" #define INK "#314c59" #define WHITE "#ffffff" #define RULE "#000000" #define PAGE_LEFT 17.300 #define PAGE_RIGHT_X 577.700 #define BAR_HEADER_Y 53.300 #define BAR_HEADER_H 22.416 #define BAR_TABLE_Y 242.436 #define BAR_TABLE_H 15.410 #define RULE_SUMMARY_Y 507.574 #define RULE_FOOTER_Y 621.756 #define RULE_W 0.7005 #define SIZE_VALUE 9.1065 #define SIZE_LABEL 7.2852 #define SIZE_FOOT 6.3746 #define SIZE_PAGE 9.75 #define ROW_STEP 14.7105 #define DESC_FIRST_STEP 29.4210 #define INFO_VALUE_X 118.8722 #define INFO_LABEL_RIGHT 114.010 #define INFO_BASE_Y 101.1053 #define LABEL_DY 0.3874 #define DRY_LABEL_Y 172.2070 #define DRY_LINE_STEP 10.4716 #define DRY_LINE_Y 174.0293 #define OUR_REF_Y 209.6830 #define YOUR_REF_Y 224.3935 #define ADDR_X 370.3521 #define ADDR_BASE_Y 115.8158 #define TABLE_HEADER_Y 253.8146 #define COL_ARTICLE_X 20.1015 #define COL_DESC_X 118.8722 #define COL_QTY_RIGHT 365.446 #define COL_UNIT_X 370.3521 #define COL_PRICE_RIGHT 433.392 #define COL_NOTE_X 442.4121 #define COL_ANM_RIGHT 504.147 #define COL_AMOUNT_RIGHT 574.892 #define ROW_FIRST_Y 268.5251 #define SUM_LABEL_RIGHT 329.760 #define SUM_VALUE_RIGHT 365.446 #define SUM_R_LABEL 504.170 #define SUM_R_VALUE 574.892 #define SUM_Y_A 518.6040 #define SUM_Y_B 533.3146 #define SUM_Y_C 548.0251 #define SUM_Y_D 569.0401 #define SUM_Y_E 583.7506 #define SUM_Y_OCR 598.4612 #define SUM_Y_TOTAL 615.2373 #define FOOT_LABEL_Y 642.4721 #define FOOT_ORG_Y 641.8922 #define FOOT_NAME_Y 656.6028 #define FOOT_VAT_Y 655.8664 #define FOOT_MOMSY 657.1827 #define FOOT_ADDR_Y 671.3133 #define FOOT_EMAIL_Y 671.3133 #define FOOT_FSKATT_Y 671.8932 #define FOOT_CITY_Y 686.0238 #define FOOT_PAGE_Y 819.7954 #define WORDMARK_X 21.742 #define FAKTURA_X 498.400 static void text_at(struct pdf *p, double x, double base, const char *font, double size, const char *rgb, const char *s) { if (s && *s) pdf_text(p, x, base, font, size, rgb, s); } static void text_right(struct pdf *p, double right, double base, const char *font, double size, const char *rgb, const char *s) { if (s && *s) pdf_text(p, right - pdf_text_width(font, size, s), base, font, size, 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]; int len = snprintf(digits, sizeof digits, "%llu", (unsigned long long)v); size_t o = 0; if (len < 0) len = 0; for (int i = 0; i < len && o + 1 < n; i++) { if (i > 0 && (len - i) % 3 == 0) out[o++] = ' '; out[o++] = digits[i]; } out[o] = '\0'; } static uint64_t ore_abs(int64_t v) { return v < 0 ? (uint64_t)(-(v + 1)) + 1 : (uint64_t)v; } static void fmt_kronor(int64_t ore, char *out, size_t n, int grouped, int always_cents) { char whole[48]; uint64_t kr = ore_abs(ore) / 100; uint64_t rest = ore_abs(ore) % 100; const char *sign = ore < 0 ? "-" : ""; if (grouped) group_digits(whole, sizeof whole, kr); else snprintf(whole, sizeof whole, "%llu", (unsigned long long)kr); if (rest || always_cents) snprintf(out, n, "%s%s,%02llu", sign, whole, (unsigned long long)rest); else snprintf(out, n, "%s%s", sign, whole); } static void fmt_quantity(int64_t milli, char *out, size_t n) { uint64_t v = ore_abs(milli); uint64_t whole = v / 1000; uint64_t frac = v % 1000; const char *sign = milli < 0 ? "-" : ""; if (!frac) snprintf(out, n, "%s%llu", sign, (unsigned long long)whole); else if (frac % 100 == 0) snprintf(out, n, "%s%llu,%llu", sign, (unsigned long long)whole, (unsigned long long)(frac / 100)); else if (frac % 10 == 0) snprintf(out, n, "%s%llu,%02llu", sign, (unsigned long long)whole, (unsigned long long)(frac / 10)); else snprintf(out, n, "%s%llu,%03llu", sign, (unsigned long long)whole, (unsigned long long)frac); } static int64_t vat_part(int64_t net, int64_t rate) { int64_t v = net * rate; if (v >= 0) return (v + 50) / 100; return -((-v + 50) / 100); } void invoice_totals(const struct invoice_doc *d, struct invoice_totals *t) { if (!t) return; memset(t, 0, sizeof *t); if (!d || (!d->lines && d->nlines)) return; for (size_t i = 0; i < d->nlines; i++) { const struct invoice_line *l = &d->lines[i]; const char *code = l->vat_code ? l->vat_code : "25"; t->net_ore += l->amount_ore; if (strcmp(code, "25") == 0) t->net_25 += l->amount_ore; else if (strcmp(code, "12") == 0) t->net_12 += l->amount_ore; else if (strcmp(code, "6") == 0) t->net_6 += l->amount_ore; else t->net_free += l->amount_ore; } t->vat_ore = vat_part(t->net_25, 25) + vat_part(t->net_12, 12) + vat_part(t->net_6, 6); t->total_ore = t->net_ore + t->vat_ore; } int invoice_ocr_check(const char *number_digits) { size_t len; int sum = 0; if (!number_digits) return -1; len = strlen(number_digits); if (!len) return 0; for (size_t i = 0; i < len; i++) if (number_digits[i] < '0' || number_digits[i] > '9') return -1; for (size_t i = 0; i < len; i++) { int d = number_digits[len - 1 - i] - '0'; if (i % 2 == 0) { d *= 2; if (d > 9) d -= 9; } sum += d; } return (10 - sum % 10) % 10; } static size_t count_lines(const char *s) { size_t n = 1; if (!s) return n; for (const char *p = s; *p; p++) if (*p == '\n') n++; return n; } static double desc_offset(size_t line) { if (line == 0) return 0.0; if (line == 1) return DESC_FIRST_STEP; return DESC_FIRST_STEP + (double)(line - 1) * ROW_STEP; } static void put_desc_line(struct pdf *p, double y, const char *s, size_t len) { char buf[512]; if (!len) return; snprintf(buf, sizeof buf, "%.*s", (int)len, s); pdf_text(p, COL_DESC_X, y, "H", SIZE_VALUE, INK, buf); } static void put_address(struct pdf *p, double x, double y, const char *rgb, double size, const char *postal, const char *city) { char buf[128]; if (!postal) postal = ""; if (!city) city = ""; if (*postal && *city) snprintf(buf, sizeof buf, "%s %s", postal, city); else snprintf(buf, sizeof buf, "%s%s", postal, city); if (*buf) pdf_text(p, x, y, "H", size, rgb, buf); } static int doc_fits(const struct invoice_doc *d) { double y = ROW_FIRST_Y; for (size_t i = 0; i < d->nlines; i++) { const char *s = d->lines[i].description; size_t n = count_lines(s); if (!s) s = ""; for (size_t k = 0; k < n; k++) { if (y + desc_offset(k) > RULE_SUMMARY_Y - 4.0) return 0; s = strchr(s, '\n'); if (s) s++; } y += (double)(n + 1) * ROW_STEP; } return 1; } static void draw_info(struct pdf *p, const struct invoice_doc *d) { static const char *const labels[5] = { "Fakturanummer", "Fakturadatum", "Leveransdatum", "Ert momsreg.nr", "Betalningsvilkor", }; char number[32], payment[32]; const char *values[5]; snprintf(number, sizeof number, "%lld", (long long)d->number); snprintf(payment, sizeof payment, "%d dagar", d->payment_days); values[0] = number; values[1] = d->invoice_date; values[2] = d->delivery_date; values[3] = d->customer.vat_nr; values[4] = payment; for (size_t i = 0; i < 5; i++) { double y = INFO_BASE_Y + (double)i * ROW_STEP; text_right(p, INFO_LABEL_RIGHT, y + LABEL_DY, "HB", SIZE_LABEL, INK, labels[i]); text_at(p, INFO_VALUE_X, y, "H", SIZE_VALUE, INK, values[i]); } text_right(p, INFO_LABEL_RIGHT, DRY_LABEL_Y, "HB", SIZE_LABEL, INK, "Dröjsmålsränta"); text_at(p, INFO_VALUE_X, DRY_LINE_Y, "H", SIZE_VALUE, INK, "Vid betalning efter"); text_at(p, INFO_VALUE_X, DRY_LINE_Y + DRY_LINE_STEP, "H", SIZE_VALUE, INK, "förfallodagen debiteras"); text_at(p, INFO_VALUE_X, DRY_LINE_Y + 2 * DRY_LINE_STEP, "H", SIZE_VALUE, INK, "ränta enligt räntelagen"); text_right(p, INFO_LABEL_RIGHT, OUR_REF_Y + LABEL_DY, "HB", SIZE_LABEL, INK, "Vår referens"); text_at(p, INFO_VALUE_X, OUR_REF_Y, "H", SIZE_VALUE, INK, d->our_ref); text_right(p, INFO_LABEL_RIGHT, YOUR_REF_Y + LABEL_DY, "HB", SIZE_LABEL, INK, "Er referens"); text_at(p, INFO_VALUE_X, YOUR_REF_Y, "H", SIZE_VALUE, INK, d->your_ref); } static void draw_customer(struct pdf *p, const struct invoice_customer *c) { const char *s = c->address ? c->address : ""; double y = ADDR_BASE_Y; text_at(p, ADDR_X, INFO_BASE_Y, "HB", SIZE_VALUE, INK, "Fakturaadress"); text_at(p, ADDR_X, y, "H", SIZE_VALUE, INK, c->name); y += ROW_STEP; for (;;) { const char *nl = strchr(s, '\n'); if (nl) { char buf[512]; snprintf(buf, sizeof buf, "%.*s", (int)(nl - s), s); if (*buf) pdf_text(p, ADDR_X, y, "H", SIZE_VALUE, INK, buf); y += ROW_STEP; s = nl + 1; } else { if (*s) pdf_text(p, ADDR_X, y, "H", SIZE_VALUE, INK, s); break; } } put_address(p, ADDR_X, y + ROW_STEP, INK, SIZE_VALUE, c->postal_code, c->city); } static void draw_table(struct pdf *p, const struct invoice_doc *d) { double y = ROW_FIRST_Y; pdf_fill_rect(p, PAGE_LEFT, BAR_TABLE_Y, PAGE_RIGHT_X - PAGE_LEFT, BAR_TABLE_H, INK); text_at(p, COL_ARTICLE_X, TABLE_HEADER_Y, "HB", SIZE_VALUE, WHITE, "Art.nr"); text_at(p, COL_DESC_X, TABLE_HEADER_Y, "HB", SIZE_VALUE, WHITE, "Beskrivning"); text_right(p, COL_QTY_RIGHT, TABLE_HEADER_Y, "HB", SIZE_VALUE, WHITE, "Antal"); text_right(p, COL_PRICE_RIGHT, TABLE_HEADER_Y, "HB", SIZE_VALUE, WHITE, "á-pris"); text_right(p, COL_ANM_RIGHT, TABLE_HEADER_Y, "HB", SIZE_VALUE, WHITE, "Anm"); text_right(p, COL_AMOUNT_RIGHT, TABLE_HEADER_Y, "HB", SIZE_VALUE, WHITE, "Belopp"); for (size_t i = 0; i < d->nlines; i++) { const struct invoice_line *l = &d->lines[i]; char qty[32], price[48], amount[48]; const char *s = l->description ? l->description : ""; size_t line = 0; fmt_quantity(l->quantity_milli, qty, sizeof qty); fmt_kronor(l->unit_price_ore, price, sizeof price, 0, 0); fmt_kronor(l->amount_ore, amount, sizeof amount, 0, 0); for (;;) { const char *nl = strchr(s, '\n'); if (nl) { put_desc_line(p, y + desc_offset(line), s, (size_t)(nl - s)); s = nl + 1; line++; } else { put_desc_line(p, y + desc_offset(line), s, strlen(s)); break; } } text_at(p, COL_ARTICLE_X, y, "H", SIZE_VALUE, INK, l->article_no); text_right(p, COL_QTY_RIGHT, y, "H", SIZE_VALUE, INK, qty); text_at(p, COL_UNIT_X, y, "H", SIZE_VALUE, INK, l->unit); text_right(p, COL_PRICE_RIGHT, y, "H", SIZE_VALUE, INK, price); text_at(p, COL_NOTE_X, y, "H", SIZE_VALUE, INK, l->note); text_right(p, COL_AMOUNT_RIGHT, y, "H", SIZE_VALUE, INK, amount); y += (double)(line + 2) * ROW_STEP; } } static void draw_summary(struct pdf *p, const struct invoice_doc *d, const struct invoice_totals *t) { char value[64], total[64]; pdf_line(p, PAGE_LEFT, RULE_SUMMARY_Y, PAGE_RIGHT_X, RULE_SUMMARY_Y, RULE_W, RULE); text_right(p, SUM_LABEL_RIGHT, SUM_Y_A + LABEL_DY, "H", SIZE_LABEL, INK, "Momsunderlag"); text_right(p, SUM_R_LABEL, SUM_Y_A + LABEL_DY, "H", SIZE_LABEL, INK, "Belopp före moms"); fmt_kronor(t->net_ore, value, sizeof value, 0, 0); text_right(p, SUM_R_VALUE, SUM_Y_A, "H", SIZE_VALUE, INK, value); text_right(p, SUM_LABEL_RIGHT, SUM_Y_B + LABEL_DY, "H", SIZE_LABEL, INK, "Moms 25%"); fmt_kronor(t->net_25, value, sizeof value, 0, 0); text_right(p, SUM_VALUE_RIGHT, SUM_Y_B, "H", SIZE_VALUE, INK, value); text_right(p, SUM_R_LABEL, SUM_Y_B + LABEL_DY, "H", SIZE_LABEL, INK, "Total moms"); fmt_kronor(t->vat_ore, value, sizeof value, 0, 0); text_right(p, SUM_R_VALUE, SUM_Y_B, "H", SIZE_VALUE, INK, value); text_right(p, SUM_LABEL_RIGHT, SUM_Y_C + LABEL_DY, "H", SIZE_LABEL, INK, "Moms 12%"); fmt_kronor(t->net_12, value, sizeof value, 0, 0); text_right(p, SUM_VALUE_RIGHT, SUM_Y_C, "H", SIZE_VALUE, INK, value); text_right(p, SUM_R_LABEL, SUM_Y_C + LABEL_DY, "H", SIZE_LABEL, INK, "Öresutjämning"); text_right(p, SUM_LABEL_RIGHT, SUM_Y_D + LABEL_DY, "H", SIZE_LABEL, INK, "Moms 6%"); fmt_kronor(t->net_6, value, sizeof value, 0, 0); text_right(p, SUM_VALUE_RIGHT, SUM_Y_D, "H", SIZE_VALUE, INK, value); text_right(p, SUM_R_LABEL, SUM_Y_D + LABEL_DY, "H", SIZE_LABEL, INK, "Förfallodatum"); text_right(p, SUM_R_VALUE, SUM_Y_D, "H", SIZE_VALUE, INK, d->due_date); text_right(p, SUM_LABEL_RIGHT, SUM_Y_E + LABEL_DY, "H", SIZE_LABEL, INK, "Momsfritt"); fmt_kronor(t->net_free, value, sizeof value, 0, 0); text_right(p, SUM_VALUE_RIGHT, SUM_Y_E, "H", SIZE_VALUE, INK, value); text_right(p, SUM_R_LABEL, SUM_Y_E + LABEL_DY, "H", SIZE_LABEL, INK, "Bankgiro"); text_right(p, SUM_R_VALUE, SUM_Y_E, "H", SIZE_VALUE, INK, d->seller.bankgiro); text_right(p, SUM_R_LABEL, SUM_Y_OCR, "H", SIZE_VALUE, INK, "OCR"); text_right(p, SUM_R_VALUE, SUM_Y_OCR, "H", SIZE_VALUE, INK, d->ocr); text_right(p, SUM_R_LABEL, SUM_Y_TOTAL, "HB", SIZE_VALUE, INK, "Summa att betala SEK"); fmt_kronor(t->total_ore, total, sizeof total, 1, 1); text_right(p, SUM_R_VALUE, SUM_Y_TOTAL, "HB", SIZE_VALUE, INK, total); } static void draw_footer(struct pdf *p, const struct invoice_doc *d) { const struct invoice_seller *s = &d->seller; pdf_line(p, PAGE_LEFT, RULE_FOOTER_Y, PAGE_RIGHT_X, RULE_FOOTER_Y, RULE_W, RULE); text_at(p, COL_ARTICLE_X, FOOT_LABEL_Y, "HB", SIZE_FOOT, INK, "Adress"); text_at(p, INFO_VALUE_X, FOOT_LABEL_Y, "HB", SIZE_FOOT, INK, "Kontakt"); text_at(p, 263.8759, FOOT_LABEL_Y, "HB", SIZE_FOOT, INK, "Orgnr"); text_at(p, COL_ARTICLE_X, FOOT_NAME_Y, "H", SIZE_VALUE, INK, s->name); if (s->phone && *s->phone) { char phone[64]; snprintf(phone, sizeof phone, " %s", s->phone); pdf_text(p, INFO_VALUE_X, FOOT_NAME_Y, "H", SIZE_VALUE, INK, phone); } text_at(p, COL_ARTICLE_X, FOOT_ADDR_Y, "H", SIZE_VALUE, INK, s->address); text_at(p, INFO_VALUE_X, FOOT_EMAIL_Y, "H", SIZE_VALUE, INK, s->email); put_address(p, COL_ARTICLE_X, FOOT_CITY_Y, INK, SIZE_VALUE, s->postal_code, s->city); text_at(p, 334.6266, FOOT_ORG_Y, "H", SIZE_VALUE, INK, s->org_nr); text_at(p, 263.8759, FOOT_MOMSY, "HB", SIZE_FOOT, INK, "Momsregnr"); text_at(p, 334.6266, FOOT_VAT_Y, "H", SIZE_VALUE, INK, s->vat_nr); text_at(p, 263.8759, FOOT_FSKATT_Y, "HB", SIZE_FOOT, INK, "Godkänd för F-skatt"); text_at(p, 567.4275, FOOT_PAGE_Y, "H", SIZE_PAGE, INK, "1"); } int invoice_render_pdf(const struct invoice_doc *d, unsigned char **out, size_t *len) { struct invoice_totals t; struct pdf *p; if (out) *out = NULL; if (len) *len = 0; if (!d || !out || !len) return -1; if (d->nlines && !d->lines) return -1; if (!doc_fits(d)) return -1; invoice_totals(d, &t); 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); draw_info(p, d); draw_customer(p, &d->customer); draw_table(p, d); draw_summary(p, d, &t); draw_footer(p, d); *out = pdf_finish(p, len); return *out ? 0 : -1; }