@aexol/spectral 0.9.103 → 0.9.104
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/dist/extensions/pdf/tools/generate.d.ts.map +1 -1
- package/dist/extensions/pdf/tools/generate.js +51 -12
- package/dist/extensions/pdf/tools/preview.d.ts.map +1 -1
- package/dist/extensions/pdf/tools/preview.js +25 -7
- package/dist/extensions/pdf/tools/render.d.ts.map +1 -1
- package/dist/extensions/pdf/tools/render.js +50 -12
- package/dist/extensions/pdf/utils/renderer.d.ts +1 -1
- package/dist/extensions/pdf/utils/renderer.d.ts.map +1 -1
- package/dist/extensions/pdf/utils/renderer.js +32 -7
- package/dist/extensions/pdf/utils/validate.d.ts.map +1 -1
- package/dist/extensions/pdf/utils/validate.js +69 -4
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/tools/generate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/tools/generate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EACV,SAAS,EAET,WAAW,EAGZ,MAAM,aAAa,CAAC;AAyBrB,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,SAAS,GAChB,IAAI,CA2KN;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAO7E"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { createDocumentPaths, ensureDir, pathToFileHref, toDisplayPath } from "../utils/paths.js";
|
|
2
|
+
import { createDocumentPaths, ensureDir, pathToFileHref, toDisplayPath, } from "../utils/paths.js";
|
|
3
3
|
import { printCssWarning, validateHtmlDocument } from "../utils/html.js";
|
|
4
4
|
import { renderPdf } from "../utils/renderer.js";
|
|
5
5
|
import { errorResult, textResult } from "./helpers.js";
|
|
@@ -29,15 +29,45 @@ export function registerGenerateTool(ext, config) {
|
|
|
29
29
|
type: "object",
|
|
30
30
|
properties: {
|
|
31
31
|
title: { type: "string", description: "Document title." },
|
|
32
|
-
html: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
32
|
+
html: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Complete HTML document. Must include doctype, html, head, and body.",
|
|
35
|
+
},
|
|
36
|
+
outputName: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Optional stable filename/folder slug.",
|
|
39
|
+
},
|
|
40
|
+
format: {
|
|
41
|
+
type: "string",
|
|
42
|
+
enum: [...formatEnum],
|
|
43
|
+
description: "Page format. Default from config: A4.",
|
|
44
|
+
},
|
|
45
|
+
orientation: {
|
|
46
|
+
type: "string",
|
|
47
|
+
enum: [...orientationEnum],
|
|
48
|
+
description: "Page orientation. Default: portrait.",
|
|
49
|
+
},
|
|
50
|
+
printBackground: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description: "Render CSS backgrounds. Default: true.",
|
|
53
|
+
},
|
|
54
|
+
allowRemoteAssets: {
|
|
55
|
+
type: "boolean",
|
|
56
|
+
description: "Allow http/https assets during validation/render. Default: false.",
|
|
57
|
+
},
|
|
58
|
+
viewport: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: { width: { type: "number" }, height: { type: "number" } },
|
|
61
|
+
description: "Override the browser viewport before rendering (useful for slide decks). Default: auto.",
|
|
62
|
+
},
|
|
63
|
+
width: {
|
|
64
|
+
type: "number",
|
|
65
|
+
description: "Custom PDF page width in millimetres. Omit for format-based sizing.",
|
|
66
|
+
},
|
|
67
|
+
height: {
|
|
68
|
+
type: "number",
|
|
69
|
+
description: "Custom PDF page height in millimetres. Omit for format-based sizing.",
|
|
70
|
+
},
|
|
41
71
|
margin: {
|
|
42
72
|
type: "object",
|
|
43
73
|
properties: {
|
|
@@ -69,7 +99,13 @@ export function registerGenerateTool(ext, config) {
|
|
|
69
99
|
pdfPath: toDisplayPath(ctx.cwd, paths.pdfPath),
|
|
70
100
|
createdAt: now,
|
|
71
101
|
updatedAt: now,
|
|
72
|
-
page: {
|
|
102
|
+
page: {
|
|
103
|
+
format,
|
|
104
|
+
orientation,
|
|
105
|
+
width: p.width,
|
|
106
|
+
height: p.height,
|
|
107
|
+
margin,
|
|
108
|
+
},
|
|
73
109
|
source: "generated",
|
|
74
110
|
};
|
|
75
111
|
writeManifest(paths.manifestPath, manifest);
|
|
@@ -86,7 +122,10 @@ export function registerGenerateTool(ext, config) {
|
|
|
86
122
|
height: p.height,
|
|
87
123
|
waitUntil: allowRemoteAssets ? "networkidle" : "load",
|
|
88
124
|
});
|
|
89
|
-
writeManifest(paths.manifestPath, {
|
|
125
|
+
writeManifest(paths.manifestPath, {
|
|
126
|
+
...manifest,
|
|
127
|
+
updatedAt: new Date().toISOString(),
|
|
128
|
+
});
|
|
90
129
|
const warning = printCssWarning(p.html);
|
|
91
130
|
const lines = [
|
|
92
131
|
"✓ PDF generated from editable HTML",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/tools/preview.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/tools/preview.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAW7C,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,YAAY,EACjB,MAAM,EAAE,SAAS,GAChB,IAAI,CAuFN"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { assertInsideWorkspace, ensureParent, resolveWorkspacePath, toDisplayPath } from "../utils/paths.js";
|
|
3
|
+
import { assertInsideWorkspace, ensureParent, resolveWorkspacePath, toDisplayPath, } from "../utils/paths.js";
|
|
4
4
|
import { validateHtmlDocument } from "../utils/html.js";
|
|
5
5
|
import { renderPreview } from "../utils/renderer.js";
|
|
6
6
|
import { errorResult, textResult } from "./helpers.js";
|
|
@@ -13,9 +13,23 @@ export function registerPreviewTool(ext, config) {
|
|
|
13
13
|
parameters: {
|
|
14
14
|
type: "object",
|
|
15
15
|
properties: {
|
|
16
|
-
htmlPath: {
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
htmlPath: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Path to an HTML file inside the workspace.",
|
|
19
|
+
},
|
|
20
|
+
outputPath: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Optional PNG output path inside the workspace.",
|
|
23
|
+
},
|
|
24
|
+
orientation: {
|
|
25
|
+
type: "string",
|
|
26
|
+
enum: ["portrait", "landscape"],
|
|
27
|
+
description: "Preview orientation. Default from PDF config.",
|
|
28
|
+
},
|
|
29
|
+
allowRemoteAssets: {
|
|
30
|
+
type: "boolean",
|
|
31
|
+
description: "Allow http/https assets during preview. Default: false.",
|
|
32
|
+
},
|
|
19
33
|
},
|
|
20
34
|
required: ["htmlPath"],
|
|
21
35
|
},
|
|
@@ -26,12 +40,16 @@ export function registerPreviewTool(ext, config) {
|
|
|
26
40
|
assertInsideWorkspace(ctx.cwd, htmlPath);
|
|
27
41
|
if (!existsSync(htmlPath))
|
|
28
42
|
throw new Error(`HTML file not found: ${p.htmlPath}`);
|
|
29
|
-
const previewPath = p.outputPath
|
|
43
|
+
const previewPath = p.outputPath
|
|
44
|
+
? resolveWorkspacePath(ctx.cwd, p.outputPath)
|
|
45
|
+
: join(dirname(htmlPath), "preview.png");
|
|
30
46
|
assertInsideWorkspace(ctx.cwd, previewPath);
|
|
31
47
|
ensureParent(previewPath);
|
|
32
48
|
const allowRemoteAssets = p.allowRemoteAssets ?? config.allowRemoteAssets;
|
|
33
|
-
validateHtmlDocument(readFileSync(htmlPath, "utf-8"), {
|
|
34
|
-
|
|
49
|
+
validateHtmlDocument(readFileSync(htmlPath, "utf-8"), {
|
|
50
|
+
allowRemoteAssets,
|
|
51
|
+
});
|
|
52
|
+
const preview = await renderPreview(config, htmlPath, previewPath, allowRemoteAssets, p.orientation);
|
|
35
53
|
return textResult([
|
|
36
54
|
"✓ HTML preview rendered",
|
|
37
55
|
`HTML: ${toDisplayPath(ctx.cwd, htmlPath)}`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/tools/render.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/tools/render.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EACV,SAAS,EAKV,MAAM,aAAa,CAAC;AAyBrB,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAqK7E"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, extname, join } from "node:path";
|
|
3
|
-
import { assertInsideWorkspace, ensureParent, pathToFileHref, resolveWorkspacePath, toDisplayPath } from "../utils/paths.js";
|
|
3
|
+
import { assertInsideWorkspace, ensureParent, pathToFileHref, resolveWorkspacePath, toDisplayPath, } from "../utils/paths.js";
|
|
4
4
|
import { printCssWarning, validateHtmlDocument } from "../utils/html.js";
|
|
5
5
|
import { renderPdf } from "../utils/renderer.js";
|
|
6
6
|
import { errorResult, textResult } from "./helpers.js";
|
|
@@ -25,15 +25,45 @@ export function registerRenderTool(ext, config) {
|
|
|
25
25
|
parameters: {
|
|
26
26
|
type: "object",
|
|
27
27
|
properties: {
|
|
28
|
-
htmlPath: {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
htmlPath: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Path to the existing HTML file, relative to workspace or absolute inside workspace.",
|
|
31
|
+
},
|
|
32
|
+
outputPath: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Optional PDF output path, relative to workspace or absolute inside workspace.",
|
|
35
|
+
},
|
|
36
|
+
format: {
|
|
37
|
+
type: "string",
|
|
38
|
+
enum: ["A4", "Letter"],
|
|
39
|
+
description: "Page format. Default: A4.",
|
|
40
|
+
},
|
|
41
|
+
orientation: {
|
|
42
|
+
type: "string",
|
|
43
|
+
enum: ["portrait", "landscape"],
|
|
44
|
+
description: "Page orientation. Default: portrait.",
|
|
45
|
+
},
|
|
46
|
+
printBackground: {
|
|
47
|
+
type: "boolean",
|
|
48
|
+
description: "Render CSS backgrounds. Default: true.",
|
|
49
|
+
},
|
|
50
|
+
allowRemoteAssets: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
description: "Allow http/https assets during validation/render. Default: false.",
|
|
53
|
+
},
|
|
54
|
+
viewport: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: { width: { type: "number" }, height: { type: "number" } },
|
|
57
|
+
description: "Override the browser viewport before rendering. Default: auto.",
|
|
58
|
+
},
|
|
59
|
+
width: {
|
|
60
|
+
type: "number",
|
|
61
|
+
description: "Custom PDF page width in millimetres. Omit for format-based sizing.",
|
|
62
|
+
},
|
|
63
|
+
height: {
|
|
64
|
+
type: "number",
|
|
65
|
+
description: "Custom PDF page height in millimetres. Omit for format-based sizing.",
|
|
66
|
+
},
|
|
37
67
|
margin: {
|
|
38
68
|
type: "object",
|
|
39
69
|
properties: {
|
|
@@ -53,7 +83,9 @@ export function registerRenderTool(ext, config) {
|
|
|
53
83
|
assertInsideWorkspace(ctx.cwd, htmlPath);
|
|
54
84
|
if (!existsSync(htmlPath))
|
|
55
85
|
throw new Error(`HTML file not found: ${p.htmlPath}`);
|
|
56
|
-
const outputPath = p.outputPath
|
|
86
|
+
const outputPath = p.outputPath
|
|
87
|
+
? resolveWorkspacePath(ctx.cwd, p.outputPath)
|
|
88
|
+
: defaultPdfPath(htmlPath);
|
|
57
89
|
assertInsideWorkspace(ctx.cwd, outputPath);
|
|
58
90
|
ensureParent(outputPath);
|
|
59
91
|
const html = readFileSync(htmlPath, "utf-8");
|
|
@@ -84,7 +116,13 @@ export function registerRenderTool(ext, config) {
|
|
|
84
116
|
pdfPath: toDisplayPath(ctx.cwd, outputPath),
|
|
85
117
|
createdAt: now,
|
|
86
118
|
updatedAt: now,
|
|
87
|
-
page: {
|
|
119
|
+
page: {
|
|
120
|
+
format,
|
|
121
|
+
orientation,
|
|
122
|
+
width: p.width,
|
|
123
|
+
height: p.height,
|
|
124
|
+
margin,
|
|
125
|
+
},
|
|
88
126
|
source: "rendered",
|
|
89
127
|
};
|
|
90
128
|
const manifestPath = join(dirname(htmlPath), "manifest.json");
|
|
@@ -20,7 +20,7 @@ export interface RenderPdfResult {
|
|
|
20
20
|
bytes: number;
|
|
21
21
|
}
|
|
22
22
|
export declare function renderPdf(config: PdfConfig, opts: RenderPdfOptions): Promise<RenderPdfResult>;
|
|
23
|
-
export declare function renderPreview(config: PdfConfig, htmlPath: string, previewPath: string, allowRemoteAssets: boolean): Promise<{
|
|
23
|
+
export declare function renderPreview(config: PdfConfig, htmlPath: string, previewPath: string, allowRemoteAssets: boolean, orientation?: PdfOrientation): Promise<{
|
|
24
24
|
previewPath: string;
|
|
25
25
|
bytes: number;
|
|
26
26
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/utils/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/utils/renderer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,cAAc,EACf,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,cAAc,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,SAAS,CAC7B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,eAAe,CAAC,CAuD1B;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,OAAO,EAC1B,WAAW,GAAE,cAA0C,GACtD,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAgCjD"}
|
|
@@ -2,6 +2,17 @@ import { existsSync, statSync } from "node:fs";
|
|
|
2
2
|
import { chromium } from "playwright";
|
|
3
3
|
import { pathToFileHref } from "./paths.js";
|
|
4
4
|
export async function renderPdf(config, opts) {
|
|
5
|
+
for (const [name, value] of [
|
|
6
|
+
["width", opts.width],
|
|
7
|
+
["height", opts.height],
|
|
8
|
+
]) {
|
|
9
|
+
if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {
|
|
10
|
+
throw new Error(`Custom PDF ${name} must be a positive number of millimetres.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if ((opts.width === undefined) !== (opts.height === undefined)) {
|
|
14
|
+
throw new Error("Custom PDF width and height must be provided together.");
|
|
15
|
+
}
|
|
5
16
|
const browser = await chromium.launch({ headless: true });
|
|
6
17
|
try {
|
|
7
18
|
const page = await browser.newPage();
|
|
@@ -12,7 +23,9 @@ export async function renderPdf(config, opts) {
|
|
|
12
23
|
if (!opts.allowRemoteAssets) {
|
|
13
24
|
await page.route("**/*", (route) => {
|
|
14
25
|
const url = route.request().url();
|
|
15
|
-
if (url.startsWith("file://") ||
|
|
26
|
+
if (url.startsWith("file://") ||
|
|
27
|
+
url.startsWith("data:") ||
|
|
28
|
+
url === "about:blank") {
|
|
16
29
|
return route.continue();
|
|
17
30
|
}
|
|
18
31
|
return route.abort();
|
|
@@ -27,8 +40,10 @@ export async function renderPdf(config, opts) {
|
|
|
27
40
|
format: opts.format,
|
|
28
41
|
landscape: opts.orientation === "landscape",
|
|
29
42
|
printBackground: opts.printBackground,
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
// Chromium expects PDF dimensions as strings with units. Numeric values
|
|
44
|
+
// are CSS pixels and can accidentally make a paper-sized page tiny.
|
|
45
|
+
...(opts.width !== undefined ? { width: `${opts.width}mm` } : {}),
|
|
46
|
+
...(opts.height !== undefined ? { height: `${opts.height}mm` } : {}),
|
|
32
47
|
margin: opts.margin,
|
|
33
48
|
});
|
|
34
49
|
if (!existsSync(opts.pdfPath))
|
|
@@ -39,20 +54,30 @@ export async function renderPdf(config, opts) {
|
|
|
39
54
|
await browser.close();
|
|
40
55
|
}
|
|
41
56
|
}
|
|
42
|
-
export async function renderPreview(config, htmlPath, previewPath, allowRemoteAssets) {
|
|
57
|
+
export async function renderPreview(config, htmlPath, previewPath, allowRemoteAssets, orientation = config.defaultOrientation) {
|
|
43
58
|
const browser = await chromium.launch({ headless: true });
|
|
44
59
|
try {
|
|
45
|
-
|
|
60
|
+
// Keep the preview in the same aspect ratio as the configured paper. This is
|
|
61
|
+
// intentionally a CSS viewport, not a page.pdf width/height override.
|
|
62
|
+
const viewport = orientation === "landscape"
|
|
63
|
+
? { width: 1754, height: 1240 }
|
|
64
|
+
: { width: 1240, height: 1754 };
|
|
65
|
+
const page = await browser.newPage({ viewport, deviceScaleFactor: 1 });
|
|
46
66
|
page.setDefaultTimeout(config.renderTimeoutMs);
|
|
47
67
|
if (!allowRemoteAssets) {
|
|
48
68
|
await page.route("**/*", (route) => {
|
|
49
69
|
const url = route.request().url();
|
|
50
|
-
if (url.startsWith("file://") ||
|
|
70
|
+
if (url.startsWith("file://") ||
|
|
71
|
+
url.startsWith("data:") ||
|
|
72
|
+
url === "about:blank")
|
|
51
73
|
return route.continue();
|
|
52
74
|
return route.abort();
|
|
53
75
|
});
|
|
54
76
|
}
|
|
55
|
-
await page.goto(pathToFileHref(htmlPath), {
|
|
77
|
+
await page.goto(pathToFileHref(htmlPath), {
|
|
78
|
+
waitUntil: "load",
|
|
79
|
+
timeout: config.renderTimeoutMs,
|
|
80
|
+
});
|
|
56
81
|
await page.screenshot({ path: previewPath, fullPage: true, type: "png" });
|
|
57
82
|
return { previewPath, bytes: statSync(previewPath).size };
|
|
58
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/utils/validate.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/extensions/pdf/utils/validate.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,qBAAqB,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA2CD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GACrC,kBAAkB,EAAE,CAkMtB;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,kBAAkB,EAAE,GAC3B,OAAO,CAET"}
|
|
@@ -15,6 +15,23 @@ const WIDE_WIDTH_RE = /(?:width|min-width)\s*:\s*(?:[1-9]\d{3,}px|(?:8[5-9]|9\d|
|
|
|
15
15
|
const IMAGE_RE = /<img\b[^>]*>/gi;
|
|
16
16
|
const WIDTH_ATTR_RE = /\bwidth\s*=\s*["'][^"']+["']/i;
|
|
17
17
|
const HEIGHT_ATTR_RE = /\bheight\s*=\s*["'][^"']+["']/i;
|
|
18
|
+
const TRANSFORM_SCALE_RE = /transform\s*:[^;{}]*scale\s*\(/i;
|
|
19
|
+
const ZOOM_RE = /zoom\s*:\s*(?!1(?:\.0+)?\b)(?:0?\.)?\d+/i;
|
|
20
|
+
const FIXED_VIEWPORT_RE = /(?:width|height)\s*:\s*100vw|(?:width|height)\s*:\s*100vh|(?:width|min-width|height|min-height)\s*:\s*\d{4,}px/i;
|
|
21
|
+
const PAGE_SIZE_RE = /@page\s*{[^}]*size\s*:\s*([^;}]*)/is;
|
|
22
|
+
function normalizePageSize(size) {
|
|
23
|
+
const value = size.trim().toLowerCase();
|
|
24
|
+
const orientation = value.includes("landscape")
|
|
25
|
+
? "landscape"
|
|
26
|
+
: value.includes("portrait")
|
|
27
|
+
? "portrait"
|
|
28
|
+
: undefined;
|
|
29
|
+
if (value.includes("a4"))
|
|
30
|
+
return { format: "A4", orientation };
|
|
31
|
+
if (value.includes("letter"))
|
|
32
|
+
return { format: "Letter", orientation };
|
|
33
|
+
return { orientation };
|
|
34
|
+
}
|
|
18
35
|
export function analyzePdfHtml(html, opts) {
|
|
19
36
|
const issues = [];
|
|
20
37
|
if (html.length > 5_000_000) {
|
|
@@ -34,13 +51,25 @@ export function analyzePdfHtml(html, opts) {
|
|
|
34
51
|
});
|
|
35
52
|
}
|
|
36
53
|
if (!HTML_RE.test(html)) {
|
|
37
|
-
issues.push({
|
|
54
|
+
issues.push({
|
|
55
|
+
severity: "error",
|
|
56
|
+
code: "missing_html",
|
|
57
|
+
message: "HTML must include <html>.",
|
|
58
|
+
});
|
|
38
59
|
}
|
|
39
60
|
if (!HEAD_RE.test(html)) {
|
|
40
|
-
issues.push({
|
|
61
|
+
issues.push({
|
|
62
|
+
severity: "error",
|
|
63
|
+
code: "missing_head",
|
|
64
|
+
message: "HTML must include <head>.",
|
|
65
|
+
});
|
|
41
66
|
}
|
|
42
67
|
if (!BODY_RE.test(html)) {
|
|
43
|
-
issues.push({
|
|
68
|
+
issues.push({
|
|
69
|
+
severity: "error",
|
|
70
|
+
code: "missing_body",
|
|
71
|
+
message: "HTML must include <body>.",
|
|
72
|
+
});
|
|
44
73
|
}
|
|
45
74
|
if (!TITLE_RE.test(html)) {
|
|
46
75
|
issues.push({
|
|
@@ -55,7 +84,7 @@ export function analyzePdfHtml(html, opts) {
|
|
|
55
84
|
severity: "info",
|
|
56
85
|
code: "missing_viewport",
|
|
57
86
|
message: "HTML has no viewport meta tag.",
|
|
58
|
-
suggestion:
|
|
87
|
+
suggestion: 'Add <meta name="viewport" content="width=device-width, initial-scale=1">.',
|
|
59
88
|
});
|
|
60
89
|
}
|
|
61
90
|
if (!PAGE_RE.test(html)) {
|
|
@@ -123,6 +152,42 @@ export function analyzePdfHtml(html, opts) {
|
|
|
123
152
|
suggestion: "Check pagination carefully; absolute elements can overlap page breaks.",
|
|
124
153
|
});
|
|
125
154
|
}
|
|
155
|
+
if (TRANSFORM_SCALE_RE.test(html)) {
|
|
156
|
+
issues.push({
|
|
157
|
+
severity: "warning",
|
|
158
|
+
code: "transform_scale",
|
|
159
|
+
message: "CSS uses transform: scale(), which can shrink or clip PDF content.",
|
|
160
|
+
suggestion: "Use print-native dimensions and layout instead of scaling the whole document.",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (ZOOM_RE.test(html)) {
|
|
164
|
+
issues.push({
|
|
165
|
+
severity: "warning",
|
|
166
|
+
code: "css_zoom",
|
|
167
|
+
message: "CSS uses zoom other than 1, which can change PDF pagination and scale.",
|
|
168
|
+
suggestion: "Remove zoom for print output or scope it to an intentional print layout.",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (FIXED_VIEWPORT_RE.test(html)) {
|
|
172
|
+
issues.push({
|
|
173
|
+
severity: "warning",
|
|
174
|
+
code: "fixed_or_viewport_layout",
|
|
175
|
+
message: "A viewport-sized or fixed oversized layout may not fit the selected paper.",
|
|
176
|
+
suggestion: "Use responsive width: 100% with print margins and avoid fixed pixel canvases.",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const pageSizeMatch = html.match(PAGE_SIZE_RE);
|
|
180
|
+
if (pageSizeMatch) {
|
|
181
|
+
const pageSize = normalizePageSize(pageSizeMatch[1]);
|
|
182
|
+
if (pageSize.orientation && pageSize.format) {
|
|
183
|
+
issues.push({
|
|
184
|
+
severity: "info",
|
|
185
|
+
code: "page_size_orientation",
|
|
186
|
+
message: `@page explicitly requests ${pageSize.format} ${pageSize.orientation}.`,
|
|
187
|
+
suggestion: "Keep @page size aligned with the PDF format and orientation options.",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
126
191
|
if (WIDE_WIDTH_RE.test(html)) {
|
|
127
192
|
issues.push({
|
|
128
193
|
severity: "warning",
|