@ekwo-ai/invoice-pdf 0.0.0-stage → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +341 -2
  4. package/dist/archive.d.ts +29 -0
  5. package/dist/archive.d.ts.map +1 -0
  6. package/dist/archive.js +82 -0
  7. package/dist/archive.js.map +1 -0
  8. package/dist/errors.d.ts +23 -0
  9. package/dist/errors.d.ts.map +1 -0
  10. package/dist/errors.js +9 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/facturx.d.ts +39 -0
  13. package/dist/facturx.d.ts.map +1 -0
  14. package/dist/facturx.js +32 -0
  15. package/dist/facturx.js.map +1 -0
  16. package/dist/fonts/noto-sans-bold.d.ts +3 -0
  17. package/dist/fonts/noto-sans-bold.d.ts.map +1 -0
  18. package/dist/fonts/noto-sans-bold.js +848 -0
  19. package/dist/fonts/noto-sans-bold.js.map +1 -0
  20. package/dist/fonts/noto-sans-regular.d.ts +3 -0
  21. package/dist/fonts/noto-sans-regular.d.ts.map +1 -0
  22. package/dist/fonts/noto-sans-regular.js +845 -0
  23. package/dist/fonts/noto-sans-regular.js.map +1 -0
  24. package/dist/fonts/noto-sans-thai-bold.d.ts +3 -0
  25. package/dist/fonts/noto-sans-thai-bold.d.ts.map +1 -0
  26. package/dist/fonts/noto-sans-thai-bold.js +56 -0
  27. package/dist/fonts/noto-sans-thai-bold.js.map +1 -0
  28. package/dist/fonts/noto-sans-thai-regular.d.ts +3 -0
  29. package/dist/fonts/noto-sans-thai-regular.d.ts.map +1 -0
  30. package/dist/fonts/noto-sans-thai-regular.js +57 -0
  31. package/dist/fonts/noto-sans-thai-regular.js.map +1 -0
  32. package/dist/fonts/srgb-icc.d.ts +3 -0
  33. package/dist/fonts/srgb-icc.d.ts.map +1 -0
  34. package/dist/fonts/srgb-icc.js +6 -0
  35. package/dist/fonts/srgb-icc.js.map +1 -0
  36. package/dist/format.d.ts +27 -0
  37. package/dist/format.d.ts.map +1 -0
  38. package/dist/format.js +87 -0
  39. package/dist/format.js.map +1 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +6 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/labels.d.ts +14 -0
  45. package/dist/labels.d.ts.map +1 -0
  46. package/dist/labels.js +87 -0
  47. package/dist/labels.js.map +1 -0
  48. package/dist/logo.d.ts +15 -0
  49. package/dist/logo.d.ts.map +1 -0
  50. package/dist/logo.js +73 -0
  51. package/dist/logo.js.map +1 -0
  52. package/dist/render.d.ts +28 -0
  53. package/dist/render.d.ts.map +1 -0
  54. package/dist/render.js +659 -0
  55. package/dist/render.js.map +1 -0
  56. package/dist/text.d.ts +45 -0
  57. package/dist/text.d.ts.map +1 -0
  58. package/dist/text.js +191 -0
  59. package/dist/text.js.map +1 -0
  60. package/dist/theme.d.ts +25 -0
  61. package/dist/theme.d.ts.map +1 -0
  62. package/dist/theme.js +44 -0
  63. package/dist/theme.js.map +1 -0
  64. package/dist/types.d.ts +297 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +16 -0
  67. package/dist/types.js.map +1 -0
  68. package/licenses/CC0-sRGB-profile.txt +116 -0
  69. package/licenses/OFL-NotoSans.txt +93 -0
  70. package/licenses/OFL-NotoSansThai.txt +93 -0
  71. package/package.json +66 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+ - **The visual PDF of a sale invoice or credit note.** `renderInvoicePdf()`
