@cenk1cenk2/md-printer 3.2.24 → 3.3.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 (46) hide show
  1. package/README.md +33 -1
  2. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorate.mjs +1 -1
  3. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateMetadata.mjs +1 -1
  4. package/dist/_virtual/{_@oxc-project_runtime@0.156.0 → _@oxc-project_runtime@0.157.0}/helpers/esm/decorateParam.mjs +1 -1
  5. package/dist/commands/index.mjs +1 -1
  6. package/dist/constants/attachment.constants.mjs +7 -0
  7. package/dist/constants/index.mjs +3 -2
  8. package/dist/constants/template.constants.mjs +3 -2
  9. package/dist/interfaces/attachment.interface.mjs +1 -0
  10. package/dist/modules/md-printer/md-printer.handler.mjs +2 -2
  11. package/dist/modules/md-printer/md-printer.module.mjs +1 -1
  12. package/dist/services/i18n.service.mjs +2 -2
  13. package/dist/services/index.mjs +2 -1
  14. package/dist/services/markdown.service.mjs +1 -1
  15. package/dist/services/pdf/pdf.service.mjs +124 -0
  16. package/dist/services/printer.module.mjs +3 -1
  17. package/dist/services/printer.service.mjs +33 -6
  18. package/dist/services/renderer/browser.service.mjs +3 -3
  19. package/dist/services/renderer/renderer.service.mjs +3 -3
  20. package/dist/services/session/preview.service.mjs +2 -2
  21. package/dist/services/session/session.service.mjs +2 -2
  22. package/dist/services/signature/adapters/a-trust.adapter.mjs +2 -2
  23. package/dist/services/signature/signature.service.mjs +2 -2
  24. package/dist/services/template/assets.mjs +1 -1
  25. package/dist/services/template/render.entry.ts +1 -1
  26. package/dist/services/template/template.service.mjs +20 -5
  27. package/oclif.manifest.json +1 -1
  28. package/package.json +13 -23
  29. package/templates/default/Template.svelte +0 -83
  30. package/templates/default/settings.json +0 -7
  31. package/templates/invoice/Template.svelte +0 -247
  32. package/templates/invoice/components/Client.svelte +0 -42
  33. package/templates/invoice/components/Company.svelte +0 -41
  34. package/templates/invoice/components/Items.svelte +0 -66
  35. package/templates/invoice/components/Notes.svelte +0 -21
  36. package/templates/invoice/components/Payment.svelte +0 -25
  37. package/templates/invoice/components/Signature.svelte +0 -34
  38. package/templates/invoice/components/Totals.svelte +0 -47
  39. package/templates/invoice/i18n/de.yaml +0 -30
  40. package/templates/invoice/i18n/en.yaml +0 -30
  41. package/templates/invoice/round.js +0 -2
  42. package/templates/invoice/settings.json +0 -11
  43. package/templates/kilic.dev/Template.svelte +0 -102
  44. package/templates/kilic.dev/settings.json +0 -7
  45. package/templates/mail/Template.svelte +0 -166
  46. package/templates/mail/settings.json +0 -7
package/README.md CHANGED
@@ -100,6 +100,8 @@ Markdown files take YAML front-matter. For YAML input the same keys are top-leve
100
100
  | `documentTitle` | Document title. |
