@ahmedrowaihi/pdf-forge-printer 1.0.0-canary.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/LICENSE.md +8 -0
- package/README.md +139 -0
- package/dist/index.d.mts +29 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +157 -0
- package/dist/index.mjs +132 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +26 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Copyright 2024 Plus Five Five, Inc
|
|
2
|
+
Copyright 2025 ahmedrowaihi (fork modifications)
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
5
|
+
|
|
6
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# @ahmedrowaihi/pdf-forge-printer
|
|
2
|
+
|
|
3
|
+
Playwright-based PDF and screenshot rendering service for React PDF Forge.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @ahmedrowaihi/pdf-forge-printer playwright
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Basic Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { PlaywrightPdfService } from '@ahmedrowaihi/pdf-forge-printer';
|
|
17
|
+
|
|
18
|
+
const pdfService = new PlaywrightPdfService();
|
|
19
|
+
|
|
20
|
+
// Generate PDF
|
|
21
|
+
const pdfBuffer = await pdfService.render({
|
|
22
|
+
html: '<html>...</html>',
|
|
23
|
+
outputType: 'pdf',
|
|
24
|
+
darkMode: false,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Generate Screenshot
|
|
28
|
+
const screenshotBuffer = await pdfService.render({
|
|
29
|
+
html: '<html>...</html>',
|
|
30
|
+
outputType: 'screenshot',
|
|
31
|
+
darkMode: true,
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### With Custom Logger
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import {
|
|
39
|
+
PlaywrightPdfService,
|
|
40
|
+
ConsoleLogger,
|
|
41
|
+
} from '@ahmedrowaihi/pdf-forge-printer';
|
|
42
|
+
|
|
43
|
+
const logger = new ConsoleLogger();
|
|
44
|
+
const pdfService = new PlaywrightPdfService(logger);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### From URL
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const pdfBuffer = await pdfService.render({
|
|
51
|
+
url: 'https://example.com',
|
|
52
|
+
outputType: 'pdf',
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### `PlaywrightPdfService`
|
|
59
|
+
|
|
60
|
+
#### Constructor
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
constructor(logger?: PdfLogger)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `logger` (optional): Custom logger instance. Defaults to `ConsoleLogger`.
|
|
67
|
+
|
|
68
|
+
#### `render(input)`
|
|
69
|
+
|
|
70
|
+
Renders HTML or URL to PDF or screenshot.
|
|
71
|
+
|
|
72
|
+
**Parameters:**
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
{
|
|
76
|
+
html?: string; // HTML content to render
|
|
77
|
+
url?: string; // URL to render (must provide html OR url)
|
|
78
|
+
outputType: 'pdf' | 'screenshot';
|
|
79
|
+
darkMode?: boolean; // Enable dark mode (default: false)
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Returns:** `Promise<Uint8Array>` - Buffer containing PDF or PNG image
|
|
84
|
+
|
|
85
|
+
**Example:**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const buffer = await pdfService.render({
|
|
89
|
+
html: '<html><body>Hello World</body></html>',
|
|
90
|
+
outputType: 'pdf',
|
|
91
|
+
darkMode: false,
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Features
|
|
96
|
+
|
|
97
|
+
- ✅ **A4 Format Only** - Simplified to A4 paper size
|
|
98
|
+
- ✅ **Dark Mode Support** - Native Playwright colorScheme emulation + class-based theming
|
|
99
|
+
- ✅ **Print Media Emulation** - Automatically applies `@media print` styles for PDFs
|
|
100
|
+
- ✅ **High-Quality Screenshots** - Full-page screenshots with disabled animations
|
|
101
|
+
- ✅ **CSS @page Support** - Respects `@page` CSS rules via `preferCSSPageSize: true`
|
|
102
|
+
- ✅ **Zero Margins** - Clean edge-to-edge rendering
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
### Environment Variables
|
|
107
|
+
|
|
108
|
+
- `CHROMIUM_EXECUTABLE_PATH` - Path to custom Chromium executable (optional)
|
|
109
|
+
|
|
110
|
+
### Default Settings
|
|
111
|
+
|
|
112
|
+
- **PDF Format:** A4 (210mm × 297mm)
|
|
113
|
+
- **Margins:** 0px on all sides
|
|
114
|
+
- **Print Background:** Enabled
|
|
115
|
+
- **Screenshot Scale:** CSS pixels (keeps file size small)
|
|
116
|
+
- **Animations:** Disabled for screenshots
|
|
117
|
+
- **Viewport:** 794px × 1123px (A4 at 96 DPI)
|
|
118
|
+
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
1. Launches headless Chromium browser
|
|
122
|
+
2. Creates a context with emulation options (colorScheme, locale, viewport)
|
|
123
|
+
3. Loads HTML content or navigates to URL
|
|
124
|
+
4. Applies dark mode class if requested
|
|
125
|
+
5. Emulates print media for PDF generation
|
|
126
|
+
6. Captures PDF or screenshot using Playwright's native APIs
|
|
127
|
+
7. Returns buffer as `Uint8Array`
|
|
128
|
+
|
|
129
|
+
## Integration with React PDF Forge
|
|
130
|
+
|
|
131
|
+
This package is used by `@ahmedrowaihi/pdf-forge-preview` for exporting templates. It works seamlessly with:
|
|
132
|
+
|
|
133
|
+
- Playwright handles page size via `format: 'A4'` and `emulateMedia({ media: 'print' })`
|
|
134
|
+
- `Theme` component - Class-based theming system
|
|
135
|
+
- `PrintStyles` component - Wraps `@media print` styles
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/logger.d.ts
|
|
2
|
+
interface PdfLogger {
|
|
3
|
+
debug(message: string): void;
|
|
4
|
+
log(message: string): void;
|
|
5
|
+
warn(message: string): void;
|
|
6
|
+
error(message: string, error?: unknown): void;
|
|
7
|
+
}
|
|
8
|
+
declare class ConsoleLogger implements PdfLogger {
|
|
9
|
+
debug(message: string): void;
|
|
10
|
+
log(message: string): void;
|
|
11
|
+
warn(message: string): void;
|
|
12
|
+
error(message: string, error?: unknown): void;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/playwright-pdf.service.d.ts
|
|
16
|
+
declare class PlaywrightPdfService {
|
|
17
|
+
private readonly logger;
|
|
18
|
+
constructor(logger?: PdfLogger);
|
|
19
|
+
private launchBrowser;
|
|
20
|
+
render(input: {
|
|
21
|
+
html?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
outputType: 'pdf' | 'screenshot';
|
|
24
|
+
darkMode?: boolean;
|
|
25
|
+
}): Promise<Uint8Array>;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ConsoleLogger, type PdfLogger, PlaywrightPdfService };
|
|
29
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/logger.ts","../src/playwright-pdf.service.ts"],"sourcesContent":[],"mappings":";UAAiB,SAAA;EAAA,KAAA,CAAA,OAAS,EAAA,MAAA,CAAA,EAAA,IAAA;EAOb,GAAA,CAAA,OAAA,EAAA,MAAc,CAAA,EAAA,IAAA;;;;ACGd,cDHA,aAAA,YAAyB,SCGL,CAAA;EAGX,KAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAwCR,GAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAR,IAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAO,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA,CAAA,EAAA,IAAA;;;;ADrDI,cCUJ,oBAAA,CDVa;EAOb,iBAAc,MAAA;uBCML;;;IAHT,IAAA,CAAA,EAAA,MAAA;IAGS,GAAA,CAAA,EAAA,MAAA;IAwCR,UAAA,EAAA,KAAA,GAAA,YAAA;IAAR,QAAA,CAAA,EAAA,OAAA;EAAO,CAAA,CAAA,EAAP,OAAO,CAAC,UAAD,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/logger.d.ts
|
|
2
|
+
interface PdfLogger {
|
|
3
|
+
debug(message: string): void;
|
|
4
|
+
log(message: string): void;
|
|
5
|
+
warn(message: string): void;
|
|
6
|
+
error(message: string, error?: unknown): void;
|
|
7
|
+
}
|
|
8
|
+
declare class ConsoleLogger implements PdfLogger {
|
|
9
|
+
debug(message: string): void;
|
|
10
|
+
log(message: string): void;
|
|
11
|
+
warn(message: string): void;
|
|
12
|
+
error(message: string, error?: unknown): void;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/playwright-pdf.service.d.ts
|
|
16
|
+
declare class PlaywrightPdfService {
|
|
17
|
+
private readonly logger;
|
|
18
|
+
constructor(logger?: PdfLogger);
|
|
19
|
+
private launchBrowser;
|
|
20
|
+
render(input: {
|
|
21
|
+
html?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
outputType: 'pdf' | 'screenshot';
|
|
24
|
+
darkMode?: boolean;
|
|
25
|
+
}): Promise<Uint8Array>;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ConsoleLogger, type PdfLogger, PlaywrightPdfService };
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/logger.ts","../src/playwright-pdf.service.ts"],"sourcesContent":[],"mappings":";UAAiB,SAAA;EAAA,KAAA,CAAA,OAAS,EAAA,MAAA,CAAA,EAAA,IAAA;EAOb,GAAA,CAAA,OAAA,EAAA,MAAc,CAAA,EAAA,IAAA;;;;ACGd,cDHA,aAAA,YAAyB,SCGL,CAAA;EAGX,KAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAwCR,GAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAR,IAAA,CAAA,OAAA,EAAA,MAAA,CAAA,EAAA,IAAA;EAAO,KAAA,CAAA,OAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,OAAA,CAAA,EAAA,IAAA;;;;ADrDI,cCUJ,oBAAA,CDVa;EAOb,iBAAc,MAAA;uBCML;;;IAHT,IAAA,CAAA,EAAA,MAAA;IAGS,GAAA,CAAA,EAAA,MAAA;IAwCR,UAAA,EAAA,KAAA,GAAA,YAAA;IAAR,QAAA,CAAA,EAAA,OAAA;EAAO,CAAA,CAAA,EAAP,OAAO,CAAC,UAAD,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
let node_fs_promises = require("node:fs/promises");
|
|
25
|
+
node_fs_promises = __toESM(node_fs_promises);
|
|
26
|
+
let playwright = require("playwright");
|
|
27
|
+
playwright = __toESM(playwright);
|
|
28
|
+
|
|
29
|
+
//#region src/logger.ts
|
|
30
|
+
var ConsoleLogger = class {
|
|
31
|
+
debug(message) {
|
|
32
|
+
if (process.env.NODE_ENV === "development") console.debug(`[PDF Printer] ${message}`);
|
|
33
|
+
}
|
|
34
|
+
log(message) {
|
|
35
|
+
console.log(`[PDF Printer] ${message}`);
|
|
36
|
+
}
|
|
37
|
+
warn(message) {
|
|
38
|
+
console.warn(`[PDF Printer] ${message}`);
|
|
39
|
+
}
|
|
40
|
+
error(message, error) {
|
|
41
|
+
console.error(`[PDF Printer] ${message}`, error);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/playwright-pdf.service.ts
|
|
47
|
+
const A4_WIDTH = 794;
|
|
48
|
+
const A4_HEIGHT = 1123;
|
|
49
|
+
var PlaywrightPdfService = class {
|
|
50
|
+
constructor(logger = new ConsoleLogger()) {
|
|
51
|
+
this.logger = logger;
|
|
52
|
+
}
|
|
53
|
+
async launchBrowser() {
|
|
54
|
+
const launchOptions = {
|
|
55
|
+
headless: true,
|
|
56
|
+
args: [
|
|
57
|
+
"--no-sandbox",
|
|
58
|
+
"--disable-setuid-sandbox",
|
|
59
|
+
"--disable-web-security",
|
|
60
|
+
"--disable-dev-shm-usage",
|
|
61
|
+
"--disable-blink-features=AutomationControlled",
|
|
62
|
+
"--disable-features=IsolateOrigins,site-per-process"
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
if (process.env.CHROMIUM_EXECUTABLE_PATH) try {
|
|
66
|
+
await (0, node_fs_promises.access)(process.env.CHROMIUM_EXECUTABLE_PATH);
|
|
67
|
+
launchOptions.executablePath = process.env.CHROMIUM_EXECUTABLE_PATH;
|
|
68
|
+
this.logger.debug(`Using system Chromium at: ${process.env.CHROMIUM_EXECUTABLE_PATH}`);
|
|
69
|
+
} catch {
|
|
70
|
+
this.logger.warn(`Chromium path specified but not found: ${process.env.CHROMIUM_EXECUTABLE_PATH}, using Playwright's bundled browser`);
|
|
71
|
+
}
|
|
72
|
+
return playwright.chromium.launch(launchOptions);
|
|
73
|
+
}
|
|
74
|
+
async render(input) {
|
|
75
|
+
let browser = null;
|
|
76
|
+
try {
|
|
77
|
+
browser = await this.launchBrowser();
|
|
78
|
+
const page = await (await browser.newContext({
|
|
79
|
+
colorScheme: input.darkMode ? "dark" : "light",
|
|
80
|
+
locale: "en-US",
|
|
81
|
+
reducedMotion: "reduce",
|
|
82
|
+
viewport: {
|
|
83
|
+
width: A4_WIDTH,
|
|
84
|
+
height: A4_HEIGHT
|
|
85
|
+
},
|
|
86
|
+
isMobile: false,
|
|
87
|
+
hasTouch: false,
|
|
88
|
+
deviceScaleFactor: 2,
|
|
89
|
+
bypassCSP: true
|
|
90
|
+
})).newPage();
|
|
91
|
+
page.on("console", (msg) => this.logger.debug(`[Browser Log] ${msg.text()}`));
|
|
92
|
+
page.on("requestfailed", (request) => {
|
|
93
|
+
this.logger.error(`[Browser Error] Failed to load resource: ${request.url()} - ${request.failure()?.errorText}`);
|
|
94
|
+
});
|
|
95
|
+
if (input.url) {
|
|
96
|
+
await page.goto(input.url, {
|
|
97
|
+
waitUntil: "load",
|
|
98
|
+
timeout: 6e4
|
|
99
|
+
});
|
|
100
|
+
await page.waitForLoadState("networkidle", { timeout: 6e4 });
|
|
101
|
+
} else if (input.html) {
|
|
102
|
+
await page.setContent(input.html, {
|
|
103
|
+
waitUntil: "load",
|
|
104
|
+
timeout: 6e4
|
|
105
|
+
});
|
|
106
|
+
await page.waitForLoadState("networkidle", { timeout: 6e4 });
|
|
107
|
+
}
|
|
108
|
+
if (input.darkMode) await page.evaluate(() => {
|
|
109
|
+
document.documentElement.classList.add("dark");
|
|
110
|
+
document.body.classList.add("dark");
|
|
111
|
+
});
|
|
112
|
+
if (input.outputType === "pdf") await page.emulateMedia({ media: "print" });
|
|
113
|
+
this.logger.log(`Page content loaded, generating ${input.outputType}...`);
|
|
114
|
+
if (input.outputType === "screenshot") {
|
|
115
|
+
const screenshotBuffer = await page.screenshot({
|
|
116
|
+
fullPage: true,
|
|
117
|
+
type: "png",
|
|
118
|
+
scale: "css",
|
|
119
|
+
animations: "disabled",
|
|
120
|
+
caret: "hide",
|
|
121
|
+
omitBackground: true
|
|
122
|
+
});
|
|
123
|
+
return new Uint8Array(screenshotBuffer);
|
|
124
|
+
}
|
|
125
|
+
const pdfBuffer = await page.pdf({
|
|
126
|
+
format: "A4",
|
|
127
|
+
printBackground: true,
|
|
128
|
+
preferCSSPageSize: true,
|
|
129
|
+
displayHeaderFooter: false,
|
|
130
|
+
outline: false,
|
|
131
|
+
scale: 1,
|
|
132
|
+
margin: {
|
|
133
|
+
top: "0px",
|
|
134
|
+
right: "0px",
|
|
135
|
+
bottom: "0px",
|
|
136
|
+
left: "0px"
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return new Uint8Array(pdfBuffer);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this.logger.error("Playwright error:", error);
|
|
142
|
+
if (error instanceof Error && error.message.includes("browser")) {
|
|
143
|
+
this.logger.error("\n💡 Troubleshooting tips:");
|
|
144
|
+
this.logger.error(" 1. Make sure Chromium is installed");
|
|
145
|
+
this.logger.error(" 2. Try running: bunx playwright install chromium");
|
|
146
|
+
this.logger.error(" 3. Check system permissions");
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
} finally {
|
|
150
|
+
if (browser) await browser.close();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
exports.ConsoleLogger = ConsoleLogger;
|
|
157
|
+
exports.PlaywrightPdfService = PlaywrightPdfService;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { chromium } from "playwright";
|
|
3
|
+
|
|
4
|
+
//#region src/logger.ts
|
|
5
|
+
var ConsoleLogger = class {
|
|
6
|
+
debug(message) {
|
|
7
|
+
if (process.env.NODE_ENV === "development") console.debug(`[PDF Printer] ${message}`);
|
|
8
|
+
}
|
|
9
|
+
log(message) {
|
|
10
|
+
console.log(`[PDF Printer] ${message}`);
|
|
11
|
+
}
|
|
12
|
+
warn(message) {
|
|
13
|
+
console.warn(`[PDF Printer] ${message}`);
|
|
14
|
+
}
|
|
15
|
+
error(message, error) {
|
|
16
|
+
console.error(`[PDF Printer] ${message}`, error);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/playwright-pdf.service.ts
|
|
22
|
+
const A4_WIDTH = 794;
|
|
23
|
+
const A4_HEIGHT = 1123;
|
|
24
|
+
var PlaywrightPdfService = class {
|
|
25
|
+
constructor(logger = new ConsoleLogger()) {
|
|
26
|
+
this.logger = logger;
|
|
27
|
+
}
|
|
28
|
+
async launchBrowser() {
|
|
29
|
+
const launchOptions = {
|
|
30
|
+
headless: true,
|
|
31
|
+
args: [
|
|
32
|
+
"--no-sandbox",
|
|
33
|
+
"--disable-setuid-sandbox",
|
|
34
|
+
"--disable-web-security",
|
|
35
|
+
"--disable-dev-shm-usage",
|
|
36
|
+
"--disable-blink-features=AutomationControlled",
|
|
37
|
+
"--disable-features=IsolateOrigins,site-per-process"
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
if (process.env.CHROMIUM_EXECUTABLE_PATH) try {
|
|
41
|
+
await access(process.env.CHROMIUM_EXECUTABLE_PATH);
|
|
42
|
+
launchOptions.executablePath = process.env.CHROMIUM_EXECUTABLE_PATH;
|
|
43
|
+
this.logger.debug(`Using system Chromium at: ${process.env.CHROMIUM_EXECUTABLE_PATH}`);
|
|
44
|
+
} catch {
|
|
45
|
+
this.logger.warn(`Chromium path specified but not found: ${process.env.CHROMIUM_EXECUTABLE_PATH}, using Playwright's bundled browser`);
|
|
46
|
+
}
|
|
47
|
+
return chromium.launch(launchOptions);
|
|
48
|
+
}
|
|
49
|
+
async render(input) {
|
|
50
|
+
let browser = null;
|
|
51
|
+
try {
|
|
52
|
+
browser = await this.launchBrowser();
|
|
53
|
+
const page = await (await browser.newContext({
|
|
54
|
+
colorScheme: input.darkMode ? "dark" : "light",
|
|
55
|
+
locale: "en-US",
|
|
56
|
+
reducedMotion: "reduce",
|
|
57
|
+
viewport: {
|
|
58
|
+
width: A4_WIDTH,
|
|
59
|
+
height: A4_HEIGHT
|
|
60
|
+
},
|
|
61
|
+
isMobile: false,
|
|
62
|
+
hasTouch: false,
|
|
63
|
+
deviceScaleFactor: 2,
|
|
64
|
+
bypassCSP: true
|
|
65
|
+
})).newPage();
|
|
66
|
+
page.on("console", (msg) => this.logger.debug(`[Browser Log] ${msg.text()}`));
|
|
67
|
+
page.on("requestfailed", (request) => {
|
|
68
|
+
this.logger.error(`[Browser Error] Failed to load resource: ${request.url()} - ${request.failure()?.errorText}`);
|
|
69
|
+
});
|
|
70
|
+
if (input.url) {
|
|
71
|
+
await page.goto(input.url, {
|
|
72
|
+
waitUntil: "load",
|
|
73
|
+
timeout: 6e4
|
|
74
|
+
});
|
|
75
|
+
await page.waitForLoadState("networkidle", { timeout: 6e4 });
|
|
76
|
+
} else if (input.html) {
|
|
77
|
+
await page.setContent(input.html, {
|
|
78
|
+
waitUntil: "load",
|
|
79
|
+
timeout: 6e4
|
|
80
|
+
});
|
|
81
|
+
await page.waitForLoadState("networkidle", { timeout: 6e4 });
|
|
82
|
+
}
|
|
83
|
+
if (input.darkMode) await page.evaluate(() => {
|
|
84
|
+
document.documentElement.classList.add("dark");
|
|
85
|
+
document.body.classList.add("dark");
|
|
86
|
+
});
|
|
87
|
+
if (input.outputType === "pdf") await page.emulateMedia({ media: "print" });
|
|
88
|
+
this.logger.log(`Page content loaded, generating ${input.outputType}...`);
|
|
89
|
+
if (input.outputType === "screenshot") {
|
|
90
|
+
const screenshotBuffer = await page.screenshot({
|
|
91
|
+
fullPage: true,
|
|
92
|
+
type: "png",
|
|
93
|
+
scale: "css",
|
|
94
|
+
animations: "disabled",
|
|
95
|
+
caret: "hide",
|
|
96
|
+
omitBackground: true
|
|
97
|
+
});
|
|
98
|
+
return new Uint8Array(screenshotBuffer);
|
|
99
|
+
}
|
|
100
|
+
const pdfBuffer = await page.pdf({
|
|
101
|
+
format: "A4",
|
|
102
|
+
printBackground: true,
|
|
103
|
+
preferCSSPageSize: true,
|
|
104
|
+
displayHeaderFooter: false,
|
|
105
|
+
outline: false,
|
|
106
|
+
scale: 1,
|
|
107
|
+
margin: {
|
|
108
|
+
top: "0px",
|
|
109
|
+
right: "0px",
|
|
110
|
+
bottom: "0px",
|
|
111
|
+
left: "0px"
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return new Uint8Array(pdfBuffer);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
this.logger.error("Playwright error:", error);
|
|
117
|
+
if (error instanceof Error && error.message.includes("browser")) {
|
|
118
|
+
this.logger.error("\n💡 Troubleshooting tips:");
|
|
119
|
+
this.logger.error(" 1. Make sure Chromium is installed");
|
|
120
|
+
this.logger.error(" 2. Try running: bunx playwright install chromium");
|
|
121
|
+
this.logger.error(" 3. Check system permissions");
|
|
122
|
+
}
|
|
123
|
+
throw error;
|
|
124
|
+
} finally {
|
|
125
|
+
if (browser) await browser.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
export { ConsoleLogger, PlaywrightPdfService };
|
|
132
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["launchOptions: Parameters<typeof chromium.launch>[0]","browser: Browser | null"],"sources":["../src/logger.ts","../src/playwright-pdf.service.ts"],"sourcesContent":["export interface PdfLogger {\n debug(message: string): void;\n log(message: string): void;\n warn(message: string): void;\n error(message: string, error?: unknown): void;\n}\n\nexport class ConsoleLogger implements PdfLogger {\n debug(message: string): void {\n if (process.env.NODE_ENV === 'development') {\n console.debug(`[PDF Printer] ${message}`);\n }\n }\n\n log(message: string): void {\n console.log(`[PDF Printer] ${message}`);\n }\n\n warn(message: string): void {\n console.warn(`[PDF Printer] ${message}`);\n }\n\n error(message: string, error?: unknown): void {\n console.error(`[PDF Printer] ${message}`, error);\n }\n}\n","import { access } from 'node:fs/promises';\nimport { type Browser, chromium } from 'playwright';\nimport type { PdfLogger } from './logger';\nimport { ConsoleLogger } from './logger';\n\n// A4 dimensions in pixels at 96 DPI (standard web resolution)\n// A4 = 210mm × 297mm = 8.27\" × 11.69\" = 794px × 1123px at 96 DPI\nconst A4_WIDTH = 794;\nconst A4_HEIGHT = 1123;\n\nexport class PlaywrightPdfService {\n private readonly logger: PdfLogger;\n\n constructor(logger: PdfLogger = new ConsoleLogger()) {\n this.logger = logger;\n }\n\n private async launchBrowser(): Promise<Browser> {\n const launchOptions: Parameters<typeof chromium.launch>[0] = {\n headless: true,\n args: [\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-web-security',\n '--disable-dev-shm-usage',\n '--disable-blink-features=AutomationControlled',\n // Bypass CSP (Content Security Policy)\n '--disable-features=IsolateOrigins,site-per-process',\n ],\n };\n\n if (process.env.CHROMIUM_EXECUTABLE_PATH) {\n try {\n await access(process.env.CHROMIUM_EXECUTABLE_PATH);\n launchOptions.executablePath = process.env.CHROMIUM_EXECUTABLE_PATH;\n this.logger.debug(\n `Using system Chromium at: ${process.env.CHROMIUM_EXECUTABLE_PATH}`,\n );\n } catch {\n this.logger.warn(\n `Chromium path specified but not found: ${process.env.CHROMIUM_EXECUTABLE_PATH}, using Playwright's bundled browser`,\n );\n }\n }\n\n return chromium.launch(launchOptions);\n }\n\n async render(input: {\n html?: string;\n url?: string;\n outputType: 'pdf' | 'screenshot';\n darkMode?: boolean;\n }): Promise<Uint8Array> {\n let browser: Browser | null = null;\n\n try {\n browser = await this.launchBrowser();\n\n const context = await browser.newContext({\n colorScheme: input.darkMode ? 'dark' : 'light',\n locale: 'en-US',\n reducedMotion: 'reduce',\n viewport: { width: A4_WIDTH, height: A4_HEIGHT },\n // Mobile and touch settings\n isMobile: false,\n hasTouch: false,\n // Best quality with device scale factor\n deviceScaleFactor: 2, // High DPI for crisp rendering\n // Bypass CSP\n bypassCSP: true,\n });\n\n const page = await context.newPage();\n\n page.on('console', (msg) =>\n this.logger.debug(`[Browser Log] ${msg.text()}`),\n );\n page.on('requestfailed', (request) => {\n this.logger.error(\n `[Browser Error] Failed to load resource: ${request.url()} - ${request.failure()?.errorText}`,\n );\n });\n\n if (input.url) {\n await page.goto(input.url, {\n waitUntil: 'load',\n timeout: 60000,\n });\n // Wait for network to be idle to ensure all resources are loaded\n await page.waitForLoadState('networkidle', { timeout: 60000 });\n } else if (input.html) {\n await page.setContent(input.html, {\n waitUntil: 'load',\n timeout: 60000,\n });\n // Wait for network to be idle (for any resources in the HTML)\n await page.waitForLoadState('networkidle', { timeout: 60000 });\n }\n\n // Apply dark mode class for Theme component system\n // (Playwright's colorScheme already handles @media (prefers-color-scheme: dark))\n if (input.darkMode) {\n await page.evaluate(() => {\n document.documentElement.classList.add('dark');\n document.body.classList.add('dark');\n });\n }\n\n // For PDF generation, emulate print media to apply @media print styles\n if (input.outputType === 'pdf') {\n await page.emulateMedia({ media: 'print' });\n }\n\n this.logger.log(`Page content loaded, generating ${input.outputType}...`);\n\n if (input.outputType === 'screenshot') {\n const screenshotBuffer = await page.screenshot({\n fullPage: true,\n type: 'png',\n scale: 'css',\n animations: 'disabled',\n caret: 'hide',\n omitBackground: true, // Transparent background\n });\n return new Uint8Array(screenshotBuffer);\n }\n const pdfBuffer = await page.pdf({\n format: 'A4',\n printBackground: true,\n preferCSSPageSize: true,\n displayHeaderFooter: false,\n outline: false,\n scale: 1,\n margin: {\n top: '0px',\n right: '0px',\n bottom: '0px',\n left: '0px',\n },\n });\n return new Uint8Array(pdfBuffer);\n } catch (error) {\n this.logger.error('Playwright error:', error);\n if (error instanceof Error && error.message.includes('browser')) {\n this.logger.error('\\n💡 Troubleshooting tips:');\n this.logger.error(' 1. Make sure Chromium is installed');\n this.logger.error(\n ' 2. Try running: bunx playwright install chromium',\n );\n this.logger.error(' 3. Check system permissions');\n }\n throw error;\n } finally {\n if (browser) await browser.close();\n }\n }\n}\n"],"mappings":";;;;AAOA,IAAa,gBAAb,MAAgD;CAC9C,MAAM,SAAuB;AAC3B,MAAI,QAAQ,IAAI,aAAa,cAC3B,SAAQ,MAAM,iBAAiB,UAAU;;CAI7C,IAAI,SAAuB;AACzB,UAAQ,IAAI,iBAAiB,UAAU;;CAGzC,KAAK,SAAuB;AAC1B,UAAQ,KAAK,iBAAiB,UAAU;;CAG1C,MAAM,SAAiB,OAAuB;AAC5C,UAAQ,MAAM,iBAAiB,WAAW,MAAM;;;;;;AChBpD,MAAM,WAAW;AACjB,MAAM,YAAY;AAElB,IAAa,uBAAb,MAAkC;CAGhC,YAAY,SAAoB,IAAI,eAAe,EAAE;AACnD,OAAK,SAAS;;CAGhB,MAAc,gBAAkC;EAC9C,MAAMA,gBAAuD;GAC3D,UAAU;GACV,MAAM;IACJ;IACA;IACA;IACA;IACA;IAEA;IACD;GACF;AAED,MAAI,QAAQ,IAAI,yBACd,KAAI;AACF,SAAM,OAAO,QAAQ,IAAI,yBAAyB;AAClD,iBAAc,iBAAiB,QAAQ,IAAI;AAC3C,QAAK,OAAO,MACV,6BAA6B,QAAQ,IAAI,2BAC1C;UACK;AACN,QAAK,OAAO,KACV,0CAA0C,QAAQ,IAAI,yBAAyB,sCAChF;;AAIL,SAAO,SAAS,OAAO,cAAc;;CAGvC,MAAM,OAAO,OAKW;EACtB,IAAIC,UAA0B;AAE9B,MAAI;AACF,aAAU,MAAM,KAAK,eAAe;GAgBpC,MAAM,OAAO,OAdG,MAAM,QAAQ,WAAW;IACvC,aAAa,MAAM,WAAW,SAAS;IACvC,QAAQ;IACR,eAAe;IACf,UAAU;KAAE,OAAO;KAAU,QAAQ;KAAW;IAEhD,UAAU;IACV,UAAU;IAEV,mBAAmB;IAEnB,WAAW;IACZ,CAAC,EAEyB,SAAS;AAEpC,QAAK,GAAG,YAAY,QAClB,KAAK,OAAO,MAAM,iBAAiB,IAAI,MAAM,GAAG,CACjD;AACD,QAAK,GAAG,kBAAkB,YAAY;AACpC,SAAK,OAAO,MACV,4CAA4C,QAAQ,KAAK,CAAC,KAAK,QAAQ,SAAS,EAAE,YACnF;KACD;AAEF,OAAI,MAAM,KAAK;AACb,UAAM,KAAK,KAAK,MAAM,KAAK;KACzB,WAAW;KACX,SAAS;KACV,CAAC;AAEF,UAAM,KAAK,iBAAiB,eAAe,EAAE,SAAS,KAAO,CAAC;cACrD,MAAM,MAAM;AACrB,UAAM,KAAK,WAAW,MAAM,MAAM;KAChC,WAAW;KACX,SAAS;KACV,CAAC;AAEF,UAAM,KAAK,iBAAiB,eAAe,EAAE,SAAS,KAAO,CAAC;;AAKhE,OAAI,MAAM,SACR,OAAM,KAAK,eAAe;AACxB,aAAS,gBAAgB,UAAU,IAAI,OAAO;AAC9C,aAAS,KAAK,UAAU,IAAI,OAAO;KACnC;AAIJ,OAAI,MAAM,eAAe,MACvB,OAAM,KAAK,aAAa,EAAE,OAAO,SAAS,CAAC;AAG7C,QAAK,OAAO,IAAI,mCAAmC,MAAM,WAAW,KAAK;AAEzE,OAAI,MAAM,eAAe,cAAc;IACrC,MAAM,mBAAmB,MAAM,KAAK,WAAW;KAC7C,UAAU;KACV,MAAM;KACN,OAAO;KACP,YAAY;KACZ,OAAO;KACP,gBAAgB;KACjB,CAAC;AACF,WAAO,IAAI,WAAW,iBAAiB;;GAEzC,MAAM,YAAY,MAAM,KAAK,IAAI;IAC/B,QAAQ;IACR,iBAAiB;IACjB,mBAAmB;IACnB,qBAAqB;IACrB,SAAS;IACT,OAAO;IACP,QAAQ;KACN,KAAK;KACL,OAAO;KACP,QAAQ;KACR,MAAM;KACP;IACF,CAAC;AACF,UAAO,IAAI,WAAW,UAAU;WACzB,OAAO;AACd,QAAK,OAAO,MAAM,qBAAqB,MAAM;AAC7C,OAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,UAAU,EAAE;AAC/D,SAAK,OAAO,MAAM,6BAA6B;AAC/C,SAAK,OAAO,MAAM,wCAAwC;AAC1D,SAAK,OAAO,MACV,sDACD;AACD,SAAK,OAAO,MAAM,iCAAiC;;AAErD,SAAM;YACE;AACR,OAAI,QAAS,OAAM,QAAQ,OAAO"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ahmedrowaihi/pdf-forge-printer",
|
|
3
|
+
"version": "1.0.0-canary.0",
|
|
4
|
+
"description": "Playwright-based PDF and screenshot rendering service",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"playwright": "^1.57.0"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "22.14.1",
|
|
18
|
+
"typescript": "5.9.3",
|
|
19
|
+
"tsdown": "0.15.12",
|
|
20
|
+
"tsconfig": "1.0.0-canary.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsdown src/index.ts --format esm,cjs --dts --external playwright",
|
|
24
|
+
"clean": "rm -rf dist"
|
|
25
|
+
}
|
|
26
|
+
}
|