#include "payslip.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 RULE_HEADER_Y 90.000 #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 SIZE_TITLE 12.0 #define ROW_STEP 14.7105 #define INFO_LABEL_RIGHT 150.000 #define INFO_VALUE_X 155.000 #define INFO_BASE_Y 112.0000 #define LABEL_DY 0.3874 #define BAR_TABLE_Y 238.000 #define BAR_TABLE_H 15.410 #define TABLE_HEADER_Y 249.3786 #define ROW_FIRST_Y 264.0891 #define AMOUNT_LABEL_X 20.1015 #define AMOUNT_RIGHT 574.892 #define AMOUNT_RULE_Y (ROW_FIRST_Y + 2 * ROW_STEP + 10.0) #define NOTE_DY 7.5000 #define RULE_FOOTER_Y 621.756 #define FOOT_LABEL_Y 642.4721 #define FOOT_NAME_Y 656.6028 #define FOOT_ADDR_Y 671.3133 #define FOOT_EMAIL_Y 671.3133 #define FOOT_CITY_Y 686.0238 #define FOOT_ORG_LABEL_X 263.8759 #define FOOT_ORG_X 334.6266 #define FOOT_ORG_Y 641.8922 #define FOOT_PAGE_Y 819.7954 #define FOOT_PAGE_RIGHT 567.4275 #define WORDMARK_X 21.742 #define TITLE_RIGHT 574.892 #define TITLE_BASE_Y 69.1358 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) { char whole[32]; 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); snprintf(out, n, "%s%s,%02llu", sign, whole, (unsigned long long)rest); } 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 void draw_info(struct pdf *p, const struct payslip_data *d) { static const char *const labels[5] = { "Anställd", "Personnummer", "Period", "Utbetalningsdatum", "Skattetabell", }; char table[16]; const char *values[5]; snprintf(table, sizeof table, "%d kol %d", d->tax_table, d->tax_column); values[0] = d->employee_name; values[1] = d->personal_no_masked; values[2] = d->period; values[3] = d->pay_date; values[4] = table; 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]); } } static void draw_amount_table(struct pdf *p, const struct payslip_data *d) { char bruttolon[48], skatt[48], netto[48]; fmt_kronor(d->gross_ore, bruttolon, sizeof bruttolon, 1); fmt_kronor(-d->tax_ore, skatt, sizeof skatt, 1); fmt_kronor(d->net_ore, netto, sizeof netto, 1); pdf_fill_rect(p, PAGE_LEFT, BAR_TABLE_Y, PAGE_RIGHT_X - PAGE_LEFT, BAR_TABLE_H, INK); text_at(p, AMOUNT_LABEL_X, TABLE_HEADER_Y, "HB", SIZE_VALUE, WHITE, "Specifikation"); text_right(p, AMOUNT_RIGHT, TABLE_HEADER_Y, "H", SIZE_VALUE, WHITE, d->run_ref); text_at(p, AMOUNT_LABEL_X, ROW_FIRST_Y, "H", SIZE_VALUE, INK, "Bruttolön"); text_right(p, AMOUNT_RIGHT, ROW_FIRST_Y, "H", SIZE_VALUE, INK, bruttolon); text_at(p, AMOUNT_LABEL_X, ROW_FIRST_Y + ROW_STEP, "H", SIZE_VALUE, INK, "Preliminärskatt"); text_right(p, AMOUNT_RIGHT, ROW_FIRST_Y + ROW_STEP, "H", SIZE_VALUE, INK, skatt); pdf_line(p, PAGE_LEFT, AMOUNT_RULE_Y, PAGE_RIGHT_X, AMOUNT_RULE_Y, RULE_W, RULE); text_at(p, AMOUNT_LABEL_X, ROW_FIRST_Y + 3 * ROW_STEP, "HB", SIZE_VALUE, INK, "Nettolön"); text_right(p, AMOUNT_RIGHT, ROW_FIRST_Y + 3 * ROW_STEP, "HB", SIZE_VALUE, INK, netto); { char note[96]; int64_t whole = d->rate_bp / 100; int64_t frac = d->rate_bp % 100; if (frac) snprintf(note, sizeof note, "Arbetsgivaravgifter %lld,%02lld %% betalas av" " arbetsgivaren.", (long long)whole, (long long)frac); else snprintf(note, sizeof note, "Arbetsgivaravgifter %lld %% betalas av arbetsgivaren.", (long long)whole); text_at(p, AMOUNT_LABEL_X, ROW_FIRST_Y + 4 * ROW_STEP + NOTE_DY, "H", SIZE_VALUE, INK, note); } } static void draw_footer(struct pdf *p, const struct payslip_data *d) { pdf_line(p, PAGE_LEFT, RULE_FOOTER_Y, PAGE_RIGHT_X, RULE_FOOTER_Y, RULE_W, RULE); text_at(p, AMOUNT_LABEL_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, FOOT_ORG_LABEL_X, FOOT_LABEL_Y, "HB", SIZE_FOOT, INK, "Orgnr"); text_at(p, AMOUNT_LABEL_X, FOOT_NAME_Y, "H", SIZE_VALUE, INK, d->employer.name); text_at(p, INFO_VALUE_X, FOOT_NAME_Y, "H", SIZE_VALUE, INK, d->employer.phone); text_at(p, AMOUNT_LABEL_X, FOOT_ADDR_Y, "H", SIZE_VALUE, INK, d->employer.address); text_at(p, INFO_VALUE_X, FOOT_EMAIL_Y, "H", SIZE_VALUE, INK, d->employer.email); put_address(p, AMOUNT_LABEL_X, FOOT_CITY_Y, INK, SIZE_VALUE, d->employer.postal_code, d->employer.city); text_at(p, FOOT_ORG_X, FOOT_ORG_Y, "H", SIZE_VALUE, INK, d->employer.org_nr); text_at(p, FOOT_PAGE_RIGHT, FOOT_PAGE_Y, "H", SIZE_PAGE, INK, "1"); } int payslip_render(const struct payslip_data *d, unsigned char **out, size_t *len) { struct pdf *p; if (out) *out = NULL; if (len) *len = 0; if (!d || !out || !len) return -1; 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); 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, RULE); draw_info(p, d); draw_amount_table(p, d); draw_footer(p, d); *out = pdf_finish(p, len); return *out ? 0 : -1; }