101
101
  | `pdfOptions` | Puppeteer [PDF options](https://pptr.dev/api/puppeteer.pdfoptions), merged over the defaults. |
102
102
  | `sign` | Signing options, see [Signing](#signing). |
103
+ | `attachments` | PDFs merged into the output, see [Attachments and compression](#attachments-and-compression). |
104
+ | `compress` | `true` compresses the images of the whole output PDF, attachments included. |
103
105
 
104
106
  A template may carry a `settings.json` with defaults for its documents:
105
107
 
@@ -133,6 +135,29 @@ pdfOptions:
133
135
  - `continuous` is enabled when any of `--continuous`, front-matter or `settings.json` enables it.
134
136
  - The document title is `--title`, then `documentTitle`, then the `<title>` set by the template, then the input file name.
135
137
 
138
+ ## Attachments and compression
139
+
140
+ PDF output can carry other PDFs, such as certificates or reference letters, and can be compressed.
141
+
142
+ ```yaml
143
+ ---
144
+ compress: true
145
+ attachments:
146
+ - file: ./assets/references/diploma.pdf
147
+ position: before
148
+ - file: https://example.com/certificate.pdf
149
+ position: 2
150
+ compress: true
151
+ - file: ./assets/references/letter.pdf
152
+ ---
153
+ ```
154
+
155
+ - `file` is a path relative to the input file, confined to its directory like every other local asset, or an `http(s)` URL.
156
+ - `position` is `before` the document, `after` it (the default) or a page number: the attachment starts at that page of the rendered document, so `2` places it between the first and the second page, and the page after the last one appends it. Page numbers always count pages of the rendered document, not of other attachments, and attachments sharing a position keep the order they are listed in.
157
+ - `compress: true` on an attachment re-encodes its images, `compress: true` at the top level does the same for the whole output, attachments included. Images are scaled down to at most 2000px and stored as JPEG at quality 75 whenever that makes them smaller; masks and images with transparency keep their lossless encoding.
158
+ - Attachments and compression are applied before signing, so a signature covers them. They are only supported for PDF output.
159
+ - `--preview` rebuilds when a local attachment changes.
160
+
136
161
  ## Development
137
162
 
138
163
  ```sh
@@ -152,11 +177,14 @@ md-printer document.md out.pdf --preview --no-open
152
177
 
153
178
  ### Built-in templates
154
179
 
180
+ Every built-in template is a package of its own, `@cenk1cenk2/md-printer-template-<name>`, published from the `templates/` directory of this repository. md-printer depends on all of them, so they install with it, and each one brings the fonts, icons and stylesheets it uses as its own dependencies.
181
+
155
182
  | Name | Description |
156
183
  | ----------- | -------------------------------------------------------------------------------------------------------------- |
157
184
  | `default` | GitHub-flavoured Markdown styling with Montserrat. This is the default. |
158
185
  | `kilic.dev` | Same styling with a logo in the header and a footer with title and page numbers. |
159
186
  | `invoice` | Invoice from YAML data (`company`, `client`, `payment`, `items`, `currency`, `date`, `id`) and Markdown notes. Translations in `en` and `de`, a signature block when signing. See `test/invoice.md`. |
187
+ | `cv` | CV in the table layout of a classic Word CV, from front-matter (`name`, `suffix`, `title`, `tagline`, `photo`) and Markdown sections placed with the `::section`, `:::details` / `::detail` / `:::pair`, `:::entry`, `::::group` / `:::course`, `::skill`, `:::note` and `:::languages` / `::language` components. The PDF is tagged and has an outline, so screening tools read it as text. See `test/cv.md`. |
160
188
  | `mail` | Plain content with no margin and Tailwind typography, meant for HTML output. |
161
189
 
162
190
  ### Authoring a template
@@ -183,7 +211,11 @@ my-template/
183
211
 
184
212
  Set the document title with `<svelte:head><title>...</title></svelte:head>`.
185
213
 
186
- Styles that need Tailwind CSS v4 or package imports go into a `<style lang="postcss">` block in `Template.svelte` or any component it imports. These blocks are collected and compiled together, so they can `@import 'tailwindcss'` and use `@plugin`, `@theme` and utility classes. Plain `<style>` blocks stay scoped Svelte styles. `@import` of packages (for example `@fontsource/*`, `github-markdown-css`) resolves from the template directory first and also from md-printer's own dependencies, so the template does not need a `node_modules`. The selected highlight.js theme is always included. Local `url()` and image references are inlined as data URIs.
214
+ Components may be written in TypeScript with `<script lang="ts">`, and `.ts` modules can be imported, as `./round.js` or `./round.ts`. Svelte strips the types of components, so only type annotations are allowed there, no enums or other TypeScript that emits code; `.ts` modules take full TypeScript. Types are not checked while rendering. The built-in templates are written this way.
215
+
216
+ A template can bring its own dependencies: give the template directory a `package.json` and install it there, as the built-in templates do. Imports in components, `.ts` modules, `@import` in `<style lang="postcss">` and imported images such as `@fortawesome/fontawesome-free/svgs/solid/user.svg` resolve from the template's `node_modules` first and fall back to md-printer's own dependencies. md-printer does not install them for you; a missing one fails the build with the directory to install it in.
217
+
218
+ Styles that need Tailwind CSS v4 or package imports go into a `<style lang="postcss">` block in `Template.svelte` or any component it imports. These blocks are collected and compiled together, so they can `@import 'tailwindcss'` and use `@plugin`, `@theme` and utility classes. Plain `<style>` blocks stay scoped Svelte styles. `@import` of packages (for example `@fontsource/*`, `github-markdown-css`) resolves from the template directory first and then from md-printer's own dependencies, which cover `tailwindcss` and the highlight.js themes. The selected highlight.js theme is always included. Local `url()` and image references are inlined as data URIs.
187
219
 
188
220
  Images are imported, which inlines them as data URIs:
189
221
 
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.156.0/helpers/esm/decorate.js
1
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/decorate.js
2
2
  function __decorate(decorators, target, key, desc) {
3
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.156.0/helpers/esm/decorateMetadata.js
1
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/decorateMetadata.js
2
2
  function __decorateMetadata(k, v) {
3
3
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
4
4
  }
@@ -1,4 +1,4 @@
1
- // \0@oxc-project+runtime@0.156.0/helpers/esm/decorateParam.js
1
+ // \0@oxc-project+runtime@0.157.0/helpers/esm/decorateParam.js
2
2
  function __decorateParam(paramIndex, decorator) {
3
3
  return function(target, key) {
4
4
  decorator(target, key, paramIndex);
@@ -1,7 +1,7 @@
1
1
  import { MdPrinterHandler } from "../modules/md-printer/md-printer.handler.mjs";
2
2
  import { MdPrinterModule } from "../modules/md-printer/md-printer.module.mjs";
3
3
  import { MdPrinterSchema } from "../modules/md-printer/md-printer.schema.mjs";
4
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
4
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Args, Command, Flags, UseHandler } from "@cenk1cenk2/oclif-common";
6
6
  // src/commands/index.ts
7
7
  let MDPrinter = class MDPrinter extends Command {
@@ -0,0 +1,7 @@
1
+ // src/constants/attachment.constants.ts
2
+ let AttachmentPosition = /* @__PURE__ */ function(AttachmentPosition) {
3
+ AttachmentPosition["BEFORE"] = "before";
4
+ AttachmentPosition["AFTER"] = "after";
5
+ return AttachmentPosition;
6
+ }({});
7
+ export { AttachmentPosition };
@@ -1,5 +1,6 @@
1
- import { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TemplateFiles } from "./template.constants.mjs";
1
+ import { AttachmentPosition } from "./attachment.constants.mjs";
2
+ import { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_PACKAGE_PREFIX, TemplateFiles } from "./template.constants.mjs";
2
3
  import { InputFileType, OutputFileType } from "./file.constants.mjs";
3
4
  import { PreviewHost } from "./preview.constants.mjs";
4
5
  import { SignatureProvider } from "./signature.constants.mjs";
5
- export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, InputFileType, MediaType, ModuleSpecifier, OutputFileType, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, PreviewHost, RenderMode, SignatureProvider, TAILWIND_STYLE_LANG, TemplateFiles };
6
+ export { AttachmentPosition, ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, InputFileType, MediaType, ModuleSpecifier, OutputFileType, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, PreviewHost, RenderMode, SignatureProvider, TAILWIND_STYLE_LANG, TEMPLATE_PACKAGE_PREFIX, TemplateFiles };
@@ -15,8 +15,9 @@ let MediaType = /* @__PURE__ */ function(MediaType) {
15
15
  MediaType["PRINT"] = "print";
16
16
  return MediaType;
17
17
  }({});
18
+ /** Built-in templates are packages named with this prefix, which md-printer depends on. */
19
+ const TEMPLATE_PACKAGE_PREFIX = "@cenk1cenk2/md-printer-template-";
18
20
  let Directory = /* @__PURE__ */ function(Directory) {
19
- Directory["TEMPLATES"] = "templates";
20
21
  Directory["COMPONENTS"] = "components";
21
22
  Directory["I18N"] = "i18n";
22
23
  return Directory;
@@ -56,4 +57,4 @@ const DEFAULT_PDF_OPTIONS = {
56
57
  left: "20mm"
57
58
  }
58
59
  };
59
- export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TemplateFiles };
60
+ export { ContextKey, DEFAULT_HIGHLIGHT_STYLE, DEFAULT_LANGUAGE, DEFAULT_PDF_OPTIONS, DEFAULT_TEMPLATE, DIRECTIVE_TAG, Directory, I18N_ACCEPTED_TYPES, MediaType, ModuleSpecifier, PAGE_BREAK_CLASS, PRINTER_MODULE_OPTIONS, RenderMode, TAILWIND_STYLE_LANG, TEMPLATE_PACKAGE_PREFIX, TemplateFiles };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,10 @@
1
1
  import "../../services/index.mjs";
2
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
2
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
3
3
  import { BrowserService } from "../../services/renderer/browser.service.mjs";
4
4
  import { PrinterService } from "../../services/printer.service.mjs";
5
5
  import { PreviewService } from "../../services/session/preview.service.mjs";
6
6
  import { SessionService } from "../../services/session/session.service.mjs";
7
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
7
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
8
8
  import { Injectable } from "@nestjs/common";
9
9
  import { mkdir } from "fs/promises";
10
10
  import { dirname } from "path";
@@ -1,7 +1,7 @@
1
1
  import { MdPrinterHandler } from "./md-printer.handler.mjs";
2
2
  import { PrinterModule } from "../../services/printer.module.mjs";
3
3
  import "../../services/index.mjs";
4
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Module } from "@nestjs/common";
6
6
  import { CommandContext, LogLevels, LoggerModule, LogoModule } from "@cenk1cenk2/oclif-common";
