@cenk1cenk2/md-printer 2.21.0 → 3.1.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.
- package/README.md +333 -48
- package/bin/dev.js +0 -2
- package/bin/run.js +0 -1
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs +1 -2
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs +1 -2
- package/dist/_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs +8 -0
- package/dist/commands/index.mjs +32 -345
- package/dist/components/Date.svelte +1 -0
- package/dist/components/Markdown.svelte +20 -0
- package/dist/components/Nodes.svelte +32 -0
- package/dist/components/Page.svelte +20 -0
- package/dist/components/PageBreak.svelte +5 -0
- package/dist/components/PageNumber.svelte +1 -0
- package/dist/components/SignatureBlock.svelte +56 -0
- package/dist/components/Title.svelte +1 -0
- package/dist/components/TotalPages.svelte +1 -0
- package/dist/components/index.ts +8 -0
- package/dist/constants/file.constants.mjs +1 -8
- package/dist/constants/index.mjs +5 -4
- package/dist/constants/preview.constants.mjs +9 -0
- package/dist/constants/signature.constants.mjs +7 -0
- package/dist/constants/template.constants.mjs +48 -10
- package/dist/interfaces/commands/index.mjs +1 -1
- package/dist/interfaces/commands/run.interface.mjs +1 -1
- package/dist/interfaces/index.mjs +1 -1
- package/dist/interfaces/markdown.interface.mjs +1 -0
- package/dist/interfaces/renderer.interface.mjs +1 -0
- package/dist/interfaces/session.interface.mjs +1 -0
- package/dist/interfaces/signature.interface.mjs +1 -1
- package/dist/interfaces/template.interface.mjs +1 -0
- package/dist/modules/md-printer/md-printer.handler.mjs +113 -0
- package/dist/modules/md-printer/md-printer.module.mjs +21 -0
- package/dist/modules/md-printer/md-printer.schema.mjs +50 -0
- package/dist/services/i18n.service.mjs +12 -15
- package/dist/services/index.mjs +9 -2
- package/dist/services/loopback.mjs +18 -0
- package/dist/services/markdown.service.mjs +123 -0
- package/dist/services/printer.module.mjs +39 -0
- package/dist/services/printer.service.mjs +171 -0
- package/dist/services/renderer/browser.mjs +41 -0
- package/dist/services/renderer/browser.service.mjs +56 -0
- package/dist/services/renderer/renderer.service.mjs +198 -0
- package/dist/services/session/Preview.svelte +238 -0
- package/dist/services/session/preview.service.mjs +187 -0
- package/dist/services/session/session.service.mjs +115 -0
- package/dist/services/signature/adapters/a-trust.adapter.mjs +32 -36
- package/dist/services/signature/index.mjs +1 -2
- package/dist/services/signature/signature.adapter.mjs +1 -2
- package/dist/services/signature/signature.service.mjs +6 -10
- package/dist/services/template/Document.svelte +21 -0
- package/dist/services/template/Margin.svelte +15 -0
- package/dist/services/template/assets.mjs +127 -0
- package/dist/services/template/render.entry.ts +32 -0
- package/dist/services/template/svelte.plugin.mjs +66 -0
- package/dist/services/template/template.service.mjs +211 -0
- package/oclif.manifest.json +49 -36
- package/package.json +69 -35
- package/templates/default/Template.svelte +83 -0
- package/templates/default/settings.json +1 -3
- package/templates/invoice/Template.svelte +247 -0
- package/templates/invoice/components/Client.svelte +42 -0
- package/templates/invoice/components/Company.svelte +41 -0
- package/templates/invoice/components/Items.svelte +66 -0
- package/templates/invoice/components/Notes.svelte +21 -0
- package/templates/invoice/components/Payment.svelte +25 -0
- package/templates/invoice/components/Signature.svelte +34 -0
- package/templates/invoice/components/Totals.svelte +47 -0
- package/templates/invoice/round.js +2 -0
- package/templates/invoice/settings.json +1 -4
- package/templates/kilic.dev/Template.svelte +102 -0
- package/templates/kilic.dev/settings.json +1 -3
- package/templates/mail/Template.svelte +166 -0
- package/templates/mail/settings.json +1 -3
- package/dist/hooks/not-found.hook.mjs +0 -7
- package/dist/hooks/update-notifier.hook.mjs +0 -7
- package/dist/interfaces/pupeteer.mjs +0 -1
- package/templates/default/main.css +0 -73
- package/templates/invoice/.gitignore +0 -1
- package/templates/invoice/footer.html +0 -18
- package/templates/invoice/tailwind.css +0 -164
- package/templates/invoice/template.html.j2 +0 -226
- package/templates/kilic.dev/footer.html +0 -26
- package/templates/kilic.dev/header.html +0 -16
- package/templates/kilic.dev/main.css +0 -74
- package/templates/mail/.gitignore +0 -1
- package/templates/mail/tailwind.css +0 -145
- package/templates/mail/template.html.j2 +0 -14
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { DEFAULT_PDF_OPTIONS } from "../constants/template.constants.mjs";
|
|
2
|
+
import { OutputFileType } from "../constants/file.constants.mjs";
|
|
3
|
+
import __decorateMetadata from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
4
|
+
import __decorate from "../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
5
|
+
import { I18nService } from "./i18n.service.mjs";
|
|
6
|
+
import { MarkdownService } from "./markdown.service.mjs";
|
|
7
|
+
import { inlineHtml } from "./template/assets.mjs";
|
|
8
|
+
import { RendererService } from "./renderer/renderer.service.mjs";
|
|
9
|
+
import { SignatureService } from "./signature/signature.service.mjs";
|
|
10
|
+
import { TemplateService } from "./template/template.service.mjs";
|
|
11
|
+
import { Injectable } from "@nestjs/common";
|
|
12
|
+
import { basename, dirname, extname, join, resolve } from "path";
|
|
13
|
+
import { FileSystemService, LoggerService } from "@cenk1cenk2/oclif-common";
|
|
14
|
+
import { cloneDeep, mergeWith, uniq } from "es-toolkit";
|
|
15
|
+
import { parse } from "yaml";
|
|
16
|
+
//#region src/services/printer.service.ts
|
|
17
|
+
let PrinterService = class PrinterService {
|
|
18
|
+
fs;
|
|
19
|
+
logger;
|
|
20
|
+
i18n;
|
|
21
|
+
markdown;
|
|
22
|
+
template;
|
|
23
|
+
renderer;
|
|
24
|
+
signature;
|
|
25
|
+
constructor(fs, logger, i18n, markdown, template, renderer, signature) {
|
|
26
|
+
this.fs = fs;
|
|
27
|
+
this.logger = logger;
|
|
28
|
+
this.i18n = i18n;
|
|
29
|
+
this.markdown = markdown;
|
|
30
|
+
this.template = template;
|
|
31
|
+
this.renderer = renderer;
|
|
32
|
+
this.signature = signature;
|
|
33
|
+
this.logger.setup(this.constructor.name);
|
|
34
|
+
}
|
|
35
|
+
async input(flags) {
|
|
36
|
+
let file;
|
|
37
|
+
let source;
|
|
38
|
+
if (flags.file !== void 0) {
|
|
39
|
+
file = resolve(flags.file);
|
|
40
|
+
if (!this.fs.exists(file)) throw new Error(`File does not exist: ${file}`);
|
|
41
|
+
source = await this.fs.read(file);
|
|
42
|
+
} else source = flags.stdin;
|
|
43
|
+
const type = flags.inputFiletype ?? (file ? extname(file).replace(/^\./, "") : "md");
|
|
44
|
+
if (type === "yaml" || type === "yml") return {
|
|
45
|
+
file,
|
|
46
|
+
directory: file ? dirname(file) : process.cwd(),
|
|
47
|
+
markdown: {
|
|
48
|
+
type: "root",
|
|
49
|
+
children: []
|
|
50
|
+
},
|
|
51
|
+
data: parse(source) ?? {}
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
file,
|
|
55
|
+
directory: file ? dirname(file) : process.cwd(),
|
|
56
|
+
...this.markdown.parse(source)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async options(flags, input) {
|
|
60
|
+
const { data } = input;
|
|
61
|
+
const template = this.template.resolve(flags.template ?? data.template ?? "default");
|
|
62
|
+
const settings = await this.template.settings(template);
|
|
63
|
+
const output = flags.outputFiletype ?? data.outputFiletype ?? "pdf";
|
|
64
|
+
if (!Object.values(OutputFileType).includes(output)) throw new Error(`Output file type is not supported: ${output}, use one of ${Object.values(OutputFileType).join(", ")}.`);
|
|
65
|
+
const dest = flags.stdout ? void 0 : flags.output ?? data.dest ?? (input.file ? `${basename(input.file, extname(input.file))}.${output}` : void 0);
|
|
66
|
+
return {
|
|
67
|
+
template,
|
|
68
|
+
output,
|
|
69
|
+
dest: dest && resolve(dest),
|
|
70
|
+
title: flags.title ?? data.documentTitle,
|
|
71
|
+
language: flags.language ?? data.language ?? settings.i18n?.language ?? "en",
|
|
72
|
+
continuous: flags.continuous || data.continuous === true || settings.continuous === true,
|
|
73
|
+
media: settings.media ?? "screen",
|
|
74
|
+
highlight: settings.highlightStyle ?? "atom-one-dark",
|
|
75
|
+
pdf: this.pdfOptions(settings.pdfOptions, data.pdfOptions),
|
|
76
|
+
i18n: settings.i18n ?? {},
|
|
77
|
+
sign: this.signOptions(flags, settings.sign, data.sign)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
bundle(options) {
|
|
81
|
+
return this.template.bundle(options.template);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the signing time and the visible signature description, which the template renders before the document is signed.
|
|
85
|
+
*/
|
|
86
|
+
async prepare(ctx) {
|
|
87
|
+
if (!ctx.options.sign.enabled) return;
|
|
88
|
+
if (ctx.options.output !== "pdf") throw new Error("Signing is only supported for PDF output.");
|
|
89
|
+
this.signature.adapter(ctx.options.sign.provider);
|
|
90
|
+
ctx.signingTime = /* @__PURE__ */ new Date();
|
|
91
|
+
ctx.certificate = ctx.options.sign.certificate ? await this.signature.certificate(ctx.options.sign.certificate, ctx.signingTime) : void 0;
|
|
92
|
+
ctx.signature = this.signature.describe(ctx.signingTime, ctx.certificate);
|
|
93
|
+
}
|
|
94
|
+
async document(ctx, bundle) {
|
|
95
|
+
const { input, options } = ctx;
|
|
96
|
+
const document = await this.template.render(options.template, bundle, {
|
|
97
|
+
data: input.data,
|
|
98
|
+
t: await this.i18n.load(join(options.template, "i18n"), {
|
|
99
|
+
...options.i18n,
|
|
100
|
+
language: options.language
|
|
101
|
+
}),
|
|
102
|
+
language: options.language,
|
|
103
|
+
signature: ctx.signature
|
|
104
|
+
}, this.markdown.resolve(input.markdown, bundle.directives), {
|
|
105
|
+
language: options.language,
|
|
106
|
+
title: options.title,
|
|
107
|
+
fallbackTitle: input.file && basename(input.file, extname(input.file)),
|
|
108
|
+
highlight: options.highlight
|
|
109
|
+
});
|
|
110
|
+
this.logger.debug("Rendered document: %d bytes, header: %d bytes, footer: %d bytes", document.html.length, document.header?.length ?? 0, document.footer?.length ?? 0);
|
|
111
|
+
return document;
|
|
112
|
+
}
|
|
113
|
+
async output(ctx, document) {
|
|
114
|
+
if (ctx.options.output === "html") {
|
|
115
|
+
const { content, assets } = await inlineHtml(document.html, ctx.input.directory, { confined: true });
|
|
116
|
+
return {
|
|
117
|
+
content: Buffer.from(content),
|
|
118
|
+
assets
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const { pdf, assets } = await this.renderer.render(document, {
|
|
122
|
+
directory: ctx.input.directory,
|
|
123
|
+
pdf: ctx.options.pdf,
|
|
124
|
+
media: ctx.options.media,
|
|
125
|
+
continuous: ctx.options.continuous
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
content: pdf,
|
|
129
|
+
assets
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
sign(ctx, pdf) {
|
|
133
|
+
return this.signature.sign(pdf, {
|
|
134
|
+
...ctx.options.sign,
|
|
135
|
+
date: ctx.signingTime,
|
|
136
|
+
certificate: ctx.certificate
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
signOptions(flags, ...sources) {
|
|
140
|
+
return {
|
|
141
|
+
...Object.assign({}, ...sources),
|
|
142
|
+
...flags.sign ? { enabled: true } : {},
|
|
143
|
+
...flags.signProvider ? { provider: flags.signProvider } : {},
|
|
144
|
+
...flags.signCertificate ? { certificate: flags.signCertificate } : {}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
pdfOptions(...sources) {
|
|
148
|
+
const pdf = sources.reduce((merged, source) => mergeWith(merged, source ?? {}, (current, value) => Array.isArray(current) && Array.isArray(value) ? uniq([...current, ...value]) : void 0), cloneDeep(DEFAULT_PDF_OPTIONS));
|
|
149
|
+
if (typeof pdf.margin === "string") {
|
|
150
|
+
const [top, right = top, bottom = top, left = right] = pdf.margin.trim().split(/\s+/);
|
|
151
|
+
pdf.margin = {
|
|
152
|
+
top,
|
|
153
|
+
right,
|
|
154
|
+
bottom,
|
|
155
|
+
left
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return pdf;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
PrinterService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [
|
|
162
|
+
typeof FileSystemService === "undefined" ? Object : FileSystemService,
|
|
163
|
+
typeof LoggerService === "undefined" ? Object : LoggerService,
|
|
164
|
+
typeof I18nService === "undefined" ? Object : I18nService,
|
|
165
|
+
typeof MarkdownService === "undefined" ? Object : MarkdownService,
|
|
166
|
+
typeof TemplateService === "undefined" ? Object : TemplateService,
|
|
167
|
+
typeof RendererService === "undefined" ? Object : RendererService,
|
|
168
|
+
typeof SignatureService === "undefined" ? Object : SignatureService
|
|
169
|
+
])], PrinterService);
|
|
170
|
+
//#endregion
|
|
171
|
+
export { PrinterService };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import which from "which";
|
|
2
|
+
//#region src/services/renderer/browser.ts
|
|
3
|
+
var BrowserNotFoundError = class extends Error {
|
|
4
|
+
tried;
|
|
5
|
+
constructor(tried) {
|
|
6
|
+
super([
|
|
7
|
+
"Can not find a Chromium-based browser to print with.",
|
|
8
|
+
`Tried: ${tried.join(", ")}`,
|
|
9
|
+
"Pass --browser <path> or set PUPPETEER_EXECUTABLE_PATH."
|
|
10
|
+
].join("\n"));
|
|
11
|
+
this.tried = tried;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the browser executable: explicit path, environment overrides, then Brave, Chrome, Chromium and Edge from PATH.
|
|
16
|
+
*/
|
|
17
|
+
function discoverBrowser(explicit, env) {
|
|
18
|
+
const candidates = explicit ? [explicit] : [
|
|
19
|
+
env.PUPPETEER_EXECUTABLE_PATH,
|
|
20
|
+
env.CHROME_PATH,
|
|
21
|
+
"brave",
|
|
22
|
+
"brave-browser",
|
|
23
|
+
"brave-browser-stable",
|
|
24
|
+
"google-chrome-stable",
|
|
25
|
+
"google-chrome",
|
|
26
|
+
"chromium",
|
|
27
|
+
"chromium-browser",
|
|
28
|
+
"microsoft-edge-stable",
|
|
29
|
+
"microsoft-edge"
|
|
30
|
+
].filter(Boolean);
|
|
31
|
+
for (const candidate of candidates) {
|
|
32
|
+
const path = which.sync(candidate, {
|
|
33
|
+
path: env.PATH,
|
|
34
|
+
nothrow: true
|
|
35
|
+
});
|
|
36
|
+
if (path) return path;
|
|
37
|
+
}
|
|
38
|
+
throw new BrowserNotFoundError(candidates);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { BrowserNotFoundError, discoverBrowser };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { PRINTER_MODULE_OPTIONS } from "../../constants/template.constants.mjs";
|
|
2
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
3
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
|
+
import { discoverBrowser } from "./browser.mjs";
|
|
5
|
+
import __decorateParam from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateParam.mjs";
|
|
6
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
7
|
+
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
8
|
+
import puppeteer from "puppeteer-core";
|
|
9
|
+
//#region src/services/renderer/browser.service.ts
|
|
10
|
+
let BrowserService = class BrowserService {
|
|
11
|
+
logger;
|
|
12
|
+
options;
|
|
13
|
+
browser;
|
|
14
|
+
constructor(logger, options) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.logger.setup(this.constructor.name);
|
|
18
|
+
}
|
|
19
|
+
executable() {
|
|
20
|
+
return discoverBrowser(this.options.browser, process.env);
|
|
21
|
+
}
|
|
22
|
+
get() {
|
|
23
|
+
this.browser ??= this.launch().catch((err) => {
|
|
24
|
+
this.browser = void 0;
|
|
25
|
+
throw err;
|
|
26
|
+
});
|
|
27
|
+
return this.browser;
|
|
28
|
+
}
|
|
29
|
+
async onModuleDestroy() {
|
|
30
|
+
const browser = await this.browser?.catch(() => void 0);
|
|
31
|
+
this.browser = void 0;
|
|
32
|
+
await browser?.close();
|
|
33
|
+
}
|
|
34
|
+
async launch() {
|
|
35
|
+
const executablePath = this.executable();
|
|
36
|
+
this.logger.debug("Launching browser: %s", executablePath);
|
|
37
|
+
const browser = await puppeteer.launch({
|
|
38
|
+
executablePath,
|
|
39
|
+
handleSIGINT: false,
|
|
40
|
+
handleSIGTERM: false,
|
|
41
|
+
handleSIGHUP: false,
|
|
42
|
+
args: process.getuid?.() === 0 || process.env.CI ? ["--no-sandbox"] : []
|
|
43
|
+
});
|
|
44
|
+
browser.on("disconnected", () => {
|
|
45
|
+
this.browser = void 0;
|
|
46
|
+
});
|
|
47
|
+
return browser;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
BrowserService = __decorate([
|
|
51
|
+
Injectable(),
|
|
52
|
+
__decorateParam(1, Inject(PRINTER_MODULE_OPTIONS)),
|
|
53
|
+
__decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService, Object])
|
|
54
|
+
], BrowserService);
|
|
55
|
+
//#endregion
|
|
56
|
+
export { BrowserService };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { PAGE_BREAK_CLASS } from "../../constants/template.constants.mjs";
|
|
2
|
+
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorateMetadata.mjs";
|
|
3
|
+
import __decorate from "../../_virtual/_@oxc-project_runtime@0.153.0/helpers/esm/decorate.mjs";
|
|
4
|
+
import { BrowserService } from "./browser.service.mjs";
|
|
5
|
+
import { mimeType, resolveConfined } from "../template/assets.mjs";
|
|
6
|
+
import { Injectable } from "@nestjs/common";
|
|
7
|
+
import { readFile } from "fs/promises";
|
|
8
|
+
import { resolve } from "path";
|
|
9
|
+
import { LoggerService } from "@cenk1cenk2/oclif-common";
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
11
|
+
//#region src/services/renderer/renderer.service.ts
|
|
12
|
+
var _RendererService;
|
|
13
|
+
let RendererService = class RendererService {
|
|
14
|
+
static {
|
|
15
|
+
_RendererService = this;
|
|
16
|
+
}
|
|
17
|
+
logger;
|
|
18
|
+
browser;
|
|
19
|
+
static ORIGIN = "http://md-printer.local";
|
|
20
|
+
static DOCUMENT = "/__document.html";
|
|
21
|
+
static SECTION = "data-md-printer-section";
|
|
22
|
+
constructor(logger, browser) {
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.browser = browser;
|
|
25
|
+
this.logger.setup(this.constructor.name);
|
|
26
|
+
}
|
|
27
|
+
async render(rendered, options) {
|
|
28
|
+
const page = await (await this.browser.get()).newPage();
|
|
29
|
+
try {
|
|
30
|
+
const assets = await this.load(page, rendered, options);
|
|
31
|
+
const sections = await this.split(page);
|
|
32
|
+
const pdf = options.continuous ? await this.continuous(page, options, sections) : await this.paginated(page, rendered, options);
|
|
33
|
+
return {
|
|
34
|
+
pdf: Buffer.from(pdf),
|
|
35
|
+
assets
|
|
36
|
+
};
|
|
37
|
+
} finally {
|
|
38
|
+
await page.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async load(page, rendered, options) {
|
|
42
|
+
const assets = /* @__PURE__ */ new Set();
|
|
43
|
+
const base = resolve(options.directory);
|
|
44
|
+
await page.setRequestInterception(true);
|
|
45
|
+
page.on("request", (request) => {
|
|
46
|
+
this.intercept(request, rendered, base, assets).catch(async (err) => {
|
|
47
|
+
this.logger.debug("Request handling failed for %s: %s", request.url(), err);
|
|
48
|
+
await request.abort().catch(() => void 0);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
await page.emulateMediaType(options.media);
|
|
52
|
+
await page.goto(_RendererService.ORIGIN + _RendererService.DOCUMENT, { waitUntil: "load" });
|
|
53
|
+
await page.evaluate(async () => {
|
|
54
|
+
for (const image of Array.from(document.images)) image.loading = "eager";
|
|
55
|
+
await document.fonts.ready;
|
|
56
|
+
await Promise.all(Array.from(document.images).map((image) => image.decode().catch(() => void 0)));
|
|
57
|
+
});
|
|
58
|
+
return [...assets];
|
|
59
|
+
}
|
|
60
|
+
async intercept(request, rendered, base, assets) {
|
|
61
|
+
const url = new URL(request.url());
|
|
62
|
+
if (url.origin !== _RendererService.ORIGIN) await request.continue();
|
|
63
|
+
else if (url.pathname === "/favicon.ico") await request.respond({
|
|
64
|
+
status: 204,
|
|
65
|
+
body: ""
|
|
66
|
+
});
|
|
67
|
+
else if (url.pathname === _RendererService.DOCUMENT) await request.respond({
|
|
68
|
+
status: 200,
|
|
69
|
+
contentType: "text/html; charset=utf-8",
|
|
70
|
+
body: rendered.html
|
|
71
|
+
});
|
|
72
|
+
else {
|
|
73
|
+
const path = fileURLToPath(new URL(`.${url.pathname}`, pathToFileURL(`${base}/`)));
|
|
74
|
+
const confined = await resolveConfined(path, base);
|
|
75
|
+
const body = confined && await readFile(confined).catch(() => void 0);
|
|
76
|
+
if (body) {
|
|
77
|
+
assets.add(path);
|
|
78
|
+
await request.respond({
|
|
79
|
+
status: 200,
|
|
80
|
+
contentType: mimeType(path),
|
|
81
|
+
body
|
|
82
|
+
});
|
|
83
|
+
} else {
|
|
84
|
+
this.logger.warn("Can not load asset: %s", path);
|
|
85
|
+
await request.respond({
|
|
86
|
+
status: 404,
|
|
87
|
+
body: ""
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Cuts the body into top-level sections at every page break, cloning the containers a break sits in, so a break works anywhere: inside
|
|
94
|
+
* flex or grid layouts, where CSS fragmentation ignores it, and in continuous mode, where every section becomes a page of its own height.
|
|
95
|
+
*/
|
|
96
|
+
split(page) {
|
|
97
|
+
return page.evaluate((selector, attribute) => {
|
|
98
|
+
const markers = Array.from(document.body.querySelectorAll(selector));
|
|
99
|
+
if (!markers.length) return 1;
|
|
100
|
+
const sections = markers.map((marker) => {
|
|
101
|
+
const range = document.createRange();
|
|
102
|
+
const section = document.createElement("div");
|
|
103
|
+
range.setStart(document.body, 0);
|
|
104
|
+
range.setEndBefore(marker);
|
|
105
|
+
section.append(range.extractContents());
|
|
106
|
+
marker.remove();
|
|
107
|
+
return section;
|
|
108
|
+
});
|
|
109
|
+
sections.push(document.createElement("div"));
|
|
110
|
+
sections.at(-1).append(...Array.from(document.body.childNodes));
|
|
111
|
+
sections.forEach((section, index) => section.setAttribute(attribute, String(index)));
|
|
112
|
+
document.body.replaceChildren(...sections);
|
|
113
|
+
const style = document.createElement("style");
|
|
114
|
+
style.textContent = `[${attribute}]:not(:last-child) { break-after: page; }`;
|
|
115
|
+
document.head.append(style);
|
|
116
|
+
return sections.length;
|
|
117
|
+
}, `.${PAGE_BREAK_CLASS}`, _RendererService.SECTION);
|
|
118
|
+
}
|
|
119
|
+
paginated(page, rendered, options) {
|
|
120
|
+
return page.pdf({
|
|
121
|
+
...options.pdf,
|
|
122
|
+
displayHeaderFooter: options.pdf.displayHeaderFooter ?? Boolean(rendered.header || rendered.footer),
|
|
123
|
+
headerTemplate: rendered.header ?? "<span></span>",
|
|
124
|
+
footerTemplate: rendered.footer ?? "<span></span>"
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Prints everything as one tall page. Chrome itself resolves the paper size (from a one page probe print) and the margins (as CSS lengths),
|
|
129
|
+
* so md-printer never has to know paper formats or length units.
|
|
130
|
+
*/
|
|
131
|
+
async continuous(page, options, sections) {
|
|
132
|
+
const [, points] = Buffer.from(await page.pdf({
|
|
133
|
+
...options.pdf,
|
|
134
|
+
displayHeaderFooter: false,
|
|
135
|
+
pageRanges: "1"
|
|
136
|
+
})).toString("latin1").match(/\/MediaBox\s*\[\s*[\d.]+\s+[\d.]+\s+([\d.]+)/);
|
|
137
|
+
const width = Math.round(Number(points) * 96 / 72);
|
|
138
|
+
const margin = await page.evaluate((lengths) => {
|
|
139
|
+
const probe = document.createElement("div");
|
|
140
|
+
probe.style.position = "absolute";
|
|
141
|
+
probe.style.visibility = "hidden";
|
|
142
|
+
Object.assign(probe.style, {
|
|
143
|
+
marginTop: lengths?.top,
|
|
144
|
+
marginRight: lengths?.right,
|
|
145
|
+
marginBottom: lengths?.bottom,
|
|
146
|
+
marginLeft: lengths?.left
|
|
147
|
+
});
|
|
148
|
+
document.body.append(probe);
|
|
149
|
+
const style = getComputedStyle(probe);
|
|
150
|
+
const resolved = {
|
|
151
|
+
top: parseFloat(style.marginTop),
|
|
152
|
+
right: parseFloat(style.marginRight),
|
|
153
|
+
bottom: parseFloat(style.marginBottom),
|
|
154
|
+
left: parseFloat(style.marginLeft)
|
|
155
|
+
};
|
|
156
|
+
probe.remove();
|
|
157
|
+
return resolved;
|
|
158
|
+
}, Object.fromEntries(Object.entries(options.pdf.margin ?? {}).map(([side, length]) => [side, typeof length === "number" ? `${length}px` : length])));
|
|
159
|
+
await page.setViewport({
|
|
160
|
+
width: Math.max(Math.round(width - margin.left - margin.right), 1),
|
|
161
|
+
height: 800
|
|
162
|
+
});
|
|
163
|
+
const totals = (await page.evaluate(async (attribute, single) => {
|
|
164
|
+
await document.fonts.ready;
|
|
165
|
+
return (single ? [document.body] : Array.from(document.body.querySelectorAll(`:scope > [${attribute}]`))).map((element) => {
|
|
166
|
+
const top = element.getBoundingClientRect().top;
|
|
167
|
+
return Math.max(element.scrollHeight, single ? document.documentElement.scrollHeight : 0, ...Array.from(element.querySelectorAll("*")).map((child) => child.getBoundingClientRect().bottom - top));
|
|
168
|
+
});
|
|
169
|
+
}, _RendererService.SECTION, sections === 1)).map((height) => Math.ceil(height + margin.top + margin.bottom + 96 / 2.54));
|
|
170
|
+
if (totals.some((total) => total * .75 > 65535)) {
|
|
171
|
+
this.logger.warn("Content is %dpx tall, which is more than a single PDF page can hold, falling back to paginated output.", Math.max(...totals));
|
|
172
|
+
return page.pdf({
|
|
173
|
+
...options.pdf,
|
|
174
|
+
displayHeaderFooter: false
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const pdf = {
|
|
178
|
+
...options.pdf,
|
|
179
|
+
displayHeaderFooter: false
|
|
180
|
+
};
|
|
181
|
+
delete pdf.format;
|
|
182
|
+
delete pdf.landscape;
|
|
183
|
+
if (sections === 1) return page.pdf({
|
|
184
|
+
...pdf,
|
|
185
|
+
width: `${width}px`,
|
|
186
|
+
height: `${totals[0]}px`,
|
|
187
|
+
pageRanges: "1"
|
|
188
|
+
});
|
|
189
|
+
await page.addStyleTag({ content: totals.map((total, index) => `@page md-printer-${index} { size: ${width}px ${total}px; } [${_RendererService.SECTION}="${index}"] { page: md-printer-${index}; }`).join("\n") });
|
|
190
|
+
return page.pdf({
|
|
191
|
+
...pdf,
|
|
192
|
+
preferCSSPageSize: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
RendererService = _RendererService = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [typeof LoggerService === "undefined" ? Object : LoggerService, typeof BrowserService === "undefined" ? Object : BrowserService])], RendererService);
|
|
197
|
+
//#endregion
|
|
198
|
+
export { RendererService };
|