11
+ takes the rows of `document_header`, `document_line_items`,
12
+ `document_tax_summary` and `document_legal_mentions`, and the bytes of a
13
+ logo, and returns `{ file, filename, title, pageCount, text }`: one sober
14
+ layout, A4 or Letter, as many pages as the lines need, the column headings
15
+ repeated and every page numbered. Amounts, dates, rates and country names
16
+ are written by `Intl` in the document's language; the words of the layout
17
+ are `ENGLISH_LABELS`, or the caller's.
18
+ - Noto Sans and Noto Sans Thai embedded, regular and bold, under the SIL Open
19
+ Font License, as subsets; other scripts through `options.fonts`. A character
20
+ no font has is refused (`glyph_not_covered`), text written right to left too
21
+ (`right_to_left_text`).
22
+ - `renderFacturXPdf()` hands the rendered PDF and a CII XML to `embedFacturX`
23
+ of `@ekwo-ai/factur-x/pdf`, passed in by the caller: a Factur-X PDF/A-3.
24
+ - **PDF/A-3b, measured with veraPDF.** The rendered PDF carries an XMP
25
+ packet declaring PDF/A-3b and mirroring its information dictionary; a PNG
26
+ logo's alpha channel is flattened on white, and a CMYK JPEG is refused.
27
+ veraPDF 1.30.3 finds no failed rule of PDF/A-3b in the examples, the
28
+ Factur-X one included (146 rules passed); `npm run verapdf` repeats the run
29
+ where veraPDF is installed. The trailer identifier, which a negative number
30
+ could write with a minus sign, is always hexadecimal digits.
31
+ - **A theme**, `options.theme`: an accent colour, a typeface (Noto Sans or the
32
+ caller's) and the side of the logo; `DEFAULT_THEME` is sober. The layout
33
+ takes from two earlier renderers the shaded panels of the parties, the band
34
+ of column headings, the framed payment block beside the totals, and the
35
+ logo on either side.
36
+ - The figures printed — totals, tax per rate, amount due — are tested equal
37
+ to those of the CII embedded beside them, read back out of the PDF, on an
38
+ invoice under two rates and on a credit note; the business terms of
39
+ EN 16931 the rows hold are tested present on the page.
40
+ - `examples/invoice-english-factur-x.pdf`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Karuna Co OÜ, trading as Ekwo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,342 @@
1
- # Temporary Holding Version
1
+ # @ekwo-ai/invoice-pdf
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The visual PDF of a sale invoice or credit note, in TypeScript, from the rows a
4
+ set of books publishes.
5
+
6
+ - **One sober layout**, A4 or Letter: the seller (its logo and its name), the
7
+ title, number, dates and references; the buyer and, where the goods went
8
+ elsewhere, the delivery address, on shaded panels; the lines, with the
9
+ column headings repeated on every page they run onto; the tax summary, one
10
+ row per tax, with the sentence that says why a group charges nothing; the
11
+ totals ending on what is still due, and beside them how to pay (terms, due
12
+ date, IBAN, BIC, reference) in a frame; the note; the legal mentions; and on
13
+ every page the seller's identity and the page number.
14
+ - **A small theme**: an accent colour, a typeface (Noto Sans or the caller's),
15
+ the logo on the left or the right — see [Theme](#theme).
16
+ - **Nothing country-specific in the layout.** The words it prints of its own
17
+ are labels, English by default and the caller's in any other language; the
18
+ sentences a country requires come in as rows; amounts, dates, rates and
19
+ country names are written by `Intl` in the document's language, with the
20
+ currency's own minor units.
21
+ - **Any script Noto Sans has**, embedded: Latin, Greek, Cyrillic, Thai; and
22
+ any other through a font the caller adds. Right to left is refused by name,
23
+ never drawn reversed.
24
+ - **PDF/A-3b, measured.** veraPDF finds no failed rule of PDF/A-3b in the
25
+ PDF it renders, nor in the Factur-X that `embedFacturX` of
26
+ [`@ekwo-ai/factur-x`](../factur-x/README.md) makes of it — see
27
+ [PDF/A-3](#pdfa-3) and [Factur-X](#factur-x).
28
+ - Pure TypeScript. Runs in Node, Deno and a browser. Reads no database, fetches
29
+ nothing, writes no file: rows and bytes in, bytes out.
30
+
31
+ Open [`examples/invoice-english.pdf`](examples/invoice-english.pdf),
32
+ [`examples/invoice-greek.pdf`](examples/invoice-greek.pdf) and
33
+ [`examples/invoice-english-factur-x.pdf`](examples/invoice-english-factur-x.pdf)
34
+ to see it; every name and number in them is invented. The Greek one passes only some of the
35
+ words in Greek, on purpose: the rest falls back to English, which is what a
36
+ caller who passes an incomplete set gets.
37
+
38
+ ## Install
39
+
40
+ ```sh
41
+ npm install @ekwo-ai/invoice-pdf
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```ts
47
+ import { renderInvoicePdf } from '@ekwo-ai/invoice-pdf';
48
+
49
+ const { file, filename, pageCount } = await renderInvoicePdf(
50
+ {
51
+ header, // one row of document_header
52
+ lines, // the rows of document_line_items, in their order
53
+ taxes, // the rows of document_tax_summary
54
+ mentions, // the rows of document_legal_mentions
55
+ logo: logoBytes, // PNG or JPEG bytes, or nothing
56
+ },
57
+ {
58
+ pageSize: 'A4', // or 'Letter'
59
+ labels: { invoice: 'Factura', amountDue: 'Importe pendiente' }, // the rest stays English
60
+ theme: { accent: '#7A1F2B', logoPosition: 'right' }, // or nothing: DEFAULT_THEME
61
+ },
62
+ );
63
+ // file: Uint8Array of a PDF; filename: 'invoice-INV-2026-0042.pdf'
64
+ ```
65
+
66
+ ## The input
67
+
68
+ What the books already publish, and nothing else: one field per column of
69
+ four views of [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os), declared in
70
+ [`src/types.ts`](src/types.ts) so that nothing is imported from it. Any
71
+ invoicing system that produces the same rows can use this package. A column
72
+ that is null is not printed; nothing is filled in for it.
73
+
74
+ | Field | View | What is printed from it |
75
+ |---|---|---|
76
+ | `header` | `document_header` | The seller (`seller_*`: name, legal name and form, address, VAT and registration numbers, e-mail, phone, website, electronic address, share capital), the buyer (`buyer_*`), the delivery address (`delivery_*`), the number, the dates (`document_date`, `due_date`, `delivery_date`, `tax_point_date`), the references, the totals (`amount_untaxed`, `amount_tax`, `amount_total`, `amount_paid`), what is still due (`amount_residual`), the payment (`payment_terms`, `payee_iban`, `payee_bic`, `payment_reference`), the `note`, the `currency_code` and the `language`. `doc_type` and `state` choose the title. |
77
+ | `lines` | `document_line_items` | `item_name`, `seller_item_identifier`, `item_description`, `quantity` and `unit_code`, `unit_price` (marked `*` where `unit_price_includes_tax`), `discount_percent`, `vat_rate`, `amount_untaxed`. A `section` line is a heading across the table, a `note` line a sentence across it. |
78
+ | `taxes` | `document_tax_summary` | `tax_name`, `tax_rate`, `base_amount`, `tax_charged` — what the customer pays — and `exemption_reason` under its group. Not `legal_reference`, written for whoever reviews a pack and not for a customer. |
79
+ | `mentions` | `document_legal_mentions` | `text`, already in the document's language and valid on its date, in the order of `sequence`. |
80
+ | `logo` | — | The bytes of the logo, PNG or JPEG. A PNG's transparency is flattened on white. `document_header.seller_logo_url` is never fetched here: the caller fetches it, or not. |
81
+
82
+ Figures arrive as the decimal text the database holds and are handed to
83
+ `Intl.NumberFormat` as text, which writes them digit for digit — never through
84
+ a JavaScript number. Nothing is computed: every total printed is a column.
85
+
86
+ ## Words and languages
87
+
88
+ `ENGLISH_LABELS` holds every word the layout prints of its own — "Invoice",
89
+ "Credit note", "Due date", "Amount due", "Page {page} of {pages}"… — and
90
+ `units` maps UN/ECE Recommendation 20 codes to what is printed after a
91
+ quantity (`DAY` → `days`, `C62` → nothing). English is the only language this
92
+ package carries. A document in another language gets its words from the
93
+ caller, `options.labels`, whole or in part; what is left out is English. A
94
+ country's own words — what its tax is called, what the law wants printed —
95
+ are not labels: they are `tax_name` and the mentions, which the books give in
96
+ the document's language.
97
+
98
+ The language amounts, dates and country names are written in is
99
+ `options.locale`, else the document's `language`, else `en`. A tag `Intl`
100
+ does not accept is refused (`invalid_language`).
101
+
102
+ ## Fonts
103
+
104
+ | Font | Covers | Licence |
105
+ |---|---|---|
106
+ | Noto Sans, Regular and Bold | Latin (with its extensions and Vietnamese), Greek, Cyrillic | SIL Open Font License 1.1 — [`licenses/OFL-NotoSans.txt`](licenses/OFL-NotoSans.txt) |
107
+ | Noto Sans Thai, Regular and Bold | Thai | SIL Open Font License 1.1 — [`licenses/OFL-NotoSansThai.txt`](licenses/OFL-NotoSansThai.txt) |
108
+
109
+ They are the hinted TrueType builds of the [Noto project](https://github.com/notofonts),
110
+ embedded in the package as base64 modules by
111
+ [`scripts/embed-fonts.mjs`](scripts/embed-fonts.mjs), which checks each file's
112
+ SHA-256. The OFL allows them to be bundled and embedded in documents; their
113
+ names are reserved, and they are not modified. In a PDF, each is embedded as
114
+ a subset holding only the glyphs drawn, and only when one of its characters is
115
+ drawn: a one-page invoice weighs about 30 kB.
116
+
117
+ Noto Sans was chosen because it is one design across the scripts a
118
+ customer's name is most often written in, and because the family extends to
119
+ nearly every other one — **Noto Sans CJK**, Devanagari, Hangul… — which a
120
+ caller adds through `options.fonts` (`{ regular, bold? }`, TrueType or
121
+ OpenType bytes) without the package growing by megabytes for everybody.
122
+
123
+ Each character is drawn by the first font that has it, embedded ones first, so
124
+ a line may mix scripts. A character no font has is refused,
125
+ `glyph_not_covered`, with its code point: an empty box where a name should be
126
+ is worse than no PDF.
127
+
128
+ **Right to left.** Hebrew, Arabic, Syriac, Thaana, N'Ko and the other scripts
129
+ of the right-to-left blocks of Unicode need the bidirectional algorithm and a
130
+ mirrored layout, which this package does not have. A string holding one —
131
+ in the rows or in the labels — is refused, `right_to_left_text`, naming the
132
+ field. It is never drawn in the wrong order.
133
+
134
+ The colour profile declared as the output intent is the compact sRGB profile
135
+ of [Compact-ICC-Profiles](https://github.com/saucecontrol/Compact-ICC-Profiles),
136
+ CC0 ([`licenses/CC0-sRGB-profile.txt`](licenses/CC0-sRGB-profile.txt)).
137
+
138
+ ## Theme
139
+
140
+ ```ts
141
+ import { DEFAULT_THEME, renderInvoicePdf } from '@ekwo-ai/invoice-pdf';
142
+
143
+ await renderInvoicePdf(input, { theme: { accent: '#7A1F2B', font: { regular, bold }, logoPosition: 'right' } });
144
+ // DEFAULT_THEME: { accent: '#1F3A5F', font: 'Noto Sans', logoPosition: 'left' }
145
+ ```
146
+
147
+ Three fields, all optional, and nothing else, on purpose: they are what a
148
+ customer's own invoice sets — its colour, its typeface, which side its logo is
149
+ on — and the hook for a theme read from one later.
150
+
151
+ | Field | What it changes |
152
+ |---|---|
153
+ | `accent` | `#rrggbb`. The title, the rule under the heading, the band of the column headings and of the amount due. The text on a band is white, or ink where the colour is light — whichever contrasts more. |
154
+ | `font` | `'Noto Sans'`, or the caller's TrueType or OpenType bytes, `{ regular, bold? }`, drawn first, with the embedded fonts for any character they lack. Embedded as a subset like the others. |
155
+ | `logoPosition` | `left`: the logo above the seller's address. `right`: above the title, the seller's name on the left. |
156
+
157
+ A colour that is not `#rrggbb` or a side that is neither is refused,
158
+ `invalid_value`. Nothing in the theme changes what is printed, only how.
159
+
160
+ ## The layout
161
+
162
+ The order of the page, top to bottom: the seller and the document's title,
163
+ number, dates and references side by side; a rule; the buyer and the delivery
164
+ address; the lines; the tax summary; the totals with, on their left, how to
165
+ pay; the note; the legal mentions; the foot.
166
+
167
+ ### What the layout keeps from earlier renderers
168
+
169
+ Two invoice renderers already in production elsewhere were read, section by
170
+ section, before this layout was settled. Both draw with the standard PDF
171
+ fonts without embedding them, which PDF/A forbids: neither is a strict
172
+ PDF/A-3. What was clearly better in them is kept; nothing of their code is.
173
+
174
+ | Section | An earlier renderer did | Here |
175
+ |---|---|---|
176
+ | Header | Put the logo top left, scaled within a box, the document's title, number and dates on the right; one offered the logo on the left, the right or the centre. | Kept: logo within 170 × 60 points, on the left or the right (`theme.logoPosition`); the centre was left out, as it collides with the title. |
177
+ | Header | Drew a separator line between the header and the customer. | Kept, in the accent colour. |
178
+ | Customer | Set the customer apart in a filled or bordered box (both renderers). | Kept: the buyer, and the delivery address beside it, on shaded panels. |
179
+ | Lines | Headed the table with a band of the theme's colour, in white. | Kept, with ink instead of white on a light colour. |
180
+ | Totals | Showed what was already paid right under the total, then what remains due, "so that it cannot be missed". | Already so here; the amount due is now a band of the accent colour. |
181
+ | Payment | Gathered the bank details — bank, holder, IBAN, BIC — in a framed box. | Kept, framed, beside the totals; the views carry no bank name or holder yet (see [EN 16931](#en-16931-on-the-page)). |
182
+ | Credit note | Ended a negative balance on "in your favour" rather than "to pay". | Already so here: a credit note ends on `amountCredited` and gives no payment instructions. |
183
+ | Theme | Several fixed themes, and one derived from the analysis of a customer's own invoice (colours, font style, title size, logo position, box styles). | Reduced to the three fields of [Theme](#theme); no gallery. |
184
+ | Lines | Added subtotals per section, zebra rows, a "paid" watermark. | Not kept: a subtotal would be computed here, and nothing printed is; zebra rows and a watermark add ink, not information. |
185
+
186
+ ## PDF/A-3
187
+
188
+ The PDF it renders declares itself PDF/A-3b, and is one by veraPDF's measure:
189
+
190
+ - every font embedded, as a subset, the theme's included;
191
+ - colours in DeviceRGB under an sRGB output intent;
192
+ - nothing transparent: a PNG logo's alpha channel is flattened on white
193
+ before it is embedded, and a CMYK JPEG is refused;
194
+ - an XMP packet that says what the document information dictionary says —
195
+ title, author, subject, creator, producer, creation and modification dates;
196
+ - the language in the catalogue, and an identifier in the trailer.
197
+
198
+ [veraPDF](https://verapdf.org) 1.30.3, profile PDF/A-3b (ISO 19005-3), on the
199
+ files of `examples/`, as rendered by `npm run examples`:
200
+
201
+ | File | Rules passed | Rules failed |
202
+ |---|---|---|
203
+ | `invoice-english.pdf` | 146 | 0 |
204
+ | `invoice-greek.pdf` | 146 | 0 |
205
+ | `invoice-english-factur-x.pdf` (with `embedFacturX`) | 146 | 0 |
206
+
207
+ Before this was measured, the two plain PDFs failed one rule (6.6.2.1: no XMP
208
+ metadata stream) and the Factur-X one passed with an XMP that did not mirror
209
+ the dictionary; a credit note rendered as Factur-X, run as a further case,
210
+ failed 6.1.6 on an identifier written with a minus sign. All three are fixed
211
+ and tested. Also run, and with no failed rule: a 120-line invoice on six
212
+ Letter pages, a Thai invoice, a logo with an alpha channel on the right with a
213
+ light accent colour, a caller's TrueType font as the theme's typeface.
214
+
215
+ veraPDF is Java and is not a dependency: install it, then
216
+
217
+ ```sh
218
+ VERAPDF=/path/to/verapdf npm run verapdf # the examples, PDF/A-3b
219
+ VERAPDF=/path/to/verapdf npm run verapdf -- --flavour 3b a.pdf b.pdf
220
+ ```
221
+
222
+ which prints the rules passed and failed for each file and exits 1 when one
223
+ fails. No test runs it, and a PDF made with caller fonts or logos this table
224
+ does not cover is the caller's to validate.
225
+
226
+ ## Factur-X
227
+
228
+ ```ts
229
+ import { renderFacturXPdf } from '@ekwo-ai/invoice-pdf';
230
+ import { embedFacturX } from '@ekwo-ai/factur-x/pdf';
231
+
232
+ const { file } = await renderFacturXPdf(input, { xml: ciiXml, profile: 'en16931', embed: embedFacturX });
233
+ ```
234
+
235
+ `renderFacturXPdf` renders the PDF and hands it, unchanged, with the CII XML
236
+ of the same document, to the `embed` function — `embedFacturX`, or anything of
237
+ its shape. This package does not import `@ekwo-ai/factur-x`: a brick depends
238
+ on no other brick.
239
+
240
+ The rendered PDF is already a PDF/A-3b; `embedFacturX` adds the XML and
241
+ replaces the XMP packet with its own, which declares the Factur-X extension
242
+ schema and is written from the same document information dictionary. veraPDF
243
+ finds no failed rule of PDF/A-3b in the result ([PDF/A-3](#pdfa-3)). The test
244
+ suite reads it back with `readFacturX` and gets the invoice, its profile and
245
+ no violation, and checks that the totals, the tax per rate and the amount due
246
+ printed on the page — read out of the PDF — are the figures of the CII inside
247
+ it, on an invoice under two rates and on a credit note.
248
+
249
+ ## What it refuses
250
+
251
+ An `InvoicePdfError` with a `code`, before anything is drawn wrong:
252
+
253
+ | Code | When |
254
+ |---|---|
255
+ | `not_a_sale_document` | A purchase, a quote, an order: this package renders what a seller issues. |
256
+ | `missing_field` | No currency, no date, no total, a tax group without its base or its tax. |
257
+ | `invalid_value` | A figure that is not a decimal, a currency that is not ISO 4217, a date that is not `YYYY-MM-DD`; a theme colour that is not `#rrggbb`, a logo position that is neither `left` nor `right`. |
258
+ | `invalid_language` | A language tag `Intl` does not accept. |
259
+ | `glyph_not_covered` | A character no font has. |
260
+ | `right_to_left_text` | A string written right to left. |
261
+ | `unsupported_logo`, `unsupported_font` | A logo that is not a PNG or a JPEG, or a CMYK JPEG; a font that cannot be read. |
262
+
263
+ A draft is rendered, titled as a draft and without a number; a cancelled
264
+ document says so under its title.
265
+
266
+ ## What it does not do
267
+
268
+ - **Keep the copy that was sent.** A posted document is reproducible from the
269
+ books ([decision 0015](../../../docs/decisions/0015-a-posted-document-is-frozen.md)),
270
+ but the copy a customer received is the one that counts, and it should be
271
+ kept as it was — an attachment of the document, with its checksum. Next.
272
+ - **Send it by e-mail.** Next, beside it.
273
+ - **The button in the hosted web application.** Next; it calls the same
274
+ function the command line and the MCP server call.
275
+ - **Read a supplier's PDF.** Receiving purchase invoices as PDF is another
276
+ brick; the Factur-X reader already reads the XML inside one.
277
+ - Right-to-left layouts, vertical text, other templates than this one: the
278
+ theme changes its colour, typeface and logo side, not its layout.
279
+ - Print what the views do not carry yet — see
280
+ [EN 16931 on the page](#en-16931-on-the-page).
281
+
282
+ ## EN 16931 on the page
283
+
284
+ Every business term of EN 16931 the rows hold is printed, and
285
+ [`test/en16931.test.ts`](test/en16931.test.ts) reads each one out of the PDF.
286
+ The terms the standard makes mandatory:
287
+
288
+ | Term | Printed as |
289
+ |---|---|
290
+ | BT-1, BT-2, BT-3 | The number, the date, the title (`Invoice`, `Credit note`). |
291
+ | BT-5 | The currency, in every amount, as `Intl` writes it (`€2,601.50`). |
292
+ | BT-27, BT-40 | The seller's name and country (with its legal name, address, VAT and registration numbers when the rows hold them). |
293
+ | BT-44, BT-55 | The buyer's name and country (with its address, VAT and registration numbers, e-mail, electronic address). |
294
+ | BT-109, BT-112, BT-115 | Total excluding tax, total, amount due — and BT-110, BT-113 when given. |
295
+ | BT-116, BT-117 | Each tax group's base and tax, with its rate (BT-119) and the reason it charges nothing (BT-120). |
296
+ | BT-129, BT-130, BT-131, BT-146, BT-153 | Each line's quantity and unit, net amount, price and name. The price is the one keyed: before the discount, printed beside it, and with the tax in it where it is marked `*`. |
297
+
298
+ Not printed, because they are codes written for a machine, which the CII of a
299
+ Factur-X carries and a reader of the page does not need: BT-24 (the
300
+ specification identifier), BT-81 (the payment means code — the IBAN says
301
+ transfer), BT-118 and BT-151 (the VAT category code — the tax's name says it),
302
+ BT-126 (the line identifier — the lines are printed in its order). BT-106, the
303
+ sum of the line amounts, is BT-109 as long as the views carry no allowance or
304
+ charge on the document, which they do not.
305
+
306
+ What the views do not carry yet, and the page therefore cannot print:
307
+
308
+ - **BT-25, BT-26**: the invoice a credit note credits, and its date (only
309
+ `documents.reversed_document_id` has it);
310
+ - **BT-45, BT-56, BT-57**: the buyer's trading name beside its legal name,
311
+ its contact person and its phone;
312
+ - **BT-76, BT-79, BT-70**: a second line and a region for the delivery
313
+ address, and the name of the party delivered to;
314
+ - **BT-85**: the holder of the payee's account — and its bank's name, for
315
+ which EN 16931 has no term; and an account that is not an IBAN (BT-84 is
316
+ read from `payee_iban` alone);
317
+ - the words of the layout in the document's language, which no pack holds.
318
+
319
+ ## In Ekwo OS
320
+
321
+ `ekwo doc pdf <document> [--factur-x] [--out <file>]` and the MCP tool
322
+ `render_invoice_pdf` read the four views as the person signed in, fetch the
323
+ logo the company names by URL (from a public http(s) address only), and call
324
+ this package — through `renderDocumentPdf()` of `@ekwo-ai/core`, which writes
325
+ the CII with the adapter of the `einvoicing` formats when Factur-X is asked
326
+ for.
327
+
328
+ ## Development
329
+
330
+ ```sh
331
+ npm install
332
+ npm run typecheck
333
+ npm test
334
+ npm run build
335
+ npm run examples # rewrites examples/*.pdf
336
+ npm run verapdf # needs veraPDF, see PDF/A-3
337
+ ```
338
+
339
+ ## License
340
+
341
+ [MIT](LICENSE) © Ekwo AI. The fonts and the colour profile it embeds keep
342
+ their own licences, in [`licenses/`](licenses).
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What PDF/A-3b asks of the file beyond what is drawn: a colour space saying
3
+ * what DeviceRGB means (the output intent), an identifier in the trailer, the
4
+ * language of the catalogue, and an XMP packet that declares the part and the
5
+ * conformance level and says what the document information dictionary says.
6
+ *
7
+ * Everything drawn is already within it: every font embedded, colours in
8
+ * DeviceRGB, no transparency (a logo's alpha channel is flattened on white
9
+ * before it is embedded). veraPDF's PDF/A-3b profile finds no failed rule in
10
+ * the examples of this package — see the README for the run.
11
+ */
12
+ import { type PDFDocument } from 'pdf-lib';
13
+ /** The entries of the document information dictionary, which the XMP repeats. */
14
+ export interface ArchiveInfo {
15
+ title: string;
16
+ author: string | null;
17
+ subject: string;
18
+ creator: string;
19
+ producer: string;
20
+ date: Date;
21
+ }
22
+ /** The XMP packet of a PDF/A-3b file whose information dictionary holds `info`. */
23
+ export declare function xmpPacket(info: ArchiveInfo): string;
24
+ /**
25
+ * Writes the information dictionary and its XMP twin, the sRGB output intent,
26
+ * the language and a stable identifier into `doc`.
27
+ */
28
+ export declare function prepareForArchive(doc: PDFDocument, info: ArchiveInfo, locale: string, seed: string): void;
29
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAoC,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAI7E,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAKD,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CA2BnD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAkCzG"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * What PDF/A-3b asks of the file beyond what is drawn: a colour space saying
3
+ * what DeviceRGB means (the output intent), an identifier in the trailer, the
4
+ * language of the catalogue, and an XMP packet that declares the part and the
5
+ * conformance level and says what the document information dictionary says.
6
+ *
7
+ * Everything drawn is already within it: every font embedded, colours in
8
+ * DeviceRGB, no transparency (a logo's alpha channel is flattened on white
9
+ * before it is embedded). veraPDF's PDF/A-3b profile finds no failed rule in
10
+ * the examples of this package — see the README for the run.
11
+ */
12
+ import { PDFHexString, PDFName, PDFString } from 'pdf-lib';
13
+ import SRGB_ICC from './fonts/srgb-icc.js';
14
+ import { decodeBase64 } from './text.js';
15
+ const escapeXml = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
16
+ /** The XMP packet of a PDF/A-3b file whose information dictionary holds `info`. */
17
+ export function xmpPacket(info) {
18
+ const date = info.date.toISOString();
19
+ const alt = (value) => `<rdf:Alt><rdf:li xml:lang="x-default">${escapeXml(value)}</rdf:li></rdf:Alt>`;
20
+ const author = info.author === null ? '' : `\n <dc:creator><rdf:Seq><rdf:li>${escapeXml(info.author)}</rdf:li></rdf:Seq></dc:creator>`;
21
+ return `<?xpacket begin="\u{feff}" id="W5M0MpCehiHzreSzNTczkc9d"?>
22
+ <x:xmpmeta xmlns:x="adobe:ns:meta/">
23
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
24
+ <rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">
25
+ <pdfaid:part>3</pdfaid:part>
26
+ <pdfaid:conformance>B</pdfaid:conformance>
27
+ </rdf:Description>
28
+ <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
29
+ <dc:format>application/pdf</dc:format>
30
+ <dc:title>${alt(info.title)}</dc:title>${author}
31
+ <dc:description>${alt(info.subject)}</dc:description>
32
+ </rdf:Description>
33
+ <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
34
+ <pdf:Producer>${escapeXml(info.producer)}</pdf:Producer>
35
+ </rdf:Description>
36
+ <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
37
+ <xmp:CreatorTool>${escapeXml(info.creator)}</xmp:CreatorTool>
38
+ <xmp:CreateDate>${date}</xmp:CreateDate>
39
+ <xmp:ModifyDate>${date}</xmp:ModifyDate>
40
+ </rdf:Description>
41
+ </rdf:RDF>
42
+ </x:xmpmeta>
43
+ <?xpacket end="w"?>`;
44
+ }
45
+ /**
46
+ * Writes the information dictionary and its XMP twin, the sRGB output intent,
47
+ * the language and a stable identifier into `doc`.
48
+ */
49
+ export function prepareForArchive(doc, info, locale, seed) {
50
+ doc.setTitle(info.title, { showInWindowTitleBar: true });
51
+ if (info.author !== null)
52
+ doc.setAuthor(info.author);
53
+ doc.setSubject(info.subject);
54
+ doc.setCreator(info.creator);
55
+ doc.setProducer(info.producer);
56
+ doc.setCreationDate(info.date);
57
+ doc.setModificationDate(info.date);
58
+ doc.setLanguage(locale);
59
+ const xmp = new TextEncoder().encode(xmpPacket(info));
60
+ const metadata = doc.context.stream(xmp, { Type: 'Metadata', Subtype: 'XML', Length: xmp.length });
61
+ doc.catalog.set(PDFName.of('Metadata'), doc.context.register(metadata));
62
+ const icc = decodeBase64(SRGB_ICC);
63
+ const profile = doc.context.register(doc.context.flateStream(icc, { N: 3 }));
64
+ const intent = doc.context.obj({
65
+ Type: 'OutputIntent',
66
+ S: 'GTS_PDFA1',
67
+ OutputConditionIdentifier: PDFString.of('sRGB IEC61966-2.1'),
68
+ Info: PDFString.of('sRGB IEC61966-2.1'),
69
+ DestOutputProfile: profile,
70
+ });
71
+ doc.catalog.set(PDFName.of('OutputIntents'), doc.context.obj([doc.context.register(intent)]));
72
+ // A stable identifier: the same document rendered twice is the same document.
73
+ let h1 = 0x811c9dc5;
74
+ let h2 = 0x01000193;
75
+ for (let i = 0; i < seed.length; i++) {
76
+ h1 = Math.imul(h1 ^ seed.charCodeAt(i), 0x01000193) >>> 0;
77
+ h2 = Math.imul(h2 ^ seed.charCodeAt(seed.length - 1 - i), 0x811c9dc5) >>> 0;
78
+ }
79
+ const id = [h1, h2, (h1 ^ h2) >>> 0, Math.imul(h1, 31) >>> 0].map((n) => n.toString(16).padStart(8, '0')).join('');
80
+ doc.context.trailerInfo.ID = doc.context.obj([PDFHexString.of(id), PDFHexString.of(id)]);
81
+ }
82
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /** Why a document could not be rendered. Each code is a refusal, never a guess. */
2
+ export type InvoicePdfErrorCode =
3
+ /** Not a sale invoice or a sale credit note. */
4
+ 'not_a_sale_document'
5
+ /** A field without which nothing honest can be printed: the currency, the date, an amount. */
6
+ | 'missing_field'
7
+ /** An amount or a rate that is not a decimal; a colour or a logo position of the theme that is not one. */
8
+ | 'invalid_value'
9
+ /** A language tag `Intl` does not accept. */
10
+ | 'invalid_language'
11
+ /** A character no embedded font and no font of `options.fonts` has. */
12
+ | 'glyph_not_covered'
13
+ /** Hebrew, Arabic and the other scripts written right to left: refused rather than drawn reversed. */
14
+ | 'right_to_left_text'
15
+ /** A logo that is neither PNG nor JPEG, that cannot be read as one, or a CMYK JPEG. */
16
+ | 'unsupported_logo'
17
+ /** A font of `options.fonts` or of the theme that cannot be read. */
18
+ | 'unsupported_font';
19
+ export declare class InvoicePdfError extends Error {
20
+ readonly code: InvoicePdfErrorCode;
21
+ constructor(code: InvoicePdfErrorCode, message: string);
22
+ }
23
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,MAAM,MAAM,mBAAmB;AAC7B,gDAAgD;AAC9C,qBAAqB;AACvB,8FAA8F;GAC5F,eAAe;AACjB,2GAA2G;GACzG,eAAe;AACjB,6CAA6C;GAC3C,kBAAkB;AACpB,uEAAuE;GACrE,mBAAmB;AACrB,sGAAsG;GACpG,oBAAoB;AACtB,uFAAuF;GACrF,kBAAkB;AACpB,qEAAqE;GACnE,kBAAkB,CAAC;AAEvB,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;gBACvB,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM;CAKvD"}
package/dist/errors.js ADDED
@@ -0,0 +1,9 @@
1
+ export class InvoicePdfError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(`${code}: ${message}`);
5
+ this.name = 'InvoicePdfError';
6
+ this.code = code;
7
+ }
8
+ }
9
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAmBA,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAsB;IACnC,YAAY,IAAyB,EAAE,OAAe;QACpD,KAAK,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The visual invoice, carrying its Factur-X XML.
3
+ *
4
+ * This package does not import `@ekwo-ai/factur-x`: a brick depends on no
5
+ * other brick, so that each can be taken alone. The caller hands over the
6
+ * function that does the embedding — `embedFacturX` of
7
+ * `@ekwo-ai/factur-x/pdf`, or any function of the same shape — and the CII XML
8
+ * it wrote for the same document. The PDF is rendered here, then given to it
9
+ * unchanged.
10
+ *
11
+ * The rendered PDF is already a PDF/A-3b — every font embedded as a subset,
12
+ * nothing transparent, DeviceRGB under an sRGB output intent, an identifier,
13
+ * an XMP packet that agrees with the information dictionary — and
14
+ * `embedFacturX` adds the XML and its own packet, written from the same
15
+ * dictionary. veraPDF finds no failed rule of PDF/A-3b in the result; the
16
+ * README records the run, which `npm run verapdf` repeats.
17
+ */
18
+ import type { InvoicePdfInput, InvoicePdfOptions, RenderedInvoicePdf } from './types.js';
19
+ /** The Factur-X profiles, under the names `@ekwo-ai/factur-x` gives them. */
20
+ export type FacturXProfile = 'minimum' | 'basic-wl' | 'basic' | 'en16931' | 'extended';
21
+ /** The shape of `embedFacturX` of `@ekwo-ai/factur-x/pdf`. */
22
+ export type EmbedFacturX = (pdf: Uint8Array, xml: string, options: {
23
+ profile?: FacturXProfile;
24
+ title?: string;
25
+ creator?: string;
26
+ producer?: string;
27
+ date?: Date;
28
+ }) => Promise<Uint8Array>;
29
+ export interface FacturXPdfOptions extends InvoicePdfOptions {
30
+ /** The CII XML of the same document. */
31
+ xml: string;
32
+ /** The guideline the XML was written to. Defaults to `en16931`. */
33
+ profile?: FacturXProfile;
34
+ /** `embedFacturX` of `@ekwo-ai/factur-x/pdf`. */
35
+ embed: EmbedFacturX;
36
+ }
37
+ /** Renders the PDF and hands it, with the XML, to `options.embed`. The result is a PDF/A-3 with its CII inside. */
38
+ export declare function renderFacturXPdf(input: InvoicePdfInput, options: FacturXPdfOptions): Promise<RenderedInvoicePdf>;
39
+ //# sourceMappingURL=facturx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facturx.d.ts","sourceRoot":"","sources":["../src/facturx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEzF,6EAA6E;AAC7E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvF,8DAA8D;AAC9D,MAAM,MAAM,YAAY,GAAG,CACzB,GAAG,EAAE,UAAU,EACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,KACpG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEzB,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,iDAAiD;IACjD,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,mHAAmH;AACnH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAWtH"}