7
7
  // src/modules/md-printer/md-printer.module.ts
@@ -1,6 +1,6 @@
1
1
  import { I18N_ACCEPTED_TYPES } from "../constants/template.constants.mjs";
2
- import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
3
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
2
+ import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
3
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
4
4
  import { Injectable } from "@nestjs/common";
5
5
  import { readdir } from "fs/promises";
6
6
  import { basename, extname, join } from "path";
@@ -1,5 +1,6 @@
1
1
  import { I18nService } from "./i18n.service.mjs";
2
2
  import { MarkdownService } from "./markdown.service.mjs";
3
+ import { PdfService } from "./pdf/pdf.service.mjs";
3
4
  import { PrinterModule } from "./printer.module.mjs";
4
5
  import { PrinterService } from "./printer.service.mjs";
5
6
  import { IncompleteSignatureError, SignatureVerificationError, parseSignedData } from "./signature/cms.mjs";
@@ -12,4 +13,4 @@ import { PreviewService } from "./session/preview.service.mjs";
12
13
  import { SessionService } from "./session/session.service.mjs";
13
14
  import "./signature/index.mjs";
14
15
  import { TemplateService } from "./template/template.service.mjs";
15
- export { ATrustAdapter, BrowserService, I18nService, IncompleteSignatureError, MarkdownService, PreviewService, PrinterModule, PrinterService, RendererService, SessionService, SignatureAdapter, SignatureService, SignatureVerificationError, TemplateService, parseSignedData };
16
+ export { ATrustAdapter, BrowserService, I18nService, IncompleteSignatureError, MarkdownService, PdfService, PreviewService, PrinterModule, PrinterService, RendererService, SessionService, SignatureAdapter, SignatureService, SignatureVerificationError, TemplateService, parseSignedData };
@@ -1,5 +1,5 @@
1
1
  import { DIRECTIVE_TAG, PAGE_BREAK_CLASS } from "../constants/template.constants.mjs";
