@iqx-limited/quick-pdf
Version:
Converting PDFs to images (📃 to 📸)
477 lines (468 loc) • 15.3 kB
JavaScript
;
var semver = require('semver');
var node_url = require('node:url');
var node_path = require('node:path');
var promises = require('node:fs/promises');
var puppeteer = require('puppeteer');
var imageSize = require('image-size');
var PDFDocument = require('pdfkit');
var node_fs = require('node:fs');
var node = require('html-validate/node');
let firefox = null;
const BROWSER_PATHS = {
firefox: {
linux: "/usr/bin/firefox",
mac: "/Applications/Firefox.app/Contents/MacOS/firefox",
windows: node_path.join("C:", "Program Files", "Mozilla Firefox", "firefox.exe")
}
};
const getOS = () => {
if (process.platform === "win32")
return "windows";
if (process.platform === "darwin")
return "mac";
return "linux";
};
const isBrowserInstalled = async (browser) => {
const os = getOS();
const browserPath = BROWSER_PATHS[browser][os];
try {
await promises.access(browserPath);
return true;
}
catch {
return false;
}
};
async function launchBrowser(browserType) {
return new Promise(async (resolve) => {
if (firefox)
return resolve(firefox);
if (!firefox) {
const os = getOS();
const executablePath = BROWSER_PATHS[browserType][os];
if (!(await isBrowserInstalled(browserType))) {
console.error(`${browserType.toUpperCase()} is not installed.`);
process.exit(1);
}
const browser = await puppeteer.launch({
browser: browserType,
headless: "shell",
executablePath,
args: ["--no-sandbox", "--disable-setuid-sandbox"]
});
{
firefox = browser;
}
return resolve(browser);
}
throw new Error(`Browser type ${browserType} is not supported`);
});
}
async function closeBrowsers() {
try {
if (firefox) {
await (await firefox).close();
firefox = null;
}
}
catch (err) {
console.error("Error closing browsers in @iqx-limited/quick-form:", err);
}
}
process.on("exit", async () => {
await closeBrowsers();
});
process.on("SIGINT", async () => {
console.log("SIGINT received. Closing browsers...");
await closeBrowsers();
});
process.on("SIGTERM", async () => {
console.log("SIGTERM received. Closing browsers...");
await closeBrowsers();
});
const pagePoolSize$1 = 5;
const RESOURCE_LIMIT$1 = 100;
let resourceCount$1 = 0;
let pagePool$1 = [];
let browser$1 = null;
async function launchPages$1() {
if (pagePool$1.length > 0) {
return pagePool$1;
}
pagePool$1 = await Promise.all(Array.from({ length: pagePoolSize$1 }, async () => {
if (browser$1) {
const page = await browser$1.newPage();
await page.setRequestInterception(true);
await page.setDefaultNavigationTimeout(10000);
await page.goto("about:blank");
page.on("request", request => {
resourceCount$1++;
if (resourceCount$1 > RESOURCE_LIMIT$1) {
page.reload();
resourceCount$1 = 0;
}
else {
request.continue();
}
});
return page;
}
else {
throw new Error("Browser not available");
}
}));
return pagePool$1;
}
const pdf2img = async (input, options = {}) => {
browser$1 = await launchBrowser("firefox");
if (!browser$1?.connected) {
throw new Error("Browser not available");
}
const pagePool = await launchPages$1();
let page = pagePool.pop();
let tempPage = false;
if (!page) {
tempPage = true;
page = await browser$1.newPage();
}
let path = "";
let address = "";
let tempFile = false;
if (Buffer.isBuffer(input)) {
path = node_path.resolve(process.cwd(), "temp.pdf");
address = node_url.pathToFileURL(path).toString();
tempFile = true;
await promises.writeFile(path, input);
}
else {
if (typeof input === "string" && input.startsWith("http")) {
path = input;
address = path;
}
else {
path = node_path.resolve(input.toString());
address = node_url.pathToFileURL(path).toString();
}
}
try {
await page.goto(address);
if (options.password) {
try {
await page.waitForSelector('input[type="password"]', {
visible: true,
timeout: 5000
});
console.log("Password prompt detected, entering password...");
await page.type('input[type="password"]', options.password || "");
await page.keyboard.press("Enter");
}
catch { }
}
await page.waitForSelector("canvas", { timeout: 5000 });
await page.waitForSelector(".loadingIcon", {
timeout: 5000,
hidden: true
});
const imageBuffers = [];
const pageCount = await page.evaluate(() => {
if (window.PDFViewerApplication) {
return window.PDFViewerApplication.pagesCount;
}
return 0;
});
const metadata = await page.evaluate(() => {
const app = window.PDFViewerApplication;
if (app && app.pdfDocument) {
return app.documentInfo ?? {};
}
return {};
});
const pdfDimensions = await page.evaluate(() => {
const canvas = document.querySelector("canvas");
const { width, height } = canvas.getBoundingClientRect();
return { width, height };
});
await page.setViewport({
width: pdfDimensions.width,
height: pdfDimensions.height,
deviceScaleFactor: 1
});
for (let i = 1; i <= pageCount; i++) {
await page.evaluate((pageNum) => {
if (window.PDFViewerApplication) {
window.PDFViewerApplication.page = pageNum;
}
}, i);
const pageBoundingBox = await page.evaluate(() => {
const pageElement = document.querySelector("canvas");
const { x, y, width, height } = pageElement.getBoundingClientRect();
return { x, y, width, height };
});
const screenshotOptions = {
fullPage: false,
type: options.type ?? "png",
clip: {
x: pageBoundingBox.x,
y: pageBoundingBox.y,
width: pageBoundingBox.width,
height: pageBoundingBox.height
}
};
if (options.type && options.type !== "png") {
screenshotOptions.quality = options.quality ?? 100;
}
const uint8array = await page.screenshot(screenshotOptions);
imageBuffers.push(Buffer.from(uint8array));
}
if (tempFile) {
await promises.unlink(path);
}
if (tempPage) {
page.close();
}
else {
await page.setViewport({
width: 800,
height: 600,
deviceScaleFactor: 1
});
pagePool.push(page);
}
return {
length: pageCount,
metadata: metadata.info,
pages: imageBuffers
};
}
catch (error) {
if (tempFile) {
await promises.unlink(path);
}
if (tempPage) {
page.close();
}
else {
await page.setViewport({
width: 800,
height: 600,
deviceScaleFactor: 1
});
pagePool.push(page);
}
throw error;
}
};
async function getBuffer(input) {
if (input instanceof Buffer) {
return input;
}
return fetch(input.toString())
.then(res => {
if (res.ok) {
return res.arrayBuffer();
}
else {
throw new Error("Failed to Fetch the File");
}
})
.then(array => Buffer.from(array))
.catch(() => {
if (node_fs.existsSync(input.toString())) {
return node_fs.readFileSync(input.toString());
}
throw new Error("Failed to Fetch the File");
});
}
const fetchHtmlFromUrl = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch content from URL: ${url}`);
}
return await response.text();
};
const readHtmlFromFilePath = async (filePath) => {
return node_fs.readFileSync(filePath, "utf-8");
};
const img2pdf = async (input, options = {}) => {
const { fileTypeFromBuffer } = await import('file-type');
return new Promise((resolve, reject) => {
getBuffer(input).then(async (buf) => {
const type = await fileTypeFromBuffer(buf);
if (type?.mime !== "image/jpeg" && type?.mime !== "image/png") {
throw new Error("Provided File is not a JPEG or a PNG.");
}
const pdfBuffers = [];
const imgSize = imageSize.imageSize(buf);
const landscape = imgSize.width && imgSize.height ? imgSize.width > imgSize.height : false;
const doc = new PDFDocument({
size: "a4",
layout: landscape ? "landscape" : "portrait",
margins: {
top: 0,
bottom: 0,
left: 0,
right: 0
}
});
doc.on("data", (data) => {
pdfBuffers.push(data);
});
doc.on("end", () => {
resolve(Buffer.concat(pdfBuffers));
});
doc.fontSize(options.fontSize ?? 10);
const topMargin = options.header ? 20 : 0;
const bottomMargin = options.footer ? 20 : 0;
const sidePadding = 20;
const imageHeight = doc.page.height - topMargin - bottomMargin;
if (options.header) {
doc.text(options.header, sidePadding, topMargin / 2 - 6, {
align: "center",
baseline: "top",
width: doc.page.width - 2 * sidePadding,
height: topMargin - 5,
ellipsis: true
}).moveDown(0.5);
}
doc.image(buf, 0, topMargin, {
width: doc.page.width,
height: imageHeight
});
if (options.footer) {
doc.text(options.footer, sidePadding, doc.page.height - bottomMargin / 2 - 6, {
align: "center",
width: doc.page.width - 2 * sidePadding,
height: bottomMargin - 5,
ellipsis: true
});
}
doc.end();
}).catch((e) => {
reject(e);
});
});
};
const pagePoolSize = 5;
const RESOURCE_LIMIT = 100;
let resourceCount = 0;
let pagePool = [];
let browser = null;
async function launchPages() {
if (pagePool.length > 0) {
return pagePool;
}
pagePool = await Promise.all(Array.from({ length: pagePoolSize }, async () => {
if (browser && browser.connected) {
const page = await browser.newPage();
await page.setRequestInterception(true);
await page.setDefaultNavigationTimeout(10000);
await page.goto("about:blank");
page.on("request", request => {
resourceCount++;
if (resourceCount > RESOURCE_LIMIT) {
page.reload();
resourceCount = 0;
}
else {
request.continue();
}
});
return page;
}
else {
throw new Error("Browser not available");
}
}));
return pagePool;
}
const html2pdf = async (input, options = {}) => {
const validator = new node.HtmlValidate(options.rules ?? {
extends: ["html-validate:standard"],
rules: {
"no-trailing-whitespace": "off"
}
});
let htmlContent = input.toString();
if (htmlContent.startsWith("http://") || htmlContent.startsWith("https://")) {
htmlContent = await fetchHtmlFromUrl(htmlContent);
}
else if (node_fs.existsSync(input)) {
htmlContent = await readHtmlFromFilePath(htmlContent);
}
browser = await launchBrowser("firefox");
if (!browser?.connected) {
throw new Error("Browser not available");
}
const pagePool = await launchPages();
let page = pagePool.pop();
let tempPage = false;
if (!page) {
tempPage = true;
page = await browser.newPage();
}
const validation = (options.validation ?? true);
try {
const res = validation ? await validator.validateString(htmlContent) : { valid: true };
if (res.valid) {
await page.setContent(htmlContent, { waitUntil: "load" });
const pdf = await page.pdf({
format: "A4",
printBackground: true
});
const pdfBuffer = Buffer.from(pdf);
if (options.base64 ?? false) {
return pdfBuffer.toString("base64");
}
return pdfBuffer;
}
else {
throw {
valid: false,
count: {
errors: res.errorCount,
warnings: res.warningCount
},
validation: res.results.map(res => {
return {
file: res.filePath,
count: {
errors: res.errorCount,
warnings: res.warningCount
},
messages: res.messages.map(msg => {
return {
message: msg.message,
line: msg.line,
column: msg.column,
ruleId: msg.ruleId
};
})
};
})
};
}
}
finally {
if (tempPage) {
page.close();
}
else {
await page.setViewport({
width: 800,
height: 600,
deviceScaleFactor: 1
});
pagePool.push(page);
}
}
};
const requiredVersion = ">=20.0.0";
if (!semver.satisfies(process.version, requiredVersion)) {
console.error(`\nError: Node.js version ${requiredVersion} is required. You are using ${process.version}.\n`);
process.exit(1);
}
exports.html2pdf = html2pdf;
exports.img2pdf = img2pdf;
exports.pdf2img = pdf2img;
//# sourceMappingURL=index.cjs.map