Files
accounted/lib/pdf/number-text.ts
T
Jakob Wennberg f266c386f3 chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers (#2150)
* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers

Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n
namespaces and 4 unused dependencies; fold byte-identical helper copies
into one canonical home each (lib/utils chunk/sleep/utcDateStamp,
lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format,
lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body +
v1ValidationError rolled out to ~55 v1 routes, booking-template schemas).

No behaviour change: v1 bodies and status codes, MCP tool schemas, DB
writes and money math are untouched. Naive ore rounding was deliberately
not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list
of things left alone on purpose.

tsc, lint, 19588 unit tests and check:guards green; antipattern baseline
ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(transactions): import RawTransaction from @/types after the ingest re-export removal

CI's type ratchet (check:types, full tsconfig) caught the one test file
that still imported the type through lib/transactions/ingest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 11:51:16 +02:00

61 lines
2.3 KiB
TypeScript

/**
* Sign guard for numbers rendered with @react-pdf/renderer.
*
* `Intl.NumberFormat('sv-SE')` and `toLocaleString('sv-SE')` write negatives
* with U+2212 MINUS SIGN, not the ASCII hyphen-minus. The bundled standard
* PDF fonts (Helvetica, Courier, Times: WinAnsi encoding) have no glyph for
* U+2212, so react-pdf emits an unmapped byte and the viewer draws nothing:
* a loss prints as a profit, a credit note as a charge (issue #1982, first
* seen on Årets resultat in the årsredovisning PDF).
*
* Run every locale-formatted amount through this before it reaches a <Text>
* that uses a standard font. Embedded TrueType fonts do carry the glyph, but
* the ASCII hyphen renders identically there, so applying it everywhere is
* safe.
*/
/** Built via fromCharCode so no editor or transport can normalise it away. */
export const UNICODE_MINUS = String.fromCharCode(0x2212)
/** "-0", "-0,00", "-0.00": a negated zero or sub-öre rounding noise. */
const NEGATIVE_ZERO = /^-0(?:[.,]0+)?$/
/**
* Locale-formatted number text made safe for a standard PDF font: U+2212
* becomes the ASCII hyphen-minus, and a negative zero (which the dropped
* glyph used to hide) prints unsigned, as it does on screen.
*/
export function pdfNumberText(text: string): string {
const ascii = text.replaceAll(UNICODE_MINUS, '-')
return NEGATIVE_ZERO.test(ascii) ? ascii.slice(1) : ascii
}
/**
* Free text (a formula, a label) made safe for a standard PDF font: U+2212
* becomes the ASCII hyphen-minus. Unlike pdfNumberText it never touches a
* leading "-0", because the text is not a single number.
*/
export function pdfText(text: string): string {
return text.replaceAll(UNICODE_MINUS, '-')
}
/**
* Two-decimal sv-SE amount for a react-pdf <Text>: the same Intl call as
* lib/utils formatAmount, run through pdfNumberText so a negative årets
* resultat or a credit note never prints unsigned (issue #1982).
*/
export function pdfAmount(amount: number): string {
return pdfNumberText(
new Intl.NumberFormat('sv-SE', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount),
)
}
/** ISO date string as sv-SE (YYYY-MM-DD in the server timezone); empty input stays empty. */
export function formatDateSv(iso: string): string {
if (!iso) return ''
return new Date(iso).toLocaleDateString('sv-SE')
}