2
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
2
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
3
3
  import { Injectable } from "@nestjs/common";
4
4
  import { toHtml } from "hast-util-to-html";
5
5
  import { all } from "lowlight";
@@ -0,0 +1,124 @@
1
+ import { resolveConfined } from "../template/assets.mjs";
2
+ import "../../constants/attachment.constants.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
+ import { Injectable } from "@nestjs/common";
6
+ import { readFile } from "fs/promises";
7
+ import { resolve } from "path";
8
+ import { LoggerService } from "@cenk1cenk2/oclif-common";
9
+ import * as mupdf from "mupdf";
10
+ // src/services/pdf/pdf.service.ts
11
+ var _PdfService;
12
+ let PdfService = class PdfService {
13
+ static {
14
+ _PdfService = this;
15
+ }
16
+ logger;
17
+ static IMAGE_MAX_DIMENSION = 2e3;
18
+ static JPEG_QUALITY = 75;
19
+ constructor(logger) {
20
+ this.logger = logger;
21
+ this.logger.setup(this.constructor.name);
22
+ }
23
+ async process(pdf, options) {
24
+ if (!options.attachments.length && !options.compress) return {
25
+ pdf,
26
+ assets: []
27
+ };
28
+ const document = new mupdf.PDFDocument(pdf);
29
+ const pages = document.countPages();
30
+ const assets = [];
31
+ const loaded = await Promise.all(options.attachments.map(async (attachment) => {
32
+ const { content, path } = await this.load(attachment.file, options.directory);
33
+ if (path) assets.push(path);
34
+ return {
35
+ attachment,
36
+ document: this.open(attachment.file, content),
37
+ anchor: this.anchor(attachment, pages)
38
+ };
39
+ }));
40
+ for (const { attachment, document: source, anchor } of loaded.map((item, index) => ({
41
+ ...item,
42
+ index
43
+ })).sort((a, b) => b.anchor - a.anchor || b.index - a.index)) {
44
+ if (attachment.compress && !options.compress) this.compress(source);
45
+ for (let page = 0; page < source.countPages(); page++) document.graftPage(anchor + page, source, page);
46
+ this.logger.debug("Attached %s with %d pages at page %d.", attachment.file, source.countPages(), anchor + 1);
47
+ }
48
+ if (options.compress) this.compress(document);
49
+ const output = Buffer.from(document.saveToBuffer(options.compress ? "garbage=compact,compress,compress-fonts" : "garbage=compact,compress").asUint8Array());
50
+ this.logger.debug("Processed PDF: %d bytes to %d bytes.", pdf.length, output.length);
51
+ return {
52
+ pdf: output,
53
+ assets
54
+ };
55
+ }
56
+ anchor(attachment, pages) {
57
+ if (attachment.position === "before") return 0;
58
+ if (typeof attachment.position !== "number") return pages;
59
+ if (attachment.position > pages + 1) throw new Error(`Attachment ${attachment.file} is placed at page ${attachment.position}, but the document has ${pages} pages.`);
60
+ return attachment.position - 1;
61
+ }
62
+ async load(file, directory) {
63
+ if (/^https?:\/\//.test(file)) {
64
+ const response = await fetch(file);
65
+ if (!response.ok) throw new Error(`Attachment can not be fetched: ${file}, ${response.status} ${response.statusText}`);
66
+ return { content: new Uint8Array(await response.arrayBuffer()) };
67
+ }
68
+ const path = await resolveConfined(resolve(directory, file), directory);
69
+ if (!path) throw new Error(`Attachment does not exist or is outside of the input directory: ${file}`);
70
+ return {
71
+ content: await readFile(path),
72
+ path
73
+ };
74
+ }
75
+ open(file, content) {
76
+ if (Buffer.from(content.subarray(0, 5)).toString() !== "%PDF-") throw new Error(`Attachment is not a PDF: ${file}`);
77
+ return new mupdf.PDFDocument(content);
78
+ }
79
+ /**
80
+ * Re-encodes the images of a document as JPEG, scaled down to fit a printed page, whenever that makes them smaller.
81
+ * Soft masks, stencil masks and images below 8 bits per component keep their lossless encoding.
82
+ */
83
+ compress(document) {
84
+ const images = [];
85
+ const masks = /* @__PURE__ */ new Set();
86
+ for (let number = 1; number < document.countObjects(); number++) {
87
+ const object = document.newIndirect(number);
88
+ if (!object.isStream()) continue;
89
+ const subtype = object.get("Subtype");
90
+ if (!subtype.isName() || subtype.asName() !== "Image") continue;
91
+ const mask = object.get("SMask");
92
+ if (mask.isIndirect()) masks.add(mask.asIndirect());
93
+ images.push(object);
94
+ }
95
+ for (const object of images) {
96
+ if (masks.has(object.asIndirect()) || object.get("ImageMask").valueOf() === true) continue;
97
+ const image = document.loadImage(object);
98
+ if (image.getBitsPerComponent() < 8) continue;
99
+ let pixmap = image.toPixmap();
100
+ if (pixmap.getAlpha()) continue;
101
+ if (!pixmap.getColorSpace().isGray() && !pixmap.getColorSpace().isRGB()) pixmap = pixmap.convertToColorSpace(mupdf.ColorSpace.DeviceRGB);
102
+ const [width, height] = [pixmap.getWidth(), pixmap.getHeight()];
103
+ const scale = Math.min(1, _PdfService.IMAGE_MAX_DIMENSION / Math.max(width, height));
104
+ if (scale < 1) pixmap = pixmap.warp([
105
+ [0, 0],
106
+ [width, 0],
107
+ [width, height],
108
+ [0, height]
109
+ ], Math.round(width * scale), Math.round(height * scale));
110
+ const jpeg = pixmap.asJPEG(_PdfService.JPEG_QUALITY);
111
+ if (jpeg.length >= object.readRawStream().length) continue;
112
+ object.writeRawStream(jpeg);
113
+ object.put("Filter", document.newName("DCTDecode"));
114
+ object.delete("DecodeParms");
115
+ object.delete("Decode");
116
+ object.put("Width", pixmap.getWidth());
117
+ object.put("Height", pixmap.getHeight());
118
+ object.put("BitsPerComponent", 8);
119
+ object.put("ColorSpace", document.newName(pixmap.getColorSpace().isGray() ? "DeviceGray" : "DeviceRGB"));
120
+ }
121
+ }
122
+ };
123
+ PdfService = _PdfService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService])], PdfService);
124
+ export { PdfService };
@@ -1,5 +1,6 @@
1
1
  import { I18nService } from "./i18n.service.mjs";
