@kitsy/pdf 0.0.0-stage → 0.2.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/CHANGELOG.md +32 -0
- package/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +256 -2
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/dist/chunk-2K5X7L5J.js +43 -0
- package/dist/chunk-2K5X7L5J.js.map +1 -0
- package/dist/chunk-4EAYBJ6G.js +362 -0
- package/dist/chunk-4EAYBJ6G.js.map +1 -0
- package/dist/chunk-4LBJTDV5.js +165 -0
- package/dist/chunk-4LBJTDV5.js.map +1 -0
- package/dist/chunk-4ZEZ2GLD.js +353 -0
- package/dist/chunk-4ZEZ2GLD.js.map +1 -0
- package/dist/chunk-5OIMR74G.js +273 -0
- package/dist/chunk-5OIMR74G.js.map +1 -0
- package/dist/chunk-6NFNAKWA.js +257 -0
- package/dist/chunk-6NFNAKWA.js.map +1 -0
- package/dist/chunk-7MTEMQLL.js +47 -0
- package/dist/chunk-7MTEMQLL.js.map +1 -0
- package/dist/chunk-7V4SWTUK.js +83 -0
- package/dist/chunk-7V4SWTUK.js.map +1 -0
- package/dist/chunk-7XH7MFZ4.js +295 -0
- package/dist/chunk-7XH7MFZ4.js.map +1 -0
- package/dist/chunk-BK3MYSUS.js +61 -0
- package/dist/chunk-BK3MYSUS.js.map +1 -0
- package/dist/chunk-BLHNKR3S.js +35 -0
- package/dist/chunk-BLHNKR3S.js.map +1 -0
- package/dist/chunk-CCJ43SIX.js +342 -0
- package/dist/chunk-CCJ43SIX.js.map +1 -0
- package/dist/chunk-CNEDWATL.js +210 -0
- package/dist/chunk-CNEDWATL.js.map +1 -0
- package/dist/chunk-CYTWDJE2.js +152 -0
- package/dist/chunk-CYTWDJE2.js.map +1 -0
- package/dist/chunk-DKVOICYZ.js +45 -0
- package/dist/chunk-DKVOICYZ.js.map +1 -0
- package/dist/chunk-DPEJU4P6.js +79 -0
- package/dist/chunk-DPEJU4P6.js.map +1 -0
- package/dist/chunk-DQPABZTU.js +22 -0
- package/dist/chunk-DQPABZTU.js.map +1 -0
- package/dist/chunk-EKHEY3LI.js +46 -0
- package/dist/chunk-EKHEY3LI.js.map +1 -0
- package/dist/chunk-FIEYAKPI.js +421 -0
- package/dist/chunk-FIEYAKPI.js.map +1 -0
- package/dist/chunk-G467LFKC.js +191 -0
- package/dist/chunk-G467LFKC.js.map +1 -0
- package/dist/chunk-GLYWSVSB.js +250 -0
- package/dist/chunk-GLYWSVSB.js.map +1 -0
- package/dist/chunk-GXBIMUO7.js +135 -0
- package/dist/chunk-GXBIMUO7.js.map +1 -0
- package/dist/chunk-H6EERRCR.js +180 -0
- package/dist/chunk-H6EERRCR.js.map +1 -0
- package/dist/chunk-HDTM3LXD.js +35 -0
- package/dist/chunk-HDTM3LXD.js.map +1 -0
- package/dist/chunk-IGMX56C3.js +180 -0
- package/dist/chunk-IGMX56C3.js.map +1 -0
- package/dist/chunk-J4IRICCJ.js +74 -0
- package/dist/chunk-J4IRICCJ.js.map +1 -0
- package/dist/chunk-JXKQ7S6W.js +331 -0
- package/dist/chunk-JXKQ7S6W.js.map +1 -0
- package/dist/chunk-KY6DXS6S.js +34 -0
- package/dist/chunk-KY6DXS6S.js.map +1 -0
- package/dist/chunk-NOFGCYGS.js +22 -0
- package/dist/chunk-NOFGCYGS.js.map +1 -0
- package/dist/chunk-OSHUJRKD.js +131 -0
- package/dist/chunk-OSHUJRKD.js.map +1 -0
- package/dist/chunk-PMH7OWPF.js +40 -0
- package/dist/chunk-PMH7OWPF.js.map +1 -0
- package/dist/chunk-PRBKSX3A.js +299 -0
- package/dist/chunk-PRBKSX3A.js.map +1 -0
- package/dist/chunk-SVPUX7AM.js +48 -0
- package/dist/chunk-SVPUX7AM.js.map +1 -0
- package/dist/chunk-TR6HJ4NO.js +659 -0
- package/dist/chunk-TR6HJ4NO.js.map +1 -0
- package/dist/chunk-TV3E4YUF.js +69 -0
- package/dist/chunk-TV3E4YUF.js.map +1 -0
- package/dist/chunk-VCTN77K2.js +112 -0
- package/dist/chunk-VCTN77K2.js.map +1 -0
- package/dist/chunk-WEOPDD3P.js +120 -0
- package/dist/chunk-WEOPDD3P.js.map +1 -0
- package/dist/chunk-ZBZ7OC6I.js +228 -0
- package/dist/chunk-ZBZ7OC6I.js.map +1 -0
- package/dist/config-CJYo46X9.d.ts +19 -0
- package/dist/index.d.ts +517 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/react/certificate-sign.d.ts +5 -0
- package/dist/react/certificate-sign.js +14 -0
- package/dist/react/certificate-sign.js.map +1 -0
- package/dist/react/compress.d.ts +5 -0
- package/dist/react/compress.js +8 -0
- package/dist/react/compress.js.map +1 -0
- package/dist/react/fill-form.d.ts +5 -0
- package/dist/react/fill-form.js +8 -0
- package/dist/react/fill-form.js.map +1 -0
- package/dist/react/images-to-pdf.d.ts +5 -0
- package/dist/react/images-to-pdf.js +7 -0
- package/dist/react/images-to-pdf.js.map +1 -0
- package/dist/react/merge.d.ts +5 -0
- package/dist/react/merge.js +8 -0
- package/dist/react/merge.js.map +1 -0
- package/dist/react/metadata.d.ts +5 -0
- package/dist/react/metadata.js +8 -0
- package/dist/react/metadata.js.map +1 -0
- package/dist/react/organize.d.ts +5 -0
- package/dist/react/organize.js +12 -0
- package/dist/react/organize.js.map +1 -0
- package/dist/react/pdf-to-images.d.ts +5 -0
- package/dist/react/pdf-to-images.js +13 -0
- package/dist/react/pdf-to-images.js.map +1 -0
- package/dist/react/protect.d.ts +5 -0
- package/dist/react/protect.js +8 -0
- package/dist/react/protect.js.map +1 -0
- package/dist/react/sign.d.ts +5 -0
- package/dist/react/sign.js +13 -0
- package/dist/react/sign.js.map +1 -0
- package/dist/react/split.d.ts +5 -0
- package/dist/react/split.js +11 -0
- package/dist/react/split.js.map +1 -0
- package/dist/react/verify-signatures.d.ts +5 -0
- package/dist/react/verify-signatures.js +8 -0
- package/dist/react/verify-signatures.js.map +1 -0
- package/dist/react/watermark.d.ts +5 -0
- package/dist/react/watermark.js +10 -0
- package/dist/react/watermark.js.map +1 -0
- package/dist/react.d.ts +40 -0
- package/dist/react.js +40 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +9 -0
- package/dist/vite.js.map +1 -0
- package/package.json +163 -4
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { imagesToPdf, detectImageKind, getJpegOrientation } from './chunk-WEOPDD3P.js';
|
|
2
|
+
import { PdfToolPage, PdfToolColumns, SummaryList, SideTips, FileDropzone, iconButtonClass, fieldLabelClass, inputClass, StatusBanner, primaryButtonClass, secondaryButtonClass, BackToPdfTools } from './chunk-H6EERRCR.js';
|
|
3
|
+
import { formatBytes, toBaseName, downloadBlob } from './chunk-EKHEY3LI.js';
|
|
4
|
+
import { useState, useRef, useEffect, useMemo } from 'react';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var MAX_IMAGES = 100;
|
|
8
|
+
var MAX_TOTAL_BYTES = 200 * 1024 * 1024;
|
|
9
|
+
var MARGINS = [
|
|
10
|
+
{ value: 0, label: "None" },
|
|
11
|
+
{ value: 18, label: "Small" },
|
|
12
|
+
{ value: 36, label: "Medium" },
|
|
13
|
+
{ value: 72, label: "Large" }
|
|
14
|
+
];
|
|
15
|
+
var nextImageId = 0;
|
|
16
|
+
function isImageFile(file) {
|
|
17
|
+
return (file?.type || "").startsWith("image/") || /\.(jpe?g|png|webp|gif|bmp)$/i.test(file?.name || "");
|
|
18
|
+
}
|
|
19
|
+
async function rasterize(file, type) {
|
|
20
|
+
const bitmap = await createImageBitmap(file, { imageOrientation: "from-image" });
|
|
21
|
+
const canvas = document.createElement("canvas");
|
|
22
|
+
canvas.width = bitmap.width;
|
|
23
|
+
canvas.height = bitmap.height;
|
|
24
|
+
const context = canvas.getContext("2d");
|
|
25
|
+
if (type === "image/jpeg") {
|
|
26
|
+
context.fillStyle = "#ffffff";
|
|
27
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
28
|
+
}
|
|
29
|
+
context.drawImage(bitmap, 0, 0);
|
|
30
|
+
bitmap.close?.();
|
|
31
|
+
const blob = await new Promise(
|
|
32
|
+
(resolve, reject) => canvas.toBlob((result) => result ? resolve(result) : reject(new Error("encode failed")), type, 0.92)
|
|
33
|
+
);
|
|
34
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
35
|
+
}
|
|
36
|
+
async function toEmbeddableBytes(file) {
|
|
37
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
38
|
+
const kind = detectImageKind(bytes);
|
|
39
|
+
if (kind === "png") return bytes;
|
|
40
|
+
if (kind === "jpg") {
|
|
41
|
+
return getJpegOrientation(bytes) === 1 ? bytes : rasterize(file, "image/jpeg");
|
|
42
|
+
}
|
|
43
|
+
return rasterize(file, "image/png");
|
|
44
|
+
}
|
|
45
|
+
function ImagesToPdf() {
|
|
46
|
+
const [images, setImages] = useState([]);
|
|
47
|
+
const [pageSize, setPageSize] = useState("a4");
|
|
48
|
+
const [orientation, setOrientation] = useState("auto");
|
|
49
|
+
const [margin, setMargin] = useState(18);
|
|
50
|
+
const [outputName, setOutputName] = useState("images");
|
|
51
|
+
const [isConverting, setIsConverting] = useState(false);
|
|
52
|
+
const [status, setStatus] = useState({
|
|
53
|
+
tone: "info",
|
|
54
|
+
text: "Add JPG, PNG, or other images, arrange them, and turn them into one PDF."
|
|
55
|
+
});
|
|
56
|
+
const imagesRef = useRef(images);
|
|
57
|
+
imagesRef.current = images;
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
document.title = "Images to PDF | Apps";
|
|
60
|
+
return () => imagesRef.current.forEach((image) => URL.revokeObjectURL(image.previewUrl));
|
|
61
|
+
}, []);
|
|
62
|
+
const totalBytes = useMemo(() => images.reduce((sum, image) => sum + image.file.size, 0), [images]);
|
|
63
|
+
const addFiles = (files) => {
|
|
64
|
+
if (!files.length) return;
|
|
65
|
+
const accepted = files.filter(isImageFile);
|
|
66
|
+
if (!accepted.length) {
|
|
67
|
+
setStatus({ tone: "error", text: "Only image files are supported." });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (images.length + accepted.length > MAX_IMAGES) {
|
|
71
|
+
setStatus({ tone: "error", text: `Please add up to ${MAX_IMAGES} images at a time.` });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (totalBytes + accepted.reduce((sum, file) => sum + file.size, 0) > MAX_TOTAL_BYTES) {
|
|
75
|
+
setStatus({ tone: "error", text: `Total size must stay under ${formatBytes(MAX_TOTAL_BYTES)}.` });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
setImages((current) => [
|
|
79
|
+
...current,
|
|
80
|
+
...accepted.map((file) => ({
|
|
81
|
+
id: `img-${++nextImageId}`,
|
|
82
|
+
file,
|
|
83
|
+
previewUrl: URL.createObjectURL(file)
|
|
84
|
+
}))
|
|
85
|
+
]);
|
|
86
|
+
const skipped = files.length - accepted.length;
|
|
87
|
+
setStatus({
|
|
88
|
+
tone: "success",
|
|
89
|
+
text: `${accepted.length} image${accepted.length > 1 ? "s" : ""} added.${skipped ? ` ${skipped} non-image file${skipped > 1 ? "s" : ""} skipped.` : ""}`
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
const moveImage = (index, offset) => {
|
|
93
|
+
setImages((current) => {
|
|
94
|
+
const target = index + offset;
|
|
95
|
+
if (target < 0 || target >= current.length) return current;
|
|
96
|
+
const next = [...current];
|
|
97
|
+
[next[index], next[target]] = [next[target], next[index]];
|
|
98
|
+
return next;
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const removeImage = (id) => {
|
|
102
|
+
setImages((current) => {
|
|
103
|
+
const removed = current.find((image) => image.id === id);
|
|
104
|
+
if (removed) URL.revokeObjectURL(removed.previewUrl);
|
|
105
|
+
return current.filter((image) => image.id !== id);
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
const clearImages = () => {
|
|
109
|
+
images.forEach((image) => URL.revokeObjectURL(image.previewUrl));
|
|
110
|
+
setImages([]);
|
|
111
|
+
setStatus({ tone: "info", text: "Selection cleared. Add images to continue." });
|
|
112
|
+
};
|
|
113
|
+
const handleConvert = async (event) => {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
if (!images.length) return;
|
|
116
|
+
setIsConverting(true);
|
|
117
|
+
setStatus({ tone: "info", text: "Building your PDF..." });
|
|
118
|
+
try {
|
|
119
|
+
const sources = [];
|
|
120
|
+
for (const image of images) {
|
|
121
|
+
try {
|
|
122
|
+
sources.push({ name: image.file.name, bytes: await toEmbeddableBytes(image.file) });
|
|
123
|
+
} catch {
|
|
124
|
+
throw new Error(`${image.file.name}: this image could not be read.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const baseName = toBaseName(outputName, "images");
|
|
128
|
+
const bytes = await imagesToPdf(sources, {
|
|
129
|
+
pageSize,
|
|
130
|
+
orientation,
|
|
131
|
+
margin: Number(margin),
|
|
132
|
+
title: baseName
|
|
133
|
+
});
|
|
134
|
+
const blob = new Blob([bytes], { type: "application/pdf" });
|
|
135
|
+
downloadBlob(blob, `${baseName}.pdf`);
|
|
136
|
+
setStatus({
|
|
137
|
+
tone: "success",
|
|
138
|
+
text: `Created a ${images.length}-page PDF (${formatBytes(blob.size)}). Your download has started.`
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
setStatus({ tone: "error", text: `Conversion failed: ${error?.message || "Please try again."}` });
|
|
142
|
+
} finally {
|
|
143
|
+
setIsConverting(false);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
return /* @__PURE__ */ jsx(
|
|
147
|
+
PdfToolPage,
|
|
148
|
+
{
|
|
149
|
+
title: "Images to PDF",
|
|
150
|
+
description: "Turn photos, scans, and screenshots into a single PDF, one image per page. Runs in your browser; images never leave your device.",
|
|
151
|
+
stats: [
|
|
152
|
+
{ value: images.length || "-", label: "Pages" },
|
|
153
|
+
{ value: "PDF", label: "Output" }
|
|
154
|
+
],
|
|
155
|
+
children: /* @__PURE__ */ jsx(
|
|
156
|
+
PdfToolColumns,
|
|
157
|
+
{
|
|
158
|
+
main: /* @__PURE__ */ jsxs("form", { onSubmit: handleConvert, children: [
|
|
159
|
+
/* @__PURE__ */ jsx(
|
|
160
|
+
FileDropzone,
|
|
161
|
+
{
|
|
162
|
+
accept: "image/*",
|
|
163
|
+
multiple: true,
|
|
164
|
+
disabled: isConverting,
|
|
165
|
+
onFiles: addFiles,
|
|
166
|
+
icon: "fa-solid fa-images",
|
|
167
|
+
title: "Drag and drop images here",
|
|
168
|
+
hint: "JPG, PNG, WebP, GIF, or BMP. Add more any time."
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
images.length ? /* @__PURE__ */ jsx("ol", { className: "mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-3", children: images.map((image, index) => /* @__PURE__ */ jsxs(
|
|
172
|
+
"li",
|
|
173
|
+
{
|
|
174
|
+
className: "overflow-hidden rounded-2xl border border-slate-200 bg-white/90 dark:border-slate-700 dark:bg-slate-900/80",
|
|
175
|
+
children: [
|
|
176
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex h-36 items-center justify-center bg-slate-100 dark:bg-slate-800", children: [
|
|
177
|
+
/* @__PURE__ */ jsx(
|
|
178
|
+
"img",
|
|
179
|
+
{
|
|
180
|
+
src: image.previewUrl,
|
|
181
|
+
alt: image.file.name,
|
|
182
|
+
className: "max-h-full max-w-full object-contain"
|
|
183
|
+
}
|
|
184
|
+
),
|
|
185
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-2 top-2 rounded-lg bg-slate-900/80 px-2 py-0.5 text-xs font-semibold text-white", children: index + 1 })
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-2.5", children: [
|
|
188
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
189
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-xs font-medium text-slate-900 dark:text-slate-100", children: image.file.name }),
|
|
190
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-slate-500 dark:text-slate-400", children: formatBytes(image.file.size) })
|
|
191
|
+
] }),
|
|
192
|
+
/* @__PURE__ */ jsx(
|
|
193
|
+
"button",
|
|
194
|
+
{
|
|
195
|
+
type: "button",
|
|
196
|
+
onClick: () => moveImage(index, -1),
|
|
197
|
+
disabled: isConverting || index === 0,
|
|
198
|
+
className: iconButtonClass,
|
|
199
|
+
"aria-label": `Move ${image.file.name} earlier`,
|
|
200
|
+
children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-left" })
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
/* @__PURE__ */ jsx(
|
|
204
|
+
"button",
|
|
205
|
+
{
|
|
206
|
+
type: "button",
|
|
207
|
+
onClick: () => moveImage(index, 1),
|
|
208
|
+
disabled: isConverting || index === images.length - 1,
|
|
209
|
+
className: iconButtonClass,
|
|
210
|
+
"aria-label": `Move ${image.file.name} later`,
|
|
211
|
+
children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-right" })
|
|
212
|
+
}
|
|
213
|
+
),
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
"button",
|
|
216
|
+
{
|
|
217
|
+
type: "button",
|
|
218
|
+
onClick: () => removeImage(image.id),
|
|
219
|
+
disabled: isConverting,
|
|
220
|
+
className: iconButtonClass,
|
|
221
|
+
"aria-label": `Remove ${image.file.name}`,
|
|
222
|
+
children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-xmark" })
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
] })
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
image.id
|
|
229
|
+
)) }) : null,
|
|
230
|
+
/* @__PURE__ */ jsxs("fieldset", { className: "mt-5 grid gap-4 sm:grid-cols-3", disabled: isConverting, children: [
|
|
231
|
+
/* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
|
|
232
|
+
"Page size",
|
|
233
|
+
/* @__PURE__ */ jsxs(
|
|
234
|
+
"select",
|
|
235
|
+
{
|
|
236
|
+
value: pageSize,
|
|
237
|
+
onChange: (event) => setPageSize(event.target.value),
|
|
238
|
+
className: `mt-2 ${inputClass}`,
|
|
239
|
+
children: [
|
|
240
|
+
/* @__PURE__ */ jsx("option", { value: "a4", children: "A4" }),
|
|
241
|
+
/* @__PURE__ */ jsx("option", { value: "letter", children: "US Letter" }),
|
|
242
|
+
/* @__PURE__ */ jsx("option", { value: "fit", children: "Same as image" })
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
] }),
|
|
247
|
+
/* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
|
|
248
|
+
"Orientation",
|
|
249
|
+
/* @__PURE__ */ jsxs(
|
|
250
|
+
"select",
|
|
251
|
+
{
|
|
252
|
+
value: orientation,
|
|
253
|
+
onChange: (event) => setOrientation(event.target.value),
|
|
254
|
+
disabled: pageSize === "fit",
|
|
255
|
+
className: `mt-2 ${inputClass}`,
|
|
256
|
+
children: [
|
|
257
|
+
/* @__PURE__ */ jsx("option", { value: "auto", children: "Auto (match image)" }),
|
|
258
|
+
/* @__PURE__ */ jsx("option", { value: "portrait", children: "Portrait" }),
|
|
259
|
+
/* @__PURE__ */ jsx("option", { value: "landscape", children: "Landscape" })
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
] }),
|
|
264
|
+
/* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
|
|
265
|
+
"Margin",
|
|
266
|
+
/* @__PURE__ */ jsx(
|
|
267
|
+
"select",
|
|
268
|
+
{
|
|
269
|
+
value: margin,
|
|
270
|
+
onChange: (event) => setMargin(Number(event.target.value)),
|
|
271
|
+
className: `mt-2 ${inputClass}`,
|
|
272
|
+
children: MARGINS.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] }),
|
|
276
|
+
/* @__PURE__ */ jsxs("label", { className: `sm:col-span-3 ${fieldLabelClass}`, children: [
|
|
277
|
+
"Output file name",
|
|
278
|
+
/* @__PURE__ */ jsx(
|
|
279
|
+
"input",
|
|
280
|
+
{
|
|
281
|
+
type: "text",
|
|
282
|
+
value: outputName,
|
|
283
|
+
onChange: (event) => setOutputName(event.target.value),
|
|
284
|
+
className: `mt-2 ${inputClass}`
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
] })
|
|
288
|
+
] }),
|
|
289
|
+
/* @__PURE__ */ jsx(StatusBanner, { status }),
|
|
290
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
|
|
291
|
+
/* @__PURE__ */ jsxs("button", { type: "submit", disabled: isConverting || !images.length, className: primaryButtonClass, children: [
|
|
292
|
+
/* @__PURE__ */ jsx("i", { className: `fa-solid ${isConverting ? "fa-spinner fa-spin" : "fa-file-pdf"}` }),
|
|
293
|
+
isConverting ? "Converting..." : "Create PDF"
|
|
294
|
+
] }),
|
|
295
|
+
/* @__PURE__ */ jsxs(
|
|
296
|
+
"button",
|
|
297
|
+
{
|
|
298
|
+
type: "button",
|
|
299
|
+
onClick: clearImages,
|
|
300
|
+
disabled: isConverting || !images.length,
|
|
301
|
+
className: secondaryButtonClass,
|
|
302
|
+
children: [
|
|
303
|
+
/* @__PURE__ */ jsx("i", { className: "fa-solid fa-trash" }),
|
|
304
|
+
"Clear"
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
),
|
|
308
|
+
/* @__PURE__ */ jsx(BackToPdfTools, {})
|
|
309
|
+
] })
|
|
310
|
+
] }),
|
|
311
|
+
side: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
312
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "Summary" }),
|
|
313
|
+
/* @__PURE__ */ jsx(
|
|
314
|
+
SummaryList,
|
|
315
|
+
{
|
|
316
|
+
items: [
|
|
317
|
+
{ label: "Images", value: images.length },
|
|
318
|
+
{ label: "Total size", value: formatBytes(totalBytes) }
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
),
|
|
322
|
+
/* @__PURE__ */ jsx(
|
|
323
|
+
SideTips,
|
|
324
|
+
{
|
|
325
|
+
tips: [
|
|
326
|
+
"Each image becomes one page, in the order shown.",
|
|
327
|
+
"Large images are scaled down to fit the page; small ones are not enlarged.",
|
|
328
|
+
"Phone photos are turned upright automatically.",
|
|
329
|
+
"JPG and PNG are embedded as-is. Other formats are converted to PNG."
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
] })
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export { ImagesToPdf };
|
|
341
|
+
//# sourceMappingURL=chunk-CCJ43SIX.js.map
|
|
342
|
+
//# sourceMappingURL=chunk-CCJ43SIX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/images/ImagesToPdf.tsx"],"names":[],"mappings":"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","file":"chunk-CCJ43SIX.js","sourcesContent":["import React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SideTips,\n StatusBanner,\n SummaryList,\n fieldLabelClass,\n iconButtonClass,\n inputClass,\n primaryButtonClass,\n secondaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { detectImageKind, getJpegOrientation, imagesToPdf, type OrientationOption, type PageSizeOption } from \"./imagesToPdfCore\";\n\nconst MAX_IMAGES = 100;\nconst MAX_TOTAL_BYTES = 200 * 1024 * 1024;\n\nconst MARGINS = [\n { value: 0, label: \"None\" },\n { value: 18, label: \"Small\" },\n { value: 36, label: \"Medium\" },\n { value: 72, label: \"Large\" },\n];\n\nlet nextImageId = 0;\n\nfunction isImageFile(file) {\n return (file?.type || \"\").startsWith(\"image/\") || /\\.(jpe?g|png|webp|gif|bmp)$/i.test(file?.name || \"\");\n}\n\n/** Re-draw an image the browser can display as PNG/JPEG bytes pdf-lib can embed. */\nasync function rasterize(file, type) {\n const bitmap = await createImageBitmap(file, { imageOrientation: \"from-image\" });\n const canvas = document.createElement(\"canvas\");\n canvas.width = bitmap.width;\n canvas.height = bitmap.height;\n const context = canvas.getContext(\"2d\");\n if (type === \"image/jpeg\") {\n context.fillStyle = \"#ffffff\";\n context.fillRect(0, 0, canvas.width, canvas.height);\n }\n context.drawImage(bitmap, 0, 0);\n bitmap.close?.();\n const blob = await new Promise<Blob>((resolve, reject) =>\n canvas.toBlob((result) => (result ? resolve(result) : reject(new Error(\"encode failed\"))), type, 0.92)\n );\n return new Uint8Array(await blob.arrayBuffer());\n}\n\n/** PNG/JPEG pass through untouched unless EXIF says the photo is rotated. */\nasync function toEmbeddableBytes(file) {\n const bytes = new Uint8Array(await file.arrayBuffer());\n const kind = detectImageKind(bytes);\n if (kind === \"png\") return bytes;\n if (kind === \"jpg\") {\n return getJpegOrientation(bytes) === 1 ? bytes : rasterize(file, \"image/jpeg\");\n }\n return rasterize(file, \"image/png\");\n}\n\nexport default function ImagesToPdf() {\n const [images, setImages] = useState([]);\n const [pageSize, setPageSize] = useState<PageSizeOption>(\"a4\");\n const [orientation, setOrientation] = useState<OrientationOption>(\"auto\");\n const [margin, setMargin] = useState(18);\n const [outputName, setOutputName] = useState(\"images\");\n const [isConverting, setIsConverting] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add JPG, PNG, or other images, arrange them, and turn them into one PDF.\",\n });\n const imagesRef = useRef(images);\n imagesRef.current = images;\n\n useEffect(() => {\n document.title = \"Images to PDF | Apps\";\n return () => imagesRef.current.forEach((image) => URL.revokeObjectURL(image.previewUrl));\n }, []);\n\n const totalBytes = useMemo(() => images.reduce((sum, image) => sum + image.file.size, 0), [images]);\n\n const addFiles = (files) => {\n if (!files.length) return;\n const accepted = files.filter(isImageFile);\n if (!accepted.length) {\n setStatus({ tone: \"error\", text: \"Only image files are supported.\" });\n return;\n }\n if (images.length + accepted.length > MAX_IMAGES) {\n setStatus({ tone: \"error\", text: `Please add up to ${MAX_IMAGES} images at a time.` });\n return;\n }\n if (totalBytes + accepted.reduce((sum, file) => sum + file.size, 0) > MAX_TOTAL_BYTES) {\n setStatus({ tone: \"error\", text: `Total size must stay under ${formatBytes(MAX_TOTAL_BYTES)}.` });\n return;\n }\n\n setImages((current) => [\n ...current,\n ...accepted.map((file) => ({\n id: `img-${++nextImageId}`,\n file,\n previewUrl: URL.createObjectURL(file),\n })),\n ]);\n const skipped = files.length - accepted.length;\n setStatus({\n tone: \"success\",\n text: `${accepted.length} image${accepted.length > 1 ? \"s\" : \"\"} added.${\n skipped ? ` ${skipped} non-image file${skipped > 1 ? \"s\" : \"\"} skipped.` : \"\"\n }`,\n });\n };\n\n const moveImage = (index, offset) => {\n setImages((current) => {\n const target = index + offset;\n if (target < 0 || target >= current.length) return current;\n const next = [...current];\n [next[index], next[target]] = [next[target], next[index]];\n return next;\n });\n };\n\n const removeImage = (id) => {\n setImages((current) => {\n const removed = current.find((image) => image.id === id);\n if (removed) URL.revokeObjectURL(removed.previewUrl);\n return current.filter((image) => image.id !== id);\n });\n };\n\n const clearImages = () => {\n images.forEach((image) => URL.revokeObjectURL(image.previewUrl));\n setImages([]);\n setStatus({ tone: \"info\", text: \"Selection cleared. Add images to continue.\" });\n };\n\n const handleConvert = async (event) => {\n event.preventDefault();\n if (!images.length) return;\n\n setIsConverting(true);\n setStatus({ tone: \"info\", text: \"Building your PDF...\" });\n\n try {\n const sources = [];\n for (const image of images) {\n try {\n sources.push({ name: image.file.name, bytes: await toEmbeddableBytes(image.file) });\n } catch {\n throw new Error(`${image.file.name}: this image could not be read.`);\n }\n }\n\n const baseName = toBaseName(outputName, \"images\");\n const bytes = await imagesToPdf(sources, {\n pageSize,\n orientation,\n margin: Number(margin),\n title: baseName,\n });\n const blob = new Blob([bytes as BlobPart], { type: \"application/pdf\" });\n downloadBlob(blob, `${baseName}.pdf`);\n setStatus({\n tone: \"success\",\n text: `Created a ${images.length}-page PDF (${formatBytes(blob.size)}). Your download has started.`,\n });\n } catch (error) {\n setStatus({ tone: \"error\", text: `Conversion failed: ${error?.message || \"Please try again.\"}` });\n } finally {\n setIsConverting(false);\n }\n };\n\n return (\n <PdfToolPage\n title=\"Images to PDF\"\n description=\"Turn photos, scans, and screenshots into a single PDF, one image per page. Runs in your browser; images never leave your device.\"\n stats={[\n { value: images.length || \"-\", label: \"Pages\" },\n { value: \"PDF\", label: \"Output\" },\n ]}\n >\n <PdfToolColumns\n main={\n <form onSubmit={handleConvert}>\n <FileDropzone\n accept=\"image/*\"\n multiple\n disabled={isConverting}\n onFiles={addFiles}\n icon=\"fa-solid fa-images\"\n title=\"Drag and drop images here\"\n hint=\"JPG, PNG, WebP, GIF, or BMP. Add more any time.\"\n />\n\n {images.length ? (\n <ol className=\"mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-3\">\n {images.map((image, index) => (\n <li\n key={image.id}\n className=\"overflow-hidden rounded-2xl border border-slate-200 bg-white/90 dark:border-slate-700 dark:bg-slate-900/80\"\n >\n <div className=\"relative flex h-36 items-center justify-center bg-slate-100 dark:bg-slate-800\">\n <img\n src={image.previewUrl}\n alt={image.file.name}\n className=\"max-h-full max-w-full object-contain\"\n />\n <span className=\"absolute left-2 top-2 rounded-lg bg-slate-900/80 px-2 py-0.5 text-xs font-semibold text-white\">\n {index + 1}\n </span>\n </div>\n <div className=\"flex items-center gap-2 p-2.5\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-xs font-medium text-slate-900 dark:text-slate-100\">\n {image.file.name}\n </p>\n <p className=\"text-[11px] text-slate-500 dark:text-slate-400\">\n {formatBytes(image.file.size)}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => moveImage(index, -1)}\n disabled={isConverting || index === 0}\n className={iconButtonClass}\n aria-label={`Move ${image.file.name} earlier`}\n >\n <i className=\"fa-solid fa-arrow-left\" />\n </button>\n <button\n type=\"button\"\n onClick={() => moveImage(index, 1)}\n disabled={isConverting || index === images.length - 1}\n className={iconButtonClass}\n aria-label={`Move ${image.file.name} later`}\n >\n <i className=\"fa-solid fa-arrow-right\" />\n </button>\n <button\n type=\"button\"\n onClick={() => removeImage(image.id)}\n disabled={isConverting}\n className={iconButtonClass}\n aria-label={`Remove ${image.file.name}`}\n >\n <i className=\"fa-solid fa-xmark\" />\n </button>\n </div>\n </li>\n ))}\n </ol>\n ) : null}\n\n <fieldset className=\"mt-5 grid gap-4 sm:grid-cols-3\" disabled={isConverting}>\n <label className={fieldLabelClass}>\n Page size\n <select\n value={pageSize}\n onChange={(event) => setPageSize(event.target.value as PageSizeOption)}\n className={`mt-2 ${inputClass}`}\n >\n <option value=\"a4\">A4</option>\n <option value=\"letter\">US Letter</option>\n <option value=\"fit\">Same as image</option>\n </select>\n </label>\n <label className={fieldLabelClass}>\n Orientation\n <select\n value={orientation}\n onChange={(event) => setOrientation(event.target.value as OrientationOption)}\n disabled={pageSize === \"fit\"}\n className={`mt-2 ${inputClass}`}\n >\n <option value=\"auto\">Auto (match image)</option>\n <option value=\"portrait\">Portrait</option>\n <option value=\"landscape\">Landscape</option>\n </select>\n </label>\n <label className={fieldLabelClass}>\n Margin\n <select\n value={margin}\n onChange={(event) => setMargin(Number(event.target.value))}\n className={`mt-2 ${inputClass}`}\n >\n {MARGINS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n <label className={`sm:col-span-3 ${fieldLabelClass}`}>\n Output file name\n <input\n type=\"text\"\n value={outputName}\n onChange={(event) => setOutputName(event.target.value)}\n className={`mt-2 ${inputClass}`}\n />\n </label>\n </fieldset>\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button type=\"submit\" disabled={isConverting || !images.length} className={primaryButtonClass}>\n <i className={`fa-solid ${isConverting ? \"fa-spinner fa-spin\" : \"fa-file-pdf\"}`} />\n {isConverting ? \"Converting...\" : \"Create PDF\"}\n </button>\n <button\n type=\"button\"\n onClick={clearImages}\n disabled={isConverting || !images.length}\n className={secondaryButtonClass}\n >\n <i className=\"fa-solid fa-trash\" />\n Clear\n </button>\n <BackToPdfTools />\n </div>\n </form>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">Summary</h2>\n <SummaryList\n items={[\n { label: \"Images\", value: images.length },\n { label: \"Total size\", value: formatBytes(totalBytes) },\n ]}\n />\n <SideTips\n tips={[\n \"Each image becomes one page, in the order shown.\",\n \"Large images are scaled down to fit the page; small ones are not enlarged.\",\n \"Phone photos are turned upright automatically.\",\n \"JPG and PNG are embedded as-is. Other formats are converted to PNG.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { useRenderDocument, useThumbnails } from './chunk-J4IRICCJ.js';
|
|
2
|
+
import { renderedSize, IMAGE_FORMATS, DPI_OPTIONS, dpiToScale, imageFileName } from './chunk-NOFGCYGS.js';
|
|
3
|
+
import { renderPageToCanvas, canvasToBlob } from './chunk-DPEJU4P6.js';
|
|
4
|
+
import { createZip } from './chunk-7V4SWTUK.js';
|
|
5
|
+
import { parsePageSelection } from './chunk-BLHNKR3S.js';
|
|
6
|
+
import { useSinglePdf } from './chunk-BK3MYSUS.js';
|
|
7
|
+
import { PdfToolPage, PdfToolColumns, SummaryList, SideTips, SelectedPdfCard, FileDropzone, fieldLabelClass, inputClass, StatusBanner, primaryButtonClass, BackToPdfTools } from './chunk-H6EERRCR.js';
|
|
8
|
+
import { toBaseName, downloadBlob, formatBytes } from './chunk-EKHEY3LI.js';
|
|
9
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
10
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
|
+
|
|
12
|
+
function PdfToImages() {
|
|
13
|
+
const [format, setFormat] = useState("png");
|
|
14
|
+
const [dpi, setDpi] = useState(150);
|
|
15
|
+
const [quality, setQuality] = useState(0.85);
|
|
16
|
+
const [pages, setPages] = useState("");
|
|
17
|
+
const [isConverting, setIsConverting] = useState(false);
|
|
18
|
+
const [status, setStatus] = useState({
|
|
19
|
+
tone: "info",
|
|
20
|
+
text: "Add a PDF to export its pages as PNG or JPG images."
|
|
21
|
+
});
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
document.title = "PDF to Images | Apps";
|
|
24
|
+
}, []);
|
|
25
|
+
const onError = useCallback((text) => setStatus({ tone: "error", text }), []);
|
|
26
|
+
const onLoaded = useCallback(({ file, pageCount }) => {
|
|
27
|
+
setPages("");
|
|
28
|
+
setStatus({
|
|
29
|
+
tone: "success",
|
|
30
|
+
text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? "" : "s"}.`
|
|
31
|
+
});
|
|
32
|
+
}, []);
|
|
33
|
+
const pdf = useSinglePdf({ onError, onLoaded });
|
|
34
|
+
const { doc, error: renderError } = useRenderDocument(pdf.bytes);
|
|
35
|
+
const thumbnails = useThumbnails(doc, 120);
|
|
36
|
+
let selection = [];
|
|
37
|
+
let selectionError = "";
|
|
38
|
+
if (pdf.pageCount) {
|
|
39
|
+
try {
|
|
40
|
+
selection = [...new Set(parsePageSelection(pages, pdf.pageCount))];
|
|
41
|
+
} catch (error) {
|
|
42
|
+
selectionError = error.message;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const firstPage = thumbnails[0];
|
|
46
|
+
const estimate = firstPage ? renderedSize(firstPage.width, firstPage.height, dpi) : null;
|
|
47
|
+
const handleConvert = async (event) => {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
if (!doc || !selection.length) return;
|
|
50
|
+
setIsConverting(true);
|
|
51
|
+
const baseName = toBaseName(pdf.file.name, "document");
|
|
52
|
+
const { mime } = IMAGE_FORMATS[format];
|
|
53
|
+
const outputs = [];
|
|
54
|
+
let reduced = false;
|
|
55
|
+
try {
|
|
56
|
+
for (let i = 0; i < selection.length; i += 1) {
|
|
57
|
+
const pageNumber = selection[i] + 1;
|
|
58
|
+
setStatus({ tone: "info", text: `Rendering page ${pageNumber} (${i + 1} of ${selection.length})...` });
|
|
59
|
+
const render = await renderPageToCanvas(doc, pageNumber, { scale: dpiToScale(dpi) });
|
|
60
|
+
reduced = reduced || render.reduced;
|
|
61
|
+
const blob = await canvasToBlob(render.canvas, mime, format === "jpeg" ? quality : void 0);
|
|
62
|
+
render.canvas.width = 0;
|
|
63
|
+
render.canvas.height = 0;
|
|
64
|
+
outputs.push({
|
|
65
|
+
name: imageFileName(baseName, pageNumber, pdf.pageCount, format),
|
|
66
|
+
bytes: new Uint8Array(await blob.arrayBuffer()),
|
|
67
|
+
type: mime
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
let size;
|
|
71
|
+
if (outputs.length === 1) {
|
|
72
|
+
const blob = new Blob([outputs[0].bytes], { type: outputs[0].type });
|
|
73
|
+
size = blob.size;
|
|
74
|
+
downloadBlob(blob, outputs[0].name);
|
|
75
|
+
} else {
|
|
76
|
+
const blob = new Blob([createZip(outputs)], { type: "application/zip" });
|
|
77
|
+
size = blob.size;
|
|
78
|
+
downloadBlob(blob, `${baseName}-images.zip`);
|
|
79
|
+
}
|
|
80
|
+
setStatus({
|
|
81
|
+
tone: "success",
|
|
82
|
+
text: `Exported ${outputs.length} image${outputs.length === 1 ? "" : "s"} (${formatBytes(size)}). Your download has started.${reduced ? " Some pages were scaled down to stay within browser canvas limits." : ""}`
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
setStatus({ tone: "error", text: `Export failed: ${error?.message || "Please try again."}` });
|
|
86
|
+
} finally {
|
|
87
|
+
setIsConverting(false);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const busy = isConverting || pdf.isReading;
|
|
91
|
+
return /* @__PURE__ */ jsx(
|
|
92
|
+
PdfToolPage,
|
|
93
|
+
{
|
|
94
|
+
title: "PDF to Images",
|
|
95
|
+
description: "Export PDF pages as PNG or JPG images at the resolution you need. Runs in your browser; files never leave your device.",
|
|
96
|
+
stats: [
|
|
97
|
+
{ value: pdf.pageCount || "-", label: "Pages" },
|
|
98
|
+
{ value: format === "png" ? "PNG" : "JPG", label: "Output" }
|
|
99
|
+
],
|
|
100
|
+
children: /* @__PURE__ */ jsx(
|
|
101
|
+
PdfToolColumns,
|
|
102
|
+
{
|
|
103
|
+
main: /* @__PURE__ */ jsxs("form", { onSubmit: handleConvert, children: [
|
|
104
|
+
pdf.file ? /* @__PURE__ */ jsx(SelectedPdfCard, { file: pdf.file, pageCount: pdf.pageCount, onRemove: pdf.clear, disabled: isConverting }) : /* @__PURE__ */ jsx(
|
|
105
|
+
FileDropzone,
|
|
106
|
+
{
|
|
107
|
+
accept: ".pdf,application/pdf",
|
|
108
|
+
disabled: busy,
|
|
109
|
+
onFiles: pdf.selectFiles,
|
|
110
|
+
title: "Drag and drop a PDF here",
|
|
111
|
+
hint: "or click to browse from your device"
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
renderError ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs text-rose-700 dark:text-rose-300", children: renderError }) : null,
|
|
115
|
+
thumbnails.length ? /* @__PURE__ */ jsx("div", { className: "mt-5 flex gap-2 overflow-x-auto pb-2", children: thumbnails.map((thumb, index) => /* @__PURE__ */ jsx(
|
|
116
|
+
"div",
|
|
117
|
+
{
|
|
118
|
+
className: `flex h-28 w-20 shrink-0 items-center justify-center rounded-lg border bg-slate-100 p-1 dark:bg-slate-800 ${selection.includes(index) ? "border-[#1F8CFF]" : "border-slate-200 opacity-40 dark:border-slate-700"}`,
|
|
119
|
+
children: thumb ? /* @__PURE__ */ jsx("img", { src: thumb.url, alt: `Page ${index + 1}`, className: "max-h-full max-w-full" }) : /* @__PURE__ */ jsx("i", { className: "fa-solid fa-spinner fa-spin text-slate-400" })
|
|
120
|
+
},
|
|
121
|
+
index
|
|
122
|
+
)) }) : null,
|
|
123
|
+
/* @__PURE__ */ jsxs("fieldset", { className: "mt-5 grid gap-4 sm:grid-cols-2", disabled: busy || !pdf.pageCount, children: [
|
|
124
|
+
/* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
|
|
125
|
+
"Format",
|
|
126
|
+
/* @__PURE__ */ jsx("select", { value: format, onChange: (event) => setFormat(event.target.value), className: `mt-2 ${inputClass}`, children: Object.entries(IMAGE_FORMATS).map(([value, option]) => /* @__PURE__ */ jsx("option", { value, children: option.label }, value)) })
|
|
127
|
+
] }),
|
|
128
|
+
/* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
|
|
129
|
+
"Resolution",
|
|
130
|
+
/* @__PURE__ */ jsx("select", { value: dpi, onChange: (event) => setDpi(Number(event.target.value)), className: `mt-2 ${inputClass}`, children: DPI_OPTIONS.map((value) => /* @__PURE__ */ jsxs("option", { value, children: [
|
|
131
|
+
value,
|
|
132
|
+
" DPI",
|
|
133
|
+
value === 72 ? " (screen)" : value === 150 ? " (recommended)" : value === 300 ? " (print)" : ""
|
|
134
|
+
] }, value)) })
|
|
135
|
+
] }),
|
|
136
|
+
format === "jpeg" ? /* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
|
|
137
|
+
"JPG quality: ",
|
|
138
|
+
Math.round(quality * 100),
|
|
139
|
+
"%",
|
|
140
|
+
/* @__PURE__ */ jsx(
|
|
141
|
+
"input",
|
|
142
|
+
{
|
|
143
|
+
type: "range",
|
|
144
|
+
min: 0.4,
|
|
145
|
+
max: 1,
|
|
146
|
+
step: 0.05,
|
|
147
|
+
value: quality,
|
|
148
|
+
onChange: (event) => setQuality(Number(event.target.value)),
|
|
149
|
+
className: "mt-2 w-full accent-[#1F8CFF]"
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
] }) : null,
|
|
153
|
+
/* @__PURE__ */ jsxs("label", { className: fieldLabelClass, children: [
|
|
154
|
+
"Pages",
|
|
155
|
+
/* @__PURE__ */ jsx(
|
|
156
|
+
"input",
|
|
157
|
+
{
|
|
158
|
+
type: "text",
|
|
159
|
+
value: pages,
|
|
160
|
+
onChange: (event) => setPages(event.target.value),
|
|
161
|
+
placeholder: "All pages (e.g. 1-3, 5)",
|
|
162
|
+
className: `mt-2 ${inputClass}`
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
] })
|
|
166
|
+
] }),
|
|
167
|
+
selectionError && pages.trim() ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs text-rose-700 dark:text-rose-300", children: selectionError }) : null,
|
|
168
|
+
/* @__PURE__ */ jsx(StatusBanner, { status }),
|
|
169
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("button", { type: "submit", disabled: busy || !doc || !selection.length, className: primaryButtonClass, children: [
|
|
171
|
+
/* @__PURE__ */ jsx("i", { className: `fa-solid ${isConverting ? "fa-spinner fa-spin" : "fa-file-image"}` }),
|
|
172
|
+
isConverting ? "Exporting..." : "Export images"
|
|
173
|
+
] }),
|
|
174
|
+
/* @__PURE__ */ jsx(BackToPdfTools, {})
|
|
175
|
+
] })
|
|
176
|
+
] }),
|
|
177
|
+
side: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
178
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "Output" }),
|
|
179
|
+
/* @__PURE__ */ jsx(
|
|
180
|
+
SummaryList,
|
|
181
|
+
{
|
|
182
|
+
items: [
|
|
183
|
+
{ label: "Images", value: pdf.pageCount ? selection.length : "-" },
|
|
184
|
+
{
|
|
185
|
+
label: "Page 1 size",
|
|
186
|
+
value: estimate ? `${estimate.width} \xD7 ${estimate.height}px` : "-"
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
),
|
|
191
|
+
/* @__PURE__ */ jsx(
|
|
192
|
+
SideTips,
|
|
193
|
+
{
|
|
194
|
+
tips: [
|
|
195
|
+
"150 DPI suits screens and sharing; use 300 DPI for printing.",
|
|
196
|
+
"More than one image downloads as a ZIP.",
|
|
197
|
+
"Very large pages are scaled down to fit browser limits."
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
] })
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export { PdfToImages };
|
|
209
|
+
//# sourceMappingURL=chunk-CNEDWATL.js.map
|
|
210
|
+
//# sourceMappingURL=chunk-CNEDWATL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/toimages/PdfToImages.tsx"],"names":[],"mappings":"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","file":"chunk-CNEDWATL.js","sourcesContent":["import React, { useCallback, useEffect, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport { parsePageSelection } from \"../shared/pageRanges\";\nimport { canvasToBlob, renderPageToCanvas } from \"../shared/pdfRender\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n fieldLabelClass,\n inputClass,\n primaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { useRenderDocument, useThumbnails } from \"../shared/useRenderDocument\";\nimport { useSinglePdf } from \"../shared/useSinglePdf\";\nimport { createZip } from \"../shared/zip\";\nimport { DPI_OPTIONS, IMAGE_FORMATS, dpiToScale, imageFileName, renderedSize, type ImageFormat } from \"./pdfToImagesCore\";\n\nexport default function PdfToImages() {\n const [format, setFormat] = useState<ImageFormat>(\"png\");\n const [dpi, setDpi] = useState(150);\n const [quality, setQuality] = useState(0.85);\n const [pages, setPages] = useState(\"\");\n const [isConverting, setIsConverting] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add a PDF to export its pages as PNG or JPG images.\",\n });\n\n useEffect(() => {\n document.title = \"PDF to Images | Apps\";\n }, []);\n\n const onError = useCallback((text) => setStatus({ tone: \"error\", text }), []);\n const onLoaded = useCallback(({ file, pageCount }) => {\n setPages(\"\");\n setStatus({\n tone: \"success\",\n text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? \"\" : \"s\"}.`,\n });\n }, []);\n const pdf = useSinglePdf({ onError, onLoaded });\n const { doc, error: renderError } = useRenderDocument(pdf.bytes);\n const thumbnails = useThumbnails(doc, 120);\n\n let selection = [];\n let selectionError = \"\";\n if (pdf.pageCount) {\n try {\n selection = [...new Set(parsePageSelection(pages, pdf.pageCount))];\n } catch (error) {\n selectionError = error.message;\n }\n }\n\n const firstPage = thumbnails[0];\n const estimate = firstPage ? renderedSize(firstPage.width, firstPage.height, dpi) : null;\n\n const handleConvert = async (event) => {\n event.preventDefault();\n if (!doc || !selection.length) return;\n\n setIsConverting(true);\n const baseName = toBaseName(pdf.file.name, \"document\");\n const { mime } = IMAGE_FORMATS[format];\n const outputs = [];\n let reduced = false;\n\n try {\n for (let i = 0; i < selection.length; i += 1) {\n const pageNumber = selection[i] + 1;\n setStatus({ tone: \"info\", text: `Rendering page ${pageNumber} (${i + 1} of ${selection.length})...` });\n const render = await renderPageToCanvas(doc, pageNumber, { scale: dpiToScale(dpi) });\n reduced = reduced || render.reduced;\n const blob = await canvasToBlob(render.canvas, mime, format === \"jpeg\" ? quality : undefined);\n render.canvas.width = 0;\n render.canvas.height = 0;\n outputs.push({\n name: imageFileName(baseName, pageNumber, pdf.pageCount, format),\n bytes: new Uint8Array(await blob.arrayBuffer()),\n type: mime,\n });\n }\n\n let size;\n if (outputs.length === 1) {\n const blob = new Blob([outputs[0].bytes as BlobPart], { type: outputs[0].type });\n size = blob.size;\n downloadBlob(blob, outputs[0].name);\n } else {\n const blob = new Blob([createZip(outputs) as BlobPart], { type: \"application/zip\" });\n size = blob.size;\n downloadBlob(blob, `${baseName}-images.zip`);\n }\n\n setStatus({\n tone: \"success\",\n text: `Exported ${outputs.length} image${outputs.length === 1 ? \"\" : \"s\"} (${formatBytes(size)}). Your download has started.${\n reduced ? \" Some pages were scaled down to stay within browser canvas limits.\" : \"\"\n }`,\n });\n } catch (error) {\n setStatus({ tone: \"error\", text: `Export failed: ${error?.message || \"Please try again.\"}` });\n } finally {\n setIsConverting(false);\n }\n };\n\n const busy = isConverting || pdf.isReading;\n\n return (\n <PdfToolPage\n title=\"PDF to Images\"\n description=\"Export PDF pages as PNG or JPG images at the resolution you need. Runs in your browser; files never leave your device.\"\n stats={[\n { value: pdf.pageCount || \"-\", label: \"Pages\" },\n { value: format === \"png\" ? \"PNG\" : \"JPG\", label: \"Output\" },\n ]}\n >\n <PdfToolColumns\n main={\n <form onSubmit={handleConvert}>\n {pdf.file ? (\n <SelectedPdfCard file={pdf.file} pageCount={pdf.pageCount} onRemove={pdf.clear} disabled={isConverting} />\n ) : (\n <FileDropzone\n accept=\".pdf,application/pdf\"\n disabled={busy}\n onFiles={pdf.selectFiles}\n title=\"Drag and drop a PDF here\"\n hint=\"or click to browse from your device\"\n />\n )}\n\n {renderError ? <p className=\"mt-3 text-xs text-rose-700 dark:text-rose-300\">{renderError}</p> : null}\n\n {thumbnails.length ? (\n <div className=\"mt-5 flex gap-2 overflow-x-auto pb-2\">\n {thumbnails.map((thumb, index) => (\n <div\n key={index}\n className={`flex h-28 w-20 shrink-0 items-center justify-center rounded-lg border bg-slate-100 p-1 dark:bg-slate-800 ${\n selection.includes(index) ? \"border-[#1F8CFF]\" : \"border-slate-200 opacity-40 dark:border-slate-700\"\n }`}\n >\n {thumb ? (\n <img src={thumb.url} alt={`Page ${index + 1}`} className=\"max-h-full max-w-full\" />\n ) : (\n <i className=\"fa-solid fa-spinner fa-spin text-slate-400\" />\n )}\n </div>\n ))}\n </div>\n ) : null}\n\n <fieldset className=\"mt-5 grid gap-4 sm:grid-cols-2\" disabled={busy || !pdf.pageCount}>\n <label className={fieldLabelClass}>\n Format\n <select value={format} onChange={(event) => setFormat(event.target.value as ImageFormat)} className={`mt-2 ${inputClass}`}>\n {Object.entries(IMAGE_FORMATS).map(([value, option]) => (\n <option key={value} value={value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n <label className={fieldLabelClass}>\n Resolution\n <select value={dpi} onChange={(event) => setDpi(Number(event.target.value))} className={`mt-2 ${inputClass}`}>\n {DPI_OPTIONS.map((value) => (\n <option key={value} value={value}>\n {value} DPI{value === 72 ? \" (screen)\" : value === 150 ? \" (recommended)\" : value === 300 ? \" (print)\" : \"\"}\n </option>\n ))}\n </select>\n </label>\n {format === \"jpeg\" ? (\n <label className={fieldLabelClass}>\n JPG quality: {Math.round(quality * 100)}%\n <input\n type=\"range\"\n min={0.4}\n max={1}\n step={0.05}\n value={quality}\n onChange={(event) => setQuality(Number(event.target.value))}\n className=\"mt-2 w-full accent-[#1F8CFF]\"\n />\n </label>\n ) : null}\n <label className={fieldLabelClass}>\n Pages\n <input\n type=\"text\"\n value={pages}\n onChange={(event) => setPages(event.target.value)}\n placeholder=\"All pages (e.g. 1-3, 5)\"\n className={`mt-2 ${inputClass}`}\n />\n </label>\n </fieldset>\n\n {selectionError && pages.trim() ? (\n <p className=\"mt-3 text-xs text-rose-700 dark:text-rose-300\">{selectionError}</p>\n ) : null}\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button type=\"submit\" disabled={busy || !doc || !selection.length} className={primaryButtonClass}>\n <i className={`fa-solid ${isConverting ? \"fa-spinner fa-spin\" : \"fa-file-image\"}`} />\n {isConverting ? \"Exporting...\" : \"Export images\"}\n </button>\n <BackToPdfTools />\n </div>\n </form>\n }\n side={\n <>\n <h2 className=\"text-base font-semibold text-slate-900 dark:text-slate-100\">Output</h2>\n <SummaryList\n items={[\n { label: \"Images\", value: pdf.pageCount ? selection.length : \"-\" },\n {\n label: \"Page 1 size\",\n value: estimate ? `${estimate.width} × ${estimate.height}px` : \"-\",\n },\n ]}\n />\n <SideTips\n tips={[\n \"150 DPI suits screens and sharing; use 300 DPI for printing.\",\n \"More than one image downloads as a ZIP.\",\n \"Very large pages are scaled down to fit browser limits.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}
|