aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/invoice.c576
-rw-r--r--src/invoice.h66
2 files changed, 642 insertions, 0 deletions
diff --git a/src/invoice.c b/src/invoice.c
new file mode 100644
index 0000000..622c05a
--- /dev/null
+++ b/src/invoice.c
@@ -0,0 +1,576 @@
+#include "invoice.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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;
+}
diff --git a/src/invoice.h b/src/invoice.h
new file mode 100644
index 0000000..c92677d
--- /dev/null
+++ b/src/invoice.h
@@ -0,0 +1,66 @@
+#ifndef BOKF_INVOICE_H
+#define BOKF_INVOICE_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+struct invoice_seller {
+ const char *name;
+ const char *address;
+ const char *postal_code;
+ const char *city;
+ const char *phone;
+ const char *email;
+ const char *org_nr;
+ const char *vat_nr;
+ const char *bankgiro;
+};
+
+struct invoice_customer {
+ const char *name;
+ const char *address;
+ const char *postal_code;
+ const char *city;
+ const char *vat_nr;
+};
+
+struct invoice_line {
+ const char *article_no;
+ const char *description;
+ int64_t quantity_milli;
+ const char *unit;
+ int64_t unit_price_ore;
+ int64_t amount_ore;
+ const char *note;
+ const char *vat_code;
+};
+
+struct invoice_doc {
+ struct invoice_seller seller;
+ struct invoice_customer customer;
+ int64_t number;
+ const char *ocr;
+ const char *invoice_date;
+ const char *due_date;
+ const char *delivery_date;
+ const char *our_ref;
+ const char *your_ref;
+ const char *notes;
+ int payment_days;
+ const struct invoice_line *lines;
+ size_t nlines;
+};
+
+struct invoice_totals {
+ int64_t net_ore;
+ int64_t vat_ore;
+ int64_t total_ore;
+ int64_t net_25, net_12, net_6, net_free;
+};
+
+void invoice_totals(const struct invoice_doc *d, struct invoice_totals *t);
+int invoice_ocr_check(const char *number_digits);
+int invoice_render_pdf(const struct invoice_doc *d, unsigned char **out,
+ size_t *len);
+
+#endif