2
2
  import { MarkdownService } from "./markdown.service.mjs";
3
+ import { PdfService } from "./pdf/pdf.service.mjs";
3
4
  import { PrinterService } from "./printer.service.mjs";
4
5
  import { BrowserService } from "./renderer/browser.service.mjs";
5
6
  import { RendererService } from "./renderer/renderer.service.mjs";
@@ -9,7 +10,7 @@ import { ATrustAdapter } from "./signature/adapters/a-trust.adapter.mjs";
9
10
  import { SignatureService } from "./signature/signature.service.mjs";
10
11
  import { TemplateService } from "./template/template.service.mjs";
11
12
  import { PRINTER_MODULE_OPTIONS } from "../constants/template.constants.mjs";
12
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
13
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
13
14
  import { ConfigurableModuleBuilder, Module } from "@nestjs/common";
14
15
  import { FilesystemModule, JsonParser, ParserModule, YamlParser } from "@cenk1cenk2/oclif-common";
15
16
  // src/services/printer.module.ts
@@ -22,6 +23,7 @@ PrinterModule = __decorate([Module({
22
23
  TemplateService,
23
24
  BrowserService,
24
25
  RendererService,
26
+ PdfService,
25
27
  ATrustAdapter,
26
28
  SignatureService,
27
29
  PrinterService,
@@ -1,14 +1,16 @@
1
1
  import { I18nService } from "./i18n.service.mjs";
2
2
  import { MarkdownService } from "./markdown.service.mjs";
3
+ import { PdfService } from "./pdf/pdf.service.mjs";
3
4
  import { RendererService } from "./renderer/renderer.service.mjs";
4
5
  import { SignatureVerificationError } from "./signature/cms.mjs";
5
6
  import { SignatureService } from "./signature/signature.service.mjs";
6
7
  import { inlineHtml } from "./template/assets.mjs";
7
8
  import { TemplateService } from "./template/template.service.mjs";
9
+ import { AttachmentPosition } from "../constants/attachment.constants.mjs";
8
10
  import { DEFAULT_PDF_OPTIONS } from "../constants/template.constants.mjs";
9
11
  import { OutputFileType } from "../constants/file.constants.mjs";
10
- import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
11
- import __decorate from "../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
12
+ import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
13
+ import __decorate from "../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
12
14
  import { Injectable } from "@nestjs/common";
13
15
  import { basename, dirname, extname, join, resolve } from "path";
14
16
  import { ConfigService, FileSystemService, LoggerService } from "@cenk1cenk2/oclif-common";
@@ -23,8 +25,9 @@ let PrinterService = class PrinterService {
23
25
  markdown;
24
26
  template;
25
27
  renderer;
28
+ pdf;
26
29
  signature;
27
- constructor(fs, logger, config, i18n, markdown, template, renderer, signature) {
30
+ constructor(fs, logger, config, i18n, markdown, template, renderer, pdf, signature) {
28
31
  this.fs = fs;
29
32
  this.logger = logger;
30
33
  this.config = config;
@@ -32,6 +35,7 @@ let PrinterService = class PrinterService {
32
35
  this.markdown = markdown;
33
36
  this.template = template;
34
37
  this.renderer = renderer;
38
+ this.pdf = pdf;
35
39
  this.signature = signature;
36
40
  this.logger.setup(this.constructor.name);
37
41
  }
@@ -77,7 +81,9 @@ let PrinterService = class PrinterService {
77
81
  highlight: settings.highlightStyle ?? "atom-one-dark",
78
82
  pdf: this.pdfOptions(settings.pdfOptions, data.pdfOptions),
79
83
  i18n: settings.i18n ?? {},
80
- sign: this.signOptions(flags, settings.sign, data.sign)
84
+ sign: this.signOptions(flags, settings.sign, data.sign),
85
+ attachments: this.attachments(data.attachments),
86
+ compress: data.compress === true
81
87
  };
82
88
  }
83
89
  bundle(options) {
@@ -115,6 +121,7 @@ let PrinterService = class PrinterService {
115
121
  }
116
122
  async output(ctx, document) {
117
123
  if (ctx.options.output === "html") {
124
+ if (ctx.options.attachments.length || ctx.options.compress) throw new Error("Attachments and compression are only supported for PDF output.");
118
125
  const { content, assets } = await inlineHtml(document.html, ctx.input.directory, { confined: true });
119
126
  return {
120
127
  content: Buffer.from(content),
@@ -127,9 +134,14 @@ let PrinterService = class PrinterService {
127
134
  media: ctx.options.media,
128
135
  continuous: ctx.options.continuous
129
136
  });
137
+ const processed = await this.pdf.process(pdf, {
138
+ directory: ctx.input.directory,
139
+ attachments: ctx.options.attachments,
140
+ compress: ctx.options.compress
141
+ });
130
142
  return {
131
- content: pdf,
132
- assets
143
+ content: processed.pdf,
144
+ assets: [...assets, ...processed.assets]
133
145
  };
134
146
  }
135
147
  sign(ctx, pdf) {
@@ -146,6 +158,20 @@ let PrinterService = class PrinterService {
146
158
  throw err;
147
159
  });
148
160
  }
161
+ attachments(value) {
162
+ if (value === void 0 || value === null) return [];
163
+ if (!Array.isArray(value)) throw new Error("Front-matter attachments must be a list of { file, position, compress }.");
164
+ return value.map((attachment, index) => {
165
+ if (typeof attachment?.file !== "string" || !attachment.file) throw new Error(`Attachment ${index + 1} needs a file path or URL.`);
166
+ const position = attachment.position ?? "after";
167
+ if (!Object.values(AttachmentPosition).includes(position) && !(Number.isInteger(position) && position >= 1)) throw new Error(`Attachment ${attachment.file} has an invalid position: ${position}, use ${Object.values(AttachmentPosition).join(", ")} or a page number.`);
168
+ return {
169
+ file: attachment.file,
170
+ position,
171
+ compress: attachment.compress === true
172
+ };
173
+ });
174
+ }
149
175
  signOptions(flags, ...sources) {
150
176
  return {
151
177
  ...Object.assign({}, ...sources),
@@ -176,6 +202,7 @@ PrinterService = __decorate([Injectable(), __decorateMetadata("design:paramtypes
176
202
  typeof MarkdownService === "undefined" ? Object : MarkdownService,
177
203
  typeof TemplateService === "undefined" ? Object : TemplateService,
178
204
  typeof RendererService === "undefined" ? Object : RendererService,
205
+ typeof PdfService === "undefined" ? Object : PdfService,
179
206
  typeof SignatureService === "undefined" ? Object : SignatureService
180
207
  ])], PrinterService);
181
208
  export { PrinterService };
@@ -1,8 +1,8 @@
1
1
  import { discoverBrowser } from "./browser.mjs";
2
2
  import { PRINTER_MODULE_OPTIONS } from "../../constants/template.constants.mjs";
3
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
4
- import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateParam.mjs";
5
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateParam.mjs";
5
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
6
6
  import { Inject, Injectable } from "@nestjs/common";
7
7
  import { LoggerService } from "@cenk1cenk2/oclif-common";
8
8
  import puppeteer from "puppeteer-core";
@@ -1,8 +1,8 @@
1
- import { BrowserService } from "./browser.service.mjs";
2
1
  import { mimeType, resolveConfined } from "../template/assets.mjs";
2
+ import { BrowserService } from "./browser.service.mjs";
3
3
  import { PAGE_BREAK_CLASS } from "../../constants/template.constants.mjs";
4
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
5
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
4
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
5
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
6
6
  import { Injectable } from "@nestjs/common";
7
7
  import { readFile } from "fs/promises";
8
8
  import { resolve } from "path";
@@ -2,8 +2,8 @@ import { isLoopbackRequest } from "../loopback.mjs";
2
2
  import { resolveConfined } from "../template/assets.mjs";
3
3
  import { TemplateService } from "../template/template.service.mjs";
4
4
  import "../../constants/file.constants.mjs";
5
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
6
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
5
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
6
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
7
7
  import { Injectable } from "@nestjs/common";
8
8
  import { mkdtemp, rm } from "fs/promises";
9
9
  import { join } from "path";
@@ -1,7 +1,7 @@
1
1
  import { PrinterService } from "../printer.service.mjs";
2
2
  import "../../constants/template.constants.mjs";
3
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
4
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Injectable } from "@nestjs/common";
6
6
  import { dirname, join, resolve } from "path";
7
7
  import { LoggerService } from "@cenk1cenk2/oclif-common";
@@ -4,8 +4,8 @@ import { BrowserService } from "../../renderer/browser.service.mjs";
4
4
  import { IncompleteSignatureError, parseSignedData } from "../cms.mjs";
5
5
  import { SignatureAdapter } from "../signature.adapter.mjs";
6
6
  import "../../../constants/signature.constants.mjs";
7
- import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
8
- import __decorate from "../../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
7
+ import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
8
+ import __decorate from "../../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
9
9
  import { Injectable } from "@nestjs/common";
10
10
  import { readFile } from "fs/promises";
11
11
  import { CommandContext, LoggerService } from "@cenk1cenk2/oclif-common";
@@ -1,7 +1,7 @@
1
1
  import { ATrustAdapter } from "./adapters/a-trust.adapter.mjs";
2
2
  import { IncompleteSignatureError, SignatureVerificationError, parseSignedData } from "./cms.mjs";
3
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
4
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
3
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
4
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
5
5
  import { Injectable } from "@nestjs/common";
6
6
  import { readFile } from "fs/promises";
7
7
  import { isAbsolute, join } from "path";
@@ -2,11 +2,11 @@ import { readFile, realpath } from "fs/promises";
2
2
  import { isAbsolute, relative, sep } from "path";
3
3
  import { toHtml } from "hast-util-to-html";
4
4
  import { visit } from "unist-util-visit";
5
- import { fileURLToPath, pathToFileURL } from "url";
6
5
  import { fromHtml } from "hast-util-from-html";
7
6
  import { transform, transformStyleAttribute } from "lightningcss";
8
7
  import mime from "mime";
9
8
  import { parseSrcset, stringifySrcset } from "srcset";
9
+ import { fileURLToPath, pathToFileURL } from "url";
10
10
  // src/services/template/assets.ts
11
11
  const REFERENCES = {
12
12
  img: ["src", "srcSet"],
@@ -2,9 +2,9 @@ import Template from 'md-printer:template'
2
2
  import components from 'md-printer:template-components'
3
3
  import { render } from 'svelte/server'
4
4
 
5
+ import Nodes from '../../components/Nodes.svelte'
5
6
  import Document from './Document.svelte'
6
7
  import Margin from './Margin.svelte'
7
- import Nodes from '../../components/Nodes.svelte'
8
8
  import type { DocumentShellProps, MarginShellProps, MarkdownNode, RenderedPart, TemplateProps } from '@interfaces'
9
9
 
10
10
  export { components }
@@ -1,8 +1,8 @@
1
1
  import { inlineCss, inlineHtml } from "./assets.mjs";
2
2
  import { sveltePlugin } from "./svelte.plugin.mjs";
3
- import "../../constants/template.constants.mjs";
4
- import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorateMetadata.mjs";
5
- import __decorate from "../../_virtual/_@oxc-project_runtime@0.156.0/helpers/esm/decorate.mjs";
3
+ import { TEMPLATE_PACKAGE_PREFIX } from "../../constants/template.constants.mjs";
4
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorateMetadata.mjs";
5
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.157.0/helpers/esm/decorate.mjs";
6
6
  import { Injectable } from "@nestjs/common";
7
7
  import { dirname, isAbsolute, join, resolve } from "path";
8
8
  import { FileSystemService } from "@cenk1cenk2/oclif-common";
@@ -47,7 +47,7 @@ let TemplateService = class TemplateService {
47
47
  }
48
48
  resolve(template) {
49
49
  const path = template.replace(/^~(?=\/|$)/, homedir());
50
- const directory = isAbsolute(path) || path.startsWith(".") || /[\\/]/.test(path) ? resolve(path) : join(_TemplateService.ROOT, "templates", path);
50
+ const directory = isAbsolute(path) || path.startsWith(".") || /[\\/]/.test(path) ? resolve(path) : this.builtin(path);
51
51
  if (!this.fs.exists(join(directory, "Template.svelte"))) throw new Error(`Template does not exist, expected Template.svelte in: ${directory}`);
52
52
  return directory;
53
53
  }
@@ -108,11 +108,26 @@ let TemplateService = class TemplateService {
108
108
  dependencies
109
109
  };
110
110
  }
111
+ /** A built-in template is a package of its own, so it brings its own fonts, icons and stylesheets as dependencies. */
112
+ builtin(name) {
113
+ const pkg = `${TEMPLATE_PACKAGE_PREFIX}${name}`;
114
+ try {
115
+ return dirname(this.require.resolve(`${pkg}/package.json`));
116
+ } catch {
117
+ throw new Error(`Template does not exist, expected the built-in template package ${pkg} or a path to a directory with Template.svelte.`);
118
+ }
119
+ }
111
120
  async compile(input, template, stylesheets) {
112
121
  const build = await rolldown({
113
122
  input,
114
123
  platform: "node",
115
- logLevel: "silent",
124
+ logLevel: "warn",
125
+ onLog(_, log, handler) {
126
+ if (log.code === "UNRESOLVED_IMPORT") handler("error", {
127
+ ...log,
128
+ message: `${log.message} Install it in the template directory: ${dirname(template)}`
129
+ });
130
+ },
116
131
  plugins: [sveltePlugin({
117
132
  template,
118
133
  components: _TemplateService.COMPONENTS,
@@ -198,5 +198,5 @@
198
198
  "enableJsonFlag": false
199
199
  }
200
200
  },
201
- "version": "3.2.24"
201
+ "version": "3.3.0"
202
202
  }