@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.
Files changed (132) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +16 -0
  4. package/README.md +256 -2
  5. package/THIRD_PARTY_NOTICES.md +48 -0
  6. package/dist/chunk-2K5X7L5J.js +43 -0
  7. package/dist/chunk-2K5X7L5J.js.map +1 -0
  8. package/dist/chunk-4EAYBJ6G.js +362 -0
  9. package/dist/chunk-4EAYBJ6G.js.map +1 -0
  10. package/dist/chunk-4LBJTDV5.js +165 -0
  11. package/dist/chunk-4LBJTDV5.js.map +1 -0
  12. package/dist/chunk-4ZEZ2GLD.js +353 -0
  13. package/dist/chunk-4ZEZ2GLD.js.map +1 -0
  14. package/dist/chunk-5OIMR74G.js +273 -0
  15. package/dist/chunk-5OIMR74G.js.map +1 -0
  16. package/dist/chunk-6NFNAKWA.js +257 -0
  17. package/dist/chunk-6NFNAKWA.js.map +1 -0
  18. package/dist/chunk-7MTEMQLL.js +47 -0
  19. package/dist/chunk-7MTEMQLL.js.map +1 -0
  20. package/dist/chunk-7V4SWTUK.js +83 -0
  21. package/dist/chunk-7V4SWTUK.js.map +1 -0
  22. package/dist/chunk-7XH7MFZ4.js +295 -0
  23. package/dist/chunk-7XH7MFZ4.js.map +1 -0
  24. package/dist/chunk-BK3MYSUS.js +61 -0
  25. package/dist/chunk-BK3MYSUS.js.map +1 -0
  26. package/dist/chunk-BLHNKR3S.js +35 -0
  27. package/dist/chunk-BLHNKR3S.js.map +1 -0
  28. package/dist/chunk-CCJ43SIX.js +342 -0
  29. package/dist/chunk-CCJ43SIX.js.map +1 -0
  30. package/dist/chunk-CNEDWATL.js +210 -0
  31. package/dist/chunk-CNEDWATL.js.map +1 -0
  32. package/dist/chunk-CYTWDJE2.js +152 -0
  33. package/dist/chunk-CYTWDJE2.js.map +1 -0
  34. package/dist/chunk-DKVOICYZ.js +45 -0
  35. package/dist/chunk-DKVOICYZ.js.map +1 -0
  36. package/dist/chunk-DPEJU4P6.js +79 -0
  37. package/dist/chunk-DPEJU4P6.js.map +1 -0
  38. package/dist/chunk-DQPABZTU.js +22 -0
  39. package/dist/chunk-DQPABZTU.js.map +1 -0
  40. package/dist/chunk-EKHEY3LI.js +46 -0
  41. package/dist/chunk-EKHEY3LI.js.map +1 -0
  42. package/dist/chunk-FIEYAKPI.js +421 -0
  43. package/dist/chunk-FIEYAKPI.js.map +1 -0
  44. package/dist/chunk-G467LFKC.js +191 -0
  45. package/dist/chunk-G467LFKC.js.map +1 -0
  46. package/dist/chunk-GLYWSVSB.js +250 -0
  47. package/dist/chunk-GLYWSVSB.js.map +1 -0
  48. package/dist/chunk-GXBIMUO7.js +135 -0
  49. package/dist/chunk-GXBIMUO7.js.map +1 -0
  50. package/dist/chunk-H6EERRCR.js +180 -0
  51. package/dist/chunk-H6EERRCR.js.map +1 -0
  52. package/dist/chunk-HDTM3LXD.js +35 -0
  53. package/dist/chunk-HDTM3LXD.js.map +1 -0
  54. package/dist/chunk-IGMX56C3.js +180 -0
  55. package/dist/chunk-IGMX56C3.js.map +1 -0
  56. package/dist/chunk-J4IRICCJ.js +74 -0
  57. package/dist/chunk-J4IRICCJ.js.map +1 -0
  58. package/dist/chunk-JXKQ7S6W.js +331 -0
  59. package/dist/chunk-JXKQ7S6W.js.map +1 -0
  60. package/dist/chunk-KY6DXS6S.js +34 -0
  61. package/dist/chunk-KY6DXS6S.js.map +1 -0
  62. package/dist/chunk-NOFGCYGS.js +22 -0
  63. package/dist/chunk-NOFGCYGS.js.map +1 -0
  64. package/dist/chunk-OSHUJRKD.js +131 -0
  65. package/dist/chunk-OSHUJRKD.js.map +1 -0
  66. package/dist/chunk-PMH7OWPF.js +40 -0
  67. package/dist/chunk-PMH7OWPF.js.map +1 -0
  68. package/dist/chunk-PRBKSX3A.js +299 -0
  69. package/dist/chunk-PRBKSX3A.js.map +1 -0
  70. package/dist/chunk-SVPUX7AM.js +48 -0
  71. package/dist/chunk-SVPUX7AM.js.map +1 -0
  72. package/dist/chunk-TR6HJ4NO.js +659 -0
  73. package/dist/chunk-TR6HJ4NO.js.map +1 -0
  74. package/dist/chunk-TV3E4YUF.js +69 -0
  75. package/dist/chunk-TV3E4YUF.js.map +1 -0
  76. package/dist/chunk-VCTN77K2.js +112 -0
  77. package/dist/chunk-VCTN77K2.js.map +1 -0
  78. package/dist/chunk-WEOPDD3P.js +120 -0
  79. package/dist/chunk-WEOPDD3P.js.map +1 -0
  80. package/dist/chunk-ZBZ7OC6I.js +228 -0
  81. package/dist/chunk-ZBZ7OC6I.js.map +1 -0
  82. package/dist/config-CJYo46X9.d.ts +19 -0
  83. package/dist/index.d.ts +517 -0
  84. package/dist/index.js +23 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/react/certificate-sign.d.ts +5 -0
  87. package/dist/react/certificate-sign.js +14 -0
  88. package/dist/react/certificate-sign.js.map +1 -0
  89. package/dist/react/compress.d.ts +5 -0
  90. package/dist/react/compress.js +8 -0
  91. package/dist/react/compress.js.map +1 -0
  92. package/dist/react/fill-form.d.ts +5 -0
  93. package/dist/react/fill-form.js +8 -0
  94. package/dist/react/fill-form.js.map +1 -0
  95. package/dist/react/images-to-pdf.d.ts +5 -0
  96. package/dist/react/images-to-pdf.js +7 -0
  97. package/dist/react/images-to-pdf.js.map +1 -0
  98. package/dist/react/merge.d.ts +5 -0
  99. package/dist/react/merge.js +8 -0
  100. package/dist/react/merge.js.map +1 -0
  101. package/dist/react/metadata.d.ts +5 -0
  102. package/dist/react/metadata.js +8 -0
  103. package/dist/react/metadata.js.map +1 -0
  104. package/dist/react/organize.d.ts +5 -0
  105. package/dist/react/organize.js +12 -0
  106. package/dist/react/organize.js.map +1 -0
  107. package/dist/react/pdf-to-images.d.ts +5 -0
  108. package/dist/react/pdf-to-images.js +13 -0
  109. package/dist/react/pdf-to-images.js.map +1 -0
  110. package/dist/react/protect.d.ts +5 -0
  111. package/dist/react/protect.js +8 -0
  112. package/dist/react/protect.js.map +1 -0
  113. package/dist/react/sign.d.ts +5 -0
  114. package/dist/react/sign.js +13 -0
  115. package/dist/react/sign.js.map +1 -0
  116. package/dist/react/split.d.ts +5 -0
  117. package/dist/react/split.js +11 -0
  118. package/dist/react/split.js.map +1 -0
  119. package/dist/react/verify-signatures.d.ts +5 -0
  120. package/dist/react/verify-signatures.js +8 -0
  121. package/dist/react/verify-signatures.js.map +1 -0
  122. package/dist/react/watermark.d.ts +5 -0
  123. package/dist/react/watermark.js +10 -0
  124. package/dist/react/watermark.js.map +1 -0
  125. package/dist/react.d.ts +40 -0
  126. package/dist/react.js +40 -0
  127. package/dist/react.js.map +1 -0
  128. package/dist/styles.css +2 -0
  129. package/dist/vite.d.ts +5 -0
  130. package/dist/vite.js +9 -0
  131. package/dist/vite.js.map +1 -0
  132. package/package.json +163 -4
@@ -0,0 +1,273 @@
1
+ import { signatureFields } from './chunk-7MTEMQLL.js';
2
+ import * as asn1js from 'asn1js';
3
+ import * as pkijs from 'pkijs';
4
+ import { PDFDocument, PDFArray, PDFNumber, PDFHexString, PDFString, PDFName } from 'pdf-lib';
5
+
6
+ var OID = {
7
+ data: "1.2.840.113549.1.7.1",
8
+ signedData: "1.2.840.113549.1.7.2",
9
+ contentType: "1.2.840.113549.1.9.3",
10
+ messageDigest: "1.2.840.113549.1.9.4",
11
+ signingTime: "1.2.840.113549.1.9.5",
12
+ signingCertificateV2: "1.2.840.113549.1.9.16.2.47",
13
+ sha256: "2.16.840.1.101.3.4.2.1",
14
+ sha256WithRSA: "1.2.840.113549.1.1.11",
15
+ commonName: "2.5.4.3",
16
+ organization: "2.5.4.10",
17
+ organizationalUnit: "2.5.4.11",
18
+ email: "1.2.840.113549.1.9.1"
19
+ };
20
+ var buffer = (bytes) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
21
+ async function sha256(data) {
22
+ return new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", data));
23
+ }
24
+ function compareBytes(a, b) {
25
+ for (let i = 0; i < Math.min(a.length, b.length); i += 1) {
26
+ if (a[i] !== b[i]) return a[i] - b[i];
27
+ }
28
+ return a.length - b.length;
29
+ }
30
+ function parseCertificate(der) {
31
+ try {
32
+ return pkijs.Certificate.fromBER(buffer(der));
33
+ } catch {
34
+ throw new Error("Could not read the signing certificate.");
35
+ }
36
+ }
37
+ async function signingCertificateV2(der, cert) {
38
+ const issuerSerial = new asn1js.Sequence({
39
+ value: [
40
+ new asn1js.Sequence({
41
+ value: [new asn1js.Constructed({ idBlock: { tagClass: 3, tagNumber: 4 }, value: [cert.issuer.toSchema()] })]
42
+ }),
43
+ cert.serialNumber
44
+ ]
45
+ });
46
+ const essCertId = new asn1js.Sequence({
47
+ value: [new asn1js.OctetString({ valueHex: buffer(await sha256(der)) }), issuerSerial]
48
+ });
49
+ return new asn1js.Sequence({ value: [new asn1js.Sequence({ value: [essCertId] })] });
50
+ }
51
+ async function buildCmsSignature(documentDigest, signer) {
52
+ const cert = parseCertificate(signer.certificate);
53
+ const chain = (signer.chain || []).map(parseCertificate);
54
+ const attributes = [
55
+ new pkijs.Attribute({ type: OID.contentType, values: [new asn1js.ObjectIdentifier({ value: OID.data })] }),
56
+ new pkijs.Attribute({
57
+ type: OID.messageDigest,
58
+ values: [new asn1js.OctetString({ valueHex: buffer(documentDigest) })]
59
+ }),
60
+ new pkijs.Attribute({
61
+ type: OID.signingCertificateV2,
62
+ values: [await signingCertificateV2(signer.certificate, cert)]
63
+ })
64
+ ].map((attribute) => ({ attribute, der: new Uint8Array(attribute.toSchema().toBER()) })).sort((a, b) => compareBytes(a.der, b.der)).map(({ attribute }) => attribute);
65
+ const toBeSigned = new Uint8Array(new asn1js.Set({ value: attributes.map((a) => a.toSchema()) }).toBER());
66
+ const signature = await signer.sign(toBeSigned);
67
+ const signerInfo = new pkijs.SignerInfo({
68
+ version: 1,
69
+ sid: new pkijs.IssuerAndSerialNumber({ issuer: cert.issuer, serialNumber: cert.serialNumber }),
70
+ digestAlgorithm: new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256 }),
71
+ signedAttrs: new pkijs.SignedAndUnsignedAttributes({ type: 0, attributes }),
72
+ signatureAlgorithm: new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256WithRSA, algorithmParams: new asn1js.Null() }),
73
+ signature: new asn1js.OctetString({ valueHex: buffer(signature) })
74
+ });
75
+ const signedData = new pkijs.SignedData({
76
+ version: 1,
77
+ encapContentInfo: new pkijs.EncapsulatedContentInfo({ eContentType: OID.data }),
78
+ digestAlgorithms: [new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256 })],
79
+ certificates: [cert, ...chain],
80
+ signerInfos: [signerInfo]
81
+ });
82
+ const contentInfo = new pkijs.ContentInfo({ contentType: OID.signedData, content: signedData.toSchema(true) });
83
+ return new Uint8Array(contentInfo.toSchema().toBER());
84
+ }
85
+ async function webCryptoSigner(identity) {
86
+ const key = await globalThis.crypto.subtle.importKey(
87
+ "pkcs8",
88
+ identity.privateKeyPkcs8,
89
+ { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
90
+ false,
91
+ ["sign"]
92
+ );
93
+ return {
94
+ certificate: identity.certificate,
95
+ chain: identity.chain,
96
+ sign: async (data) => new Uint8Array(await globalThis.crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, data))
97
+ };
98
+ }
99
+ function nameValue(name, oid) {
100
+ const entry = name.typesAndValues.find((item) => item.type === oid);
101
+ return entry ? String(entry.value.valueBlock.value) : "";
102
+ }
103
+ function describeCertificate(der) {
104
+ const cert = parseCertificate(der);
105
+ const hex = (bytes) => Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join(":");
106
+ let keyBits = 0;
107
+ try {
108
+ const rsa = cert.subjectPublicKeyInfo.parsedKey;
109
+ const modulus = new Uint8Array(rsa?.modulus?.valueBlock?.valueHexView || []);
110
+ keyBits = (modulus[0] === 0 ? modulus.length - 1 : modulus.length) * 8;
111
+ } catch {
112
+ keyBits = 0;
113
+ }
114
+ return {
115
+ commonName: nameValue(cert.subject, OID.commonName),
116
+ organization: nameValue(cert.subject, OID.organization),
117
+ organizationalUnit: nameValue(cert.subject, OID.organizationalUnit),
118
+ email: nameValue(cert.subject, OID.email),
119
+ issuerCommonName: nameValue(cert.issuer, OID.commonName),
120
+ issuerOrganization: nameValue(cert.issuer, OID.organization),
121
+ serialNumber: hex(cert.serialNumber.valueBlock.valueHexView.slice().buffer),
122
+ notBefore: cert.notBefore.value,
123
+ notAfter: cert.notAfter.value,
124
+ selfSigned: cert.subject.isEqual(cert.issuer),
125
+ keyBits
126
+ };
127
+ }
128
+ var N = (name) => PDFName.of(name);
129
+ var DIGESTS = {
130
+ "1.3.14.3.2.26": "SHA-1",
131
+ [OID.sha256]: "SHA-256",
132
+ "2.16.840.1.101.3.4.2.2": "SHA-384",
133
+ "2.16.840.1.101.3.4.2.3": "SHA-512"
134
+ };
135
+ function text(value) {
136
+ return value instanceof PDFString || value instanceof PDFHexString ? value.decodeText() : "";
137
+ }
138
+ function parsePdfDate(value) {
139
+ const match = /^D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?([Zz]|[+-]\d{2}'?\d{2}'?)?/.exec(value || "");
140
+ if (!match) return null;
141
+ const [, y, mo = "01", d = "01", h = "00", mi = "00", s = "00", tz = "Z"] = match;
142
+ let offset = "Z";
143
+ if (/^[+-]/.test(tz)) {
144
+ const digits = tz.replace(/'/g, "");
145
+ offset = `${digits.slice(0, 3)}:${digits.slice(3, 5) || "00"}`;
146
+ }
147
+ const date = /* @__PURE__ */ new Date(`${y}-${mo}-${d}T${h}:${mi}:${s}${offset}`);
148
+ return Number.isNaN(date.getTime()) ? null : date;
149
+ }
150
+ function derLength(bytes) {
151
+ if (bytes.length < 2) return 0;
152
+ const first = bytes[1];
153
+ if (first < 128) return 2 + first;
154
+ const count = first & 127;
155
+ let length = 0;
156
+ for (let i = 0; i < count; i += 1) length = length * 256 + bytes[2 + i];
157
+ return 2 + count + length;
158
+ }
159
+ function isHexOrSpace(byte) {
160
+ return byte >= 48 && byte <= 57 || // 0-9
161
+ byte >= 65 && byte <= 70 || // A-F
162
+ byte >= 97 && byte <= 102 || // a-f
163
+ byte === 32 || byte === 10 || byte === 13 || byte === 9 || byte === 12 || byte === 0;
164
+ }
165
+ function cmsTime(value) {
166
+ if (value instanceof asn1js.UTCTime || value instanceof asn1js.GeneralizedTime) {
167
+ const date = value.toDate();
168
+ return Number.isNaN(date.getTime()) ? null : date;
169
+ }
170
+ return null;
171
+ }
172
+ function attributeValue(signerInfo, oid) {
173
+ return signerInfo.signedAttrs?.attributes.find((attribute) => attribute.type === oid)?.values[0];
174
+ }
175
+ async function checkSignature(file, name, dict) {
176
+ const check = {
177
+ fieldName: name,
178
+ subFilter: dict.lookup(N("SubFilter"))?.decodeText?.() ?? "",
179
+ intact: null,
180
+ coversWholeFile: false,
181
+ signer: null,
182
+ certificateValidAtSigning: null,
183
+ signingTime: parsePdfDate(text(dict.lookup(N("M")))),
184
+ reason: text(dict.lookup(N("Reason"))),
185
+ location: text(dict.lookup(N("Location"))),
186
+ problem: ""
187
+ };
188
+ const byteRange = dict.lookup(N("ByteRange"));
189
+ const contents = dict.lookup(N("Contents"));
190
+ const range = byteRange instanceof PDFArray ? byteRange.asArray().map((item) => item instanceof PDFNumber ? item.asNumber() : NaN) : [];
191
+ if (range.length !== 4 || range.some((value) => !Number.isInteger(value) || value < 0) || !(contents instanceof PDFHexString || contents instanceof PDFString)) {
192
+ check.problem = "The signature is incomplete or malformed.";
193
+ return check;
194
+ }
195
+ const [start, firstLength, secondStart, secondLength] = range;
196
+ const structureOk = start === 0 && firstLength < secondStart && secondStart + secondLength <= file.length && file[firstLength] === 60 && file[secondStart - 1] === 62 && // The unsigned gap must be exactly the /Contents hex string. Anything else
197
+ // in it (a signature-wrapping attack) would be unsigned content.
198
+ file.subarray(firstLength + 1, secondStart - 1).every(isHexOrSpace);
199
+ check.coversWholeFile = structureOk && secondStart + secondLength === file.length;
200
+ if (!structureOk) {
201
+ check.intact = false;
202
+ check.problem = "The signed byte range does not match the file.";
203
+ return check;
204
+ }
205
+ if (!/^(ETSI\.CAdES\.detached|adbe\.pkcs7\.detached)$/.test(check.subFilter)) {
206
+ check.problem = `The ${check.subFilter || "unknown"} signature format is not supported here.`;
207
+ return check;
208
+ }
209
+ const signed = new Uint8Array(firstLength + secondLength);
210
+ signed.set(file.subarray(0, firstLength), 0);
211
+ signed.set(file.subarray(secondStart, secondStart + secondLength), firstLength);
212
+ const raw = contents.asBytes();
213
+ const der = raw.subarray(0, derLength(raw));
214
+ let signedData;
215
+ try {
216
+ const contentInfo = pkijs.ContentInfo.fromBER(der.slice().buffer);
217
+ signedData = new pkijs.SignedData({ schema: contentInfo.content });
218
+ } catch {
219
+ check.intact = false;
220
+ check.problem = "The signature data could not be read.";
221
+ return check;
222
+ }
223
+ const signerInfo = signedData.signerInfos[0];
224
+ const sid = signerInfo?.sid;
225
+ const signerCert = (signedData.certificates || []).find(
226
+ (cert) => cert instanceof pkijs.Certificate && sid instanceof pkijs.IssuerAndSerialNumber && cert.issuer.isEqual(sid.issuer) && cert.serialNumber.isEqual(sid.serialNumber)
227
+ );
228
+ if (signerCert) {
229
+ check.signer = describeCertificate(new Uint8Array(signerCert.toSchema().toBER()));
230
+ }
231
+ const signingTimeAttr = attributeValue(signerInfo, OID.signingTime);
232
+ if (!check.signingTime) check.signingTime = cmsTime(signingTimeAttr);
233
+ if (check.signer && check.signingTime) {
234
+ check.certificateValidAtSigning = check.signingTime >= check.signer.notBefore && check.signingTime <= check.signer.notAfter;
235
+ }
236
+ const digestName = DIGESTS[signerInfo.digestAlgorithm.algorithmId];
237
+ if (!digestName) {
238
+ check.problem = "The signature uses an unsupported digest algorithm.";
239
+ return check;
240
+ }
241
+ try {
242
+ const result = await signedData.verify({
243
+ signer: 0,
244
+ data: signed.slice().buffer,
245
+ checkChain: false,
246
+ extendedMode: true
247
+ });
248
+ check.intact = Boolean(result.signatureVerified);
249
+ if (!check.intact) check.problem = "The document was changed after it was signed, or the signature is corrupt.";
250
+ } catch (error) {
251
+ check.intact = false;
252
+ check.problem = error?.code === 15 || /message digest/i.test(String(error?.message || error?.date || "")) ? "The document was changed after it was signed." : "The signature does not match the document or its certificate.";
253
+ }
254
+ return check;
255
+ }
256
+ async function verifyPdfSignatures(bytes) {
257
+ const file = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
258
+ let doc;
259
+ try {
260
+ doc = await PDFDocument.load(file, { ignoreEncryption: true, updateMetadata: false });
261
+ } catch {
262
+ throw new Error("This file is not a readable PDF.");
263
+ }
264
+ const checks = [];
265
+ for (const field of signatureFields(doc)) {
266
+ if (field.value) checks.push(await checkSignature(file, field.name, field.value));
267
+ }
268
+ return checks;
269
+ }
270
+
271
+ export { buildCmsSignature, describeCertificate, parsePdfDate, sha256, verifyPdfSignatures, webCryptoSigner };
272
+ //# sourceMappingURL=chunk-5OIMR74G.js.map
273
+ //# sourceMappingURL=chunk-5OIMR74G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/certsign/cms.ts","../src/certsign/verifyCore.ts"],"names":["asn1js2","pkijs2"],"mappings":"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","file":"chunk-5OIMR74G.js","sourcesContent":["// CMS (PKCS#7) signatures for PDF, PAdES baseline style (ETSI.CAdES.detached).\nimport * as asn1js from \"asn1js\";\nimport * as pkijs from \"pkijs\";\n\nexport const OID = {\n data: \"1.2.840.113549.1.7.1\",\n signedData: \"1.2.840.113549.1.7.2\",\n contentType: \"1.2.840.113549.1.9.3\",\n messageDigest: \"1.2.840.113549.1.9.4\",\n signingTime: \"1.2.840.113549.1.9.5\",\n signingCertificateV2: \"1.2.840.113549.1.9.16.2.47\",\n sha256: \"2.16.840.1.101.3.4.2.1\",\n sha256WithRSA: \"1.2.840.113549.1.1.11\",\n commonName: \"2.5.4.3\",\n organization: \"2.5.4.10\",\n organizationalUnit: \"2.5.4.11\",\n country: \"2.5.4.6\",\n email: \"1.2.840.113549.1.9.1\",\n};\n\n/**\n * Whatever holds the private key: a .pfx in the browser today, a USB token\n * behind a local signer later. It signs the DER-encoded signed attributes\n * with RSASSA-PKCS1-v1_5 / SHA-256 and never exposes the key.\n */\nexport type RawSigner = {\n certificate: Uint8Array;\n chain?: Uint8Array[];\n sign(toBeSigned: Uint8Array): Promise<Uint8Array>;\n};\n\nconst buffer = (bytes: Uint8Array) => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n\nexport async function sha256(data: Uint8Array) {\n return new Uint8Array(await globalThis.crypto.subtle.digest(\"SHA-256\", data as BufferSource));\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array) {\n for (let i = 0; i < Math.min(a.length, b.length); i += 1) {\n if (a[i] !== b[i]) return a[i] - b[i];\n }\n return a.length - b.length;\n}\n\nexport function parseCertificate(der: Uint8Array) {\n try {\n return pkijs.Certificate.fromBER(buffer(der));\n } catch {\n throw new Error(\"Could not read the signing certificate.\");\n }\n}\n\n/** ESS signing-certificate-v2: binds the signature to this exact certificate. */\nasync function signingCertificateV2(der: Uint8Array, cert: pkijs.Certificate) {\n const issuerSerial = new asn1js.Sequence({\n value: [\n new asn1js.Sequence({\n value: [new asn1js.Constructed({ idBlock: { tagClass: 3, tagNumber: 4 }, value: [cert.issuer.toSchema()] })],\n }),\n cert.serialNumber,\n ],\n });\n // hashAlgorithm defaults to SHA-256 in ESSCertIDv2, so it is omitted.\n const essCertId = new asn1js.Sequence({\n value: [new asn1js.OctetString({ valueHex: buffer(await sha256(der)) }), issuerSerial],\n });\n return new asn1js.Sequence({ value: [new asn1js.Sequence({ value: [essCertId] })] });\n}\n\n/**\n * Build a detached CMS SignedData over a document digest. The signed\n * attributes are DER-sorted and the exact bytes signed are the ones embedded.\n */\nexport async function buildCmsSignature(documentDigest: Uint8Array, signer: RawSigner) {\n const cert = parseCertificate(signer.certificate);\n const chain = (signer.chain || []).map(parseCertificate);\n\n const attributes = [\n new pkijs.Attribute({ type: OID.contentType, values: [new asn1js.ObjectIdentifier({ value: OID.data })] }),\n new pkijs.Attribute({\n type: OID.messageDigest,\n values: [new asn1js.OctetString({ valueHex: buffer(documentDigest) })],\n }),\n new pkijs.Attribute({\n type: OID.signingCertificateV2,\n values: [await signingCertificateV2(signer.certificate, cert)],\n }),\n ]\n .map((attribute) => ({ attribute, der: new Uint8Array(attribute.toSchema().toBER()) }))\n .sort((a, b) => compareBytes(a.der, b.der))\n .map(({ attribute }) => attribute);\n\n // What gets signed is the SET OF attributes; the CMS stores the same bytes\n // under an implicit [0] tag.\n const toBeSigned = new Uint8Array(new asn1js.Set({ value: attributes.map((a) => a.toSchema()) }).toBER());\n const signature = await signer.sign(toBeSigned);\n\n const signerInfo = new pkijs.SignerInfo({\n version: 1,\n sid: new pkijs.IssuerAndSerialNumber({ issuer: cert.issuer, serialNumber: cert.serialNumber }),\n digestAlgorithm: new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256 }),\n signedAttrs: new pkijs.SignedAndUnsignedAttributes({ type: 0, attributes }),\n signatureAlgorithm: new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256WithRSA, algorithmParams: new asn1js.Null() }),\n signature: new asn1js.OctetString({ valueHex: buffer(signature) }),\n });\n\n const signedData = new pkijs.SignedData({\n version: 1,\n encapContentInfo: new pkijs.EncapsulatedContentInfo({ eContentType: OID.data }),\n digestAlgorithms: [new pkijs.AlgorithmIdentifier({ algorithmId: OID.sha256 })],\n certificates: [cert, ...chain],\n signerInfos: [signerInfo],\n });\n\n const contentInfo = new pkijs.ContentInfo({ contentType: OID.signedData, content: signedData.toSchema(true) });\n return new Uint8Array(contentInfo.toSchema().toBER());\n}\n\n/** A RawSigner backed by a PKCS#8 RSA key held in WebCrypto (non-extractable). */\nexport async function webCryptoSigner(identity: { certificate: Uint8Array; chain?: Uint8Array[]; privateKeyPkcs8: Uint8Array }): Promise<RawSigner> {\n const key = await globalThis.crypto.subtle.importKey(\n \"pkcs8\",\n identity.privateKeyPkcs8 as BufferSource,\n { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" },\n false,\n [\"sign\"]\n );\n return {\n certificate: identity.certificate,\n chain: identity.chain,\n sign: async (data) => new Uint8Array(await globalThis.crypto.subtle.sign(\"RSASSA-PKCS1-v1_5\", key, data as BufferSource)),\n };\n}\n\nfunction nameValue(name: pkijs.RelativeDistinguishedNames, oid: string) {\n const entry = name.typesAndValues.find((item) => item.type === oid);\n return entry ? String(entry.value.valueBlock.value) : \"\";\n}\n\nexport type CertificateSummary = {\n commonName: string;\n organization: string;\n organizationalUnit: string;\n email: string;\n issuerCommonName: string;\n issuerOrganization: string;\n serialNumber: string;\n notBefore: Date;\n notAfter: Date;\n selfSigned: boolean;\n keyBits: number;\n};\n\nexport function describeCertificate(der: Uint8Array): CertificateSummary {\n const cert = parseCertificate(der);\n const hex = (bytes: ArrayBuffer) =>\n Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, \"0\")).join(\":\");\n let keyBits = 0;\n try {\n const rsa = cert.subjectPublicKeyInfo.parsedKey as pkijs.RSAPublicKey | undefined;\n const modulus = new Uint8Array(rsa?.modulus?.valueBlock?.valueHexView || []);\n keyBits = (modulus[0] === 0 ? modulus.length - 1 : modulus.length) * 8;\n } catch {\n keyBits = 0;\n }\n return {\n commonName: nameValue(cert.subject, OID.commonName),\n organization: nameValue(cert.subject, OID.organization),\n organizationalUnit: nameValue(cert.subject, OID.organizationalUnit),\n email: nameValue(cert.subject, OID.email),\n issuerCommonName: nameValue(cert.issuer, OID.commonName),\n issuerOrganization: nameValue(cert.issuer, OID.organization),\n serialNumber: hex(cert.serialNumber.valueBlock.valueHexView.slice().buffer),\n notBefore: cert.notBefore.value,\n notAfter: cert.notAfter.value,\n selfSigned: cert.subject.isEqual(cert.issuer),\n keyBits,\n };\n}\n","// Inspect and verify the digital signatures in a PDF.\nimport * as asn1js from \"asn1js\";\nimport * as pkijs from \"pkijs\";\nimport { PDFArray, PDFDict, PDFDocument, PDFHexString, PDFName, PDFNumber, PDFString } from \"pdf-lib\";\nimport { signatureFields } from \"../shared/signatureFields\";\nimport { OID, describeCertificate, type CertificateSummary } from \"./cms\";\n\nconst N = (name: string) => PDFName.of(name);\n\nexport type SignatureCheck = {\n fieldName: string;\n subFilter: string;\n /** The signature bytes match the signed content. */\n intact: boolean | null;\n /** The signature covers the whole file (nothing appended after signing). */\n coversWholeFile: boolean;\n signer: CertificateSummary | null;\n /** Certificate validity period includes the claimed signing time. */\n certificateValidAtSigning: boolean | null;\n signingTime: Date | null;\n reason: string;\n location: string;\n /** Why the signature could not be fully checked, if it couldn't. */\n problem: string;\n};\n\nconst DIGESTS: Record<string, string> = {\n \"1.3.14.3.2.26\": \"SHA-1\",\n [OID.sha256]: \"SHA-256\",\n \"2.16.840.1.101.3.4.2.2\": \"SHA-384\",\n \"2.16.840.1.101.3.4.2.3\": \"SHA-512\",\n};\n\nfunction text(value: unknown) {\n return value instanceof PDFString || value instanceof PDFHexString ? value.decodeText() : \"\";\n}\n\n/** \"D:20261101120509+05'30'\" -> Date. */\nexport function parsePdfDate(value: string) {\n const match = /^D:(\\d{4})(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?(\\d{2})?([Zz]|[+-]\\d{2}'?\\d{2}'?)?/.exec(value || \"\");\n if (!match) return null;\n const [, y, mo = \"01\", d = \"01\", h = \"00\", mi = \"00\", s = \"00\", tz = \"Z\"] = match;\n let offset = \"Z\";\n if (/^[+-]/.test(tz)) {\n const digits = tz.replace(/'/g, \"\");\n offset = `${digits.slice(0, 3)}:${digits.slice(3, 5) || \"00\"}`;\n }\n const date = new Date(`${y}-${mo}-${d}T${h}:${mi}:${s}${offset}`);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/** Length of a DER value starting at 0 (the Contents hex is zero-padded). */\nfunction derLength(bytes: Uint8Array) {\n if (bytes.length < 2) return 0;\n const first = bytes[1];\n if (first < 0x80) return 2 + first;\n const count = first & 0x7f;\n let length = 0;\n for (let i = 0; i < count; i += 1) length = length * 256 + bytes[2 + i];\n return 2 + count + length;\n}\n\n/** Hex digits, plus the whitespace PDF allows inside hex strings. */\nfunction isHexOrSpace(byte: number) {\n return (\n (byte >= 0x30 && byte <= 0x39) || // 0-9\n (byte >= 0x41 && byte <= 0x46) || // A-F\n (byte >= 0x61 && byte <= 0x66) || // a-f\n byte === 0x20 ||\n byte === 0x0a ||\n byte === 0x0d ||\n byte === 0x09 ||\n byte === 0x0c ||\n byte === 0x00\n );\n}\n\n/** A CMS signingTime value: UTCTime up to 2049, GeneralizedTime from 2050. */\nexport function cmsTime(value: unknown) {\n if (value instanceof asn1js.UTCTime || value instanceof asn1js.GeneralizedTime) {\n const date = value.toDate();\n return Number.isNaN(date.getTime()) ? null : date;\n }\n return null;\n}\n\nfunction attributeValue(signerInfo: pkijs.SignerInfo, oid: string) {\n return signerInfo.signedAttrs?.attributes.find((attribute) => attribute.type === oid)?.values[0];\n}\n\nasync function checkSignature(file: Uint8Array, name: string, dict: PDFDict): Promise<SignatureCheck> {\n const check: SignatureCheck = {\n fieldName: name,\n subFilter: (dict.lookup(N(\"SubFilter\")) as PDFName | undefined)?.decodeText?.() ?? \"\",\n intact: null,\n coversWholeFile: false,\n signer: null,\n certificateValidAtSigning: null,\n signingTime: parsePdfDate(text(dict.lookup(N(\"M\")))),\n reason: text(dict.lookup(N(\"Reason\"))),\n location: text(dict.lookup(N(\"Location\"))),\n problem: \"\",\n };\n\n const byteRange = dict.lookup(N(\"ByteRange\"));\n const contents = dict.lookup(N(\"Contents\"));\n const range = byteRange instanceof PDFArray\n ? byteRange.asArray().map((item) => (item instanceof PDFNumber ? item.asNumber() : NaN))\n : [];\n if (range.length !== 4 || range.some((value) => !Number.isInteger(value) || value < 0) || !(contents instanceof PDFHexString || contents instanceof PDFString)) {\n check.problem = \"The signature is incomplete or malformed.\";\n return check;\n }\n\n const [start, firstLength, secondStart, secondLength] = range;\n const structureOk =\n start === 0 &&\n firstLength < secondStart &&\n secondStart + secondLength <= file.length &&\n file[firstLength] === 0x3c /* < */ &&\n file[secondStart - 1] === 0x3e /* > */ &&\n // The unsigned gap must be exactly the /Contents hex string. Anything else\n // in it (a signature-wrapping attack) would be unsigned content.\n file.subarray(firstLength + 1, secondStart - 1).every(isHexOrSpace);\n check.coversWholeFile = structureOk && secondStart + secondLength === file.length;\n if (!structureOk) {\n check.intact = false;\n check.problem = \"The signed byte range does not match the file.\";\n return check;\n }\n\n if (!/^(ETSI\\.CAdES\\.detached|adbe\\.pkcs7\\.detached)$/.test(check.subFilter)) {\n check.problem = `The ${check.subFilter || \"unknown\"} signature format is not supported here.`;\n return check;\n }\n\n const signed = new Uint8Array(firstLength + secondLength);\n signed.set(file.subarray(0, firstLength), 0);\n signed.set(file.subarray(secondStart, secondStart + secondLength), firstLength);\n\n const raw = contents.asBytes();\n const der = raw.subarray(0, derLength(raw));\n let signedData: pkijs.SignedData;\n try {\n const contentInfo = pkijs.ContentInfo.fromBER(der.slice().buffer);\n signedData = new pkijs.SignedData({ schema: contentInfo.content });\n } catch {\n check.intact = false;\n check.problem = \"The signature data could not be read.\";\n return check;\n }\n\n const signerInfo = signedData.signerInfos[0];\n const sid = signerInfo?.sid;\n const signerCert = (signedData.certificates || []).find(\n (cert) =>\n cert instanceof pkijs.Certificate &&\n sid instanceof pkijs.IssuerAndSerialNumber &&\n cert.issuer.isEqual(sid.issuer) &&\n cert.serialNumber.isEqual(sid.serialNumber)\n ) as pkijs.Certificate | undefined;\n if (signerCert) {\n check.signer = describeCertificate(new Uint8Array(signerCert.toSchema().toBER()));\n }\n\n const signingTimeAttr = attributeValue(signerInfo, OID.signingTime);\n if (!check.signingTime) check.signingTime = cmsTime(signingTimeAttr);\n if (check.signer && check.signingTime) {\n check.certificateValidAtSigning =\n check.signingTime >= check.signer.notBefore && check.signingTime <= check.signer.notAfter;\n }\n\n const digestName = DIGESTS[signerInfo.digestAlgorithm.algorithmId];\n if (!digestName) {\n check.problem = \"The signature uses an unsupported digest algorithm.\";\n return check;\n }\n\n try {\n const result = await signedData.verify({\n signer: 0,\n data: signed.slice().buffer,\n checkChain: false,\n extendedMode: true,\n });\n check.intact = Boolean(result.signatureVerified);\n if (!check.intact) check.problem = \"The document was changed after it was signed, or the signature is corrupt.\";\n } catch (error) {\n check.intact = false;\n check.problem =\n error?.code === 15 || /message digest/i.test(String(error?.message || error?.date || \"\"))\n ? \"The document was changed after it was signed.\"\n : \"The signature does not match the document or its certificate.\";\n }\n return check;\n}\n\nexport async function verifyPdfSignatures(bytes: ArrayBuffer | Uint8Array) {\n const file = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n let doc: PDFDocument;\n try {\n // Signature /Contents are never encrypted, so encrypted PDFs can be read too.\n doc = await PDFDocument.load(file, { ignoreEncryption: true, updateMetadata: false });\n } catch {\n throw new Error(\"This file is not a readable PDF.\");\n }\n\n const checks: SignatureCheck[] = [];\n for (const field of signatureFields(doc)) {\n if (field.value) checks.push(await checkSignature(file, field.name, field.value));\n }\n return checks;\n}\n"]}
@@ -0,0 +1,257 @@
1
+ import { useRenderDocument, useThumbnails } from './chunk-J4IRICCJ.js';
2
+ import { organizePdf } from './chunk-GXBIMUO7.js';
3
+ import { useSinglePdf } from './chunk-BK3MYSUS.js';
4
+ import { PdfToolPage, PdfToolColumns, SummaryList, SideTips, SelectedPdfCard, FileDropzone, secondaryButtonClass, iconButtonClass, StatusBanner, primaryButtonClass, BackToPdfTools } from './chunk-H6EERRCR.js';
5
+ import { downloadBlob, toBaseName, formatBytes } from './chunk-EKHEY3LI.js';
6
+ import { useState, useEffect, useCallback, useMemo } from 'react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+
9
+ function initialPages(count) {
10
+ return Array.from({ length: count }, (_, source) => ({ source, rotation: 0 }));
11
+ }
12
+ function OrganizePdf() {
13
+ const [pages, setPages] = useState([]);
14
+ const [dragIndex, setDragIndex] = useState(null);
15
+ const [dropIndex, setDropIndex] = useState(null);
16
+ const [isSaving, setIsSaving] = useState(false);
17
+ const [status, setStatus] = useState({
18
+ tone: "info",
19
+ text: "Add a PDF to reorder, rotate, or delete its pages."
20
+ });
21
+ useEffect(() => {
22
+ document.title = "Organize PDF Pages | Apps";
23
+ }, []);
24
+ const onError = useCallback((text) => setStatus({ tone: "error", text }), []);
25
+ const onLoaded = useCallback(({ file, pageCount }) => {
26
+ setPages(initialPages(pageCount));
27
+ setStatus({
28
+ tone: "success",
29
+ text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? "" : "s"}. Drag pages to reorder.`
30
+ });
31
+ }, []);
32
+ const pdf = useSinglePdf({ onError, onLoaded });
33
+ const { doc, error: renderError } = useRenderDocument(pdf.bytes);
34
+ const thumbnails = useThumbnails(doc, 150);
35
+ const changed = useMemo(
36
+ () => pages.length !== pdf.pageCount || pages.some((page, index) => page.source !== index || page.rotation % 360 !== 0),
37
+ [pages, pdf.pageCount]
38
+ );
39
+ const deleted = pdf.pageCount - pages.length;
40
+ const clearFile = () => {
41
+ pdf.clear();
42
+ setPages([]);
43
+ };
44
+ const move = (from, to) => {
45
+ setPages((current) => {
46
+ if (to < 0 || to >= current.length || from === to) return current;
47
+ const next = [...current];
48
+ const [item] = next.splice(from, 1);
49
+ next.splice(to, 0, item);
50
+ return next;
51
+ });
52
+ };
53
+ const rotate = (index, delta) => {
54
+ setPages(
55
+ (current) => current.map((page, i) => i === index ? { ...page, rotation: page.rotation + delta } : page)
56
+ );
57
+ };
58
+ const rotateAll = (delta) => {
59
+ setPages((current) => current.map((page) => ({ ...page, rotation: page.rotation + delta })));
60
+ };
61
+ const remove = (index) => {
62
+ setPages((current) => current.filter((_, i) => i !== index));
63
+ };
64
+ const handleDrop = (event, index) => {
65
+ event.preventDefault();
66
+ if (dragIndex !== null) move(dragIndex, index);
67
+ setDragIndex(null);
68
+ setDropIndex(null);
69
+ };
70
+ const handleSave = async () => {
71
+ if (!pdf.bytes || !pages.length) return;
72
+ setIsSaving(true);
73
+ setStatus({ tone: "info", text: "Saving your PDF..." });
74
+ try {
75
+ const bytes = await organizePdf(pdf.bytes, pages);
76
+ const blob = new Blob([bytes], { type: "application/pdf" });
77
+ downloadBlob(blob, `${toBaseName(pdf.file.name, "document")}-organized.pdf`);
78
+ setStatus({
79
+ tone: "success",
80
+ text: `Saved ${pages.length} page${pages.length === 1 ? "" : "s"} (${formatBytes(blob.size)}). Your download has started.`
81
+ });
82
+ } catch (error) {
83
+ setStatus({ tone: "error", text: `Save failed: ${error?.message || "Please try again."}` });
84
+ } finally {
85
+ setIsSaving(false);
86
+ }
87
+ };
88
+ const busy = isSaving || pdf.isReading;
89
+ return /* @__PURE__ */ jsx(
90
+ PdfToolPage,
91
+ {
92
+ title: "Organize Pages",
93
+ description: "Reorder, rotate, and delete pages with thumbnail previews. Drag a page to move it. Runs in your browser; files never leave your device.",
94
+ stats: [
95
+ { value: pages.length || "-", label: "Pages" },
96
+ { value: deleted > 0 ? deleted : "-", label: "Deleted" }
97
+ ],
98
+ children: /* @__PURE__ */ jsx(
99
+ PdfToolColumns,
100
+ {
101
+ main: /* @__PURE__ */ jsxs(Fragment, { children: [
102
+ pdf.file ? /* @__PURE__ */ jsx(
103
+ SelectedPdfCard,
104
+ {
105
+ signed: pdf.signed,
106
+ file: pdf.file,
107
+ pageCount: pdf.pageCount,
108
+ onRemove: clearFile,
109
+ disabled: isSaving
110
+ }
111
+ ) : /* @__PURE__ */ jsx(
112
+ FileDropzone,
113
+ {
114
+ accept: ".pdf,application/pdf",
115
+ disabled: busy,
116
+ onFiles: pdf.selectFiles,
117
+ title: "Drag and drop a PDF here",
118
+ hint: "or click to browse from your device"
119
+ }
120
+ ),
121
+ renderError ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs text-rose-700 dark:text-rose-300", children: renderError }) : null,
122
+ pages.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
123
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-2", children: [
124
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => rotateAll(-90), disabled: busy, className: secondaryButtonClass, children: [
125
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-rotate-left" }),
126
+ "Rotate all left"
127
+ ] }),
128
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => rotateAll(90), disabled: busy, className: secondaryButtonClass, children: [
129
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-rotate-right" }),
130
+ "Rotate all right"
131
+ ] }),
132
+ /* @__PURE__ */ jsxs(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ onClick: () => setPages((current) => [...current].reverse()),
137
+ disabled: busy,
138
+ className: secondaryButtonClass,
139
+ children: [
140
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-down-wide-short" }),
141
+ "Reverse order"
142
+ ]
143
+ }
144
+ ),
145
+ /* @__PURE__ */ jsxs(
146
+ "button",
147
+ {
148
+ type: "button",
149
+ onClick: () => setPages(initialPages(pdf.pageCount)),
150
+ disabled: busy || !changed,
151
+ className: secondaryButtonClass,
152
+ children: [
153
+ /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-rotate-left" }),
154
+ "Reset"
155
+ ]
156
+ }
157
+ )
158
+ ] }),
159
+ /* @__PURE__ */ jsx("ol", { className: "mt-4 grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4", children: pages.map((page, index) => {
160
+ const thumb = thumbnails[page.source];
161
+ return /* @__PURE__ */ jsxs(
162
+ "li",
163
+ {
164
+ draggable: !busy,
165
+ onDragStart: (event) => {
166
+ setDragIndex(index);
167
+ event.dataTransfer.effectAllowed = "move";
168
+ },
169
+ onDragOver: (event) => {
170
+ event.preventDefault();
171
+ setDropIndex(index);
172
+ },
173
+ onDragLeave: () => setDropIndex((current) => current === index ? null : current),
174
+ onDrop: (event) => handleDrop(event, index),
175
+ onDragEnd: () => {
176
+ setDragIndex(null);
177
+ setDropIndex(null);
178
+ },
179
+ className: `flex cursor-grab flex-col overflow-hidden rounded-2xl border bg-white/90 transition active:cursor-grabbing dark:bg-slate-900/80 ${dropIndex === index && dragIndex !== index ? "border-[#1F8CFF] ring-2 ring-[#1F8CFF]/40" : "border-slate-200 dark:border-slate-700"} ${dragIndex === index ? "opacity-40" : ""}`,
180
+ children: [
181
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-44 items-center justify-center overflow-hidden bg-slate-100 p-2 dark:bg-slate-800", children: [
182
+ thumb ? /* @__PURE__ */ jsx(
183
+ "img",
184
+ {
185
+ src: thumb.url,
186
+ alt: `Page ${page.source + 1}`,
187
+ draggable: false,
188
+ className: "max-h-full max-w-full shadow-sm transition-transform",
189
+ style: { transform: `rotate(${page.rotation}deg)`, maxHeight: page.rotation % 180 ? "8.5rem" : void 0 }
190
+ }
191
+ ) : /* @__PURE__ */ jsx("i", { className: "fa-solid fa-spinner fa-spin text-slate-400", "aria-label": "Rendering" }),
192
+ /* @__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 }),
193
+ /* @__PURE__ */ jsxs(
194
+ "span",
195
+ {
196
+ "data-source-page": page.source + 1,
197
+ className: "absolute right-2 top-2 rounded-lg bg-white/90 px-1.5 py-0.5 text-[10px] font-medium text-slate-600 shadow-sm dark:bg-slate-900/90 dark:text-slate-300",
198
+ children: [
199
+ "p.",
200
+ page.source + 1
201
+ ]
202
+ }
203
+ )
204
+ ] }),
205
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-2", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
206
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => move(index, index - 1), disabled: busy || index === 0, className: iconButtonClass, "aria-label": `Move page ${page.source + 1} earlier`, children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-left" }) }),
207
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => move(index, index + 1), disabled: busy || index === pages.length - 1, className: iconButtonClass, "aria-label": `Move page ${page.source + 1} later`, children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-arrow-right" }) }),
208
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => rotate(index, 90), disabled: busy, className: iconButtonClass, "aria-label": `Rotate page ${page.source + 1}`, children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-rotate-right" }) }),
209
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => remove(index), disabled: busy || pages.length === 1, className: iconButtonClass, "aria-label": `Delete page ${page.source + 1}`, children: /* @__PURE__ */ jsx("i", { className: "fa-solid fa-trash" }) })
210
+ ] }) })
211
+ ]
212
+ },
213
+ page.source
214
+ );
215
+ }) })
216
+ ] }) : null,
217
+ /* @__PURE__ */ jsx(StatusBanner, { status }),
218
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex flex-wrap gap-3", children: [
219
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: handleSave, disabled: busy || !pages.length || !changed, className: primaryButtonClass, children: [
220
+ /* @__PURE__ */ jsx("i", { className: `fa-solid ${isSaving ? "fa-spinner fa-spin" : "fa-floppy-disk"}` }),
221
+ isSaving ? "Saving..." : "Save PDF"
222
+ ] }),
223
+ /* @__PURE__ */ jsx(BackToPdfTools, {})
224
+ ] })
225
+ ] }),
226
+ side: /* @__PURE__ */ jsxs(Fragment, { children: [
227
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-slate-900 dark:text-slate-100", children: "Summary" }),
228
+ /* @__PURE__ */ jsx(
229
+ SummaryList,
230
+ {
231
+ items: [
232
+ { label: "Original pages", value: pdf.pageCount || "-" },
233
+ { label: "Pages kept", value: pages.length || "-" },
234
+ { label: "Pages deleted", value: pdf.pageCount ? deleted : "-" }
235
+ ]
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsx(
239
+ SideTips,
240
+ {
241
+ tips: [
242
+ "Drag a page onto another to move it there, or use the arrows.",
243
+ "Rotation is saved as part of the page, so every viewer shows it.",
244
+ "Your original file is not changed; you download a new copy."
245
+ ]
246
+ }
247
+ )
248
+ ] })
249
+ }
250
+ )
251
+ }
252
+ );
253
+ }
254
+
255
+ export { OrganizePdf };
256
+ //# sourceMappingURL=chunk-6NFNAKWA.js.map
257
+ //# sourceMappingURL=chunk-6NFNAKWA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/organize/OrganizePdf.tsx"],"names":[],"mappings":"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","file":"chunk-6NFNAKWA.js","sourcesContent":["import React, { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { downloadBlob, formatBytes, toBaseName } from \"../shared/files\";\nimport {\n BackToPdfTools,\n FileDropzone,\n PdfToolColumns,\n PdfToolPage,\n SelectedPdfCard,\n SideTips,\n StatusBanner,\n SummaryList,\n iconButtonClass,\n primaryButtonClass,\n secondaryButtonClass,\n} from \"../shared/ToolLayout\";\nimport { useRenderDocument, useThumbnails } from \"../shared/useRenderDocument\";\nimport { useSinglePdf } from \"../shared/useSinglePdf\";\nimport { organizePdf } from \"./organizePdfCore\";\n\nfunction initialPages(count) {\n return Array.from({ length: count }, (_, source) => ({ source, rotation: 0 }));\n}\n\nexport default function OrganizePdf() {\n const [pages, setPages] = useState([]);\n const [dragIndex, setDragIndex] = useState(null);\n const [dropIndex, setDropIndex] = useState(null);\n const [isSaving, setIsSaving] = useState(false);\n const [status, setStatus] = useState({\n tone: \"info\",\n text: \"Add a PDF to reorder, rotate, or delete its pages.\",\n });\n\n useEffect(() => {\n document.title = \"Organize PDF Pages | Apps\";\n }, []);\n\n const onError = useCallback((text) => setStatus({ tone: \"error\", text }), []);\n const onLoaded = useCallback(({ file, pageCount }) => {\n setPages(initialPages(pageCount));\n setStatus({\n tone: \"success\",\n text: `${file.name} loaded with ${pageCount} page${pageCount === 1 ? \"\" : \"s\"}. Drag pages to reorder.`,\n });\n }, []);\n const pdf = useSinglePdf({ onError, onLoaded });\n const { doc, error: renderError } = useRenderDocument(pdf.bytes);\n const thumbnails = useThumbnails(doc, 150);\n\n const changed = useMemo(\n () =>\n pages.length !== pdf.pageCount ||\n pages.some((page, index) => page.source !== index || page.rotation % 360 !== 0),\n [pages, pdf.pageCount]\n );\n const deleted = pdf.pageCount - pages.length;\n\n const clearFile = () => {\n pdf.clear();\n setPages([]);\n };\n\n const move = (from, to) => {\n setPages((current) => {\n if (to < 0 || to >= current.length || from === to) return current;\n const next = [...current];\n const [item] = next.splice(from, 1);\n next.splice(to, 0, item);\n return next;\n });\n };\n\n const rotate = (index, delta) => {\n setPages((current) =>\n current.map((page, i) => (i === index ? { ...page, rotation: page.rotation + delta } : page))\n );\n };\n\n const rotateAll = (delta) => {\n setPages((current) => current.map((page) => ({ ...page, rotation: page.rotation + delta })));\n };\n\n const remove = (index) => {\n setPages((current) => current.filter((_, i) => i !== index));\n };\n\n const handleDrop = (event, index) => {\n event.preventDefault();\n if (dragIndex !== null) move(dragIndex, index);\n setDragIndex(null);\n setDropIndex(null);\n };\n\n const handleSave = async () => {\n if (!pdf.bytes || !pages.length) return;\n setIsSaving(true);\n setStatus({ tone: \"info\", text: \"Saving your PDF...\" });\n try {\n const bytes = await organizePdf(pdf.bytes, pages);\n const blob = new Blob([bytes as BlobPart], { type: \"application/pdf\" });\n downloadBlob(blob, `${toBaseName(pdf.file.name, \"document\")}-organized.pdf`);\n setStatus({\n tone: \"success\",\n text: `Saved ${pages.length} page${pages.length === 1 ? \"\" : \"s\"} (${formatBytes(blob.size)}). Your download has started.`,\n });\n } catch (error) {\n setStatus({ tone: \"error\", text: `Save failed: ${error?.message || \"Please try again.\"}` });\n } finally {\n setIsSaving(false);\n }\n };\n\n const busy = isSaving || pdf.isReading;\n\n return (\n <PdfToolPage\n title=\"Organize Pages\"\n description=\"Reorder, rotate, and delete pages with thumbnail previews. Drag a page to move it. Runs in your browser; files never leave your device.\"\n stats={[\n { value: pages.length || \"-\", label: \"Pages\" },\n { value: deleted > 0 ? deleted : \"-\", label: \"Deleted\" },\n ]}\n >\n <PdfToolColumns\n main={\n <>\n {pdf.file ? (\n <SelectedPdfCard\n signed={pdf.signed}\n file={pdf.file}\n pageCount={pdf.pageCount}\n onRemove={clearFile}\n disabled={isSaving}\n />\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 ? (\n <p className=\"mt-3 text-xs text-rose-700 dark:text-rose-300\">{renderError}</p>\n ) : null}\n\n {pages.length ? (\n <>\n <div className=\"mt-5 flex flex-wrap gap-2\">\n <button type=\"button\" onClick={() => rotateAll(-90)} disabled={busy} className={secondaryButtonClass}>\n <i className=\"fa-solid fa-rotate-left\" />\n Rotate all left\n </button>\n <button type=\"button\" onClick={() => rotateAll(90)} disabled={busy} className={secondaryButtonClass}>\n <i className=\"fa-solid fa-rotate-right\" />\n Rotate all right\n </button>\n <button\n type=\"button\"\n onClick={() => setPages((current) => [...current].reverse())}\n disabled={busy}\n className={secondaryButtonClass}\n >\n <i className=\"fa-solid fa-arrow-down-wide-short\" />\n Reverse order\n </button>\n <button\n type=\"button\"\n onClick={() => setPages(initialPages(pdf.pageCount))}\n disabled={busy || !changed}\n className={secondaryButtonClass}\n >\n <i className=\"fa-solid fa-arrow-rotate-left\" />\n Reset\n </button>\n </div>\n\n <ol className=\"mt-4 grid grid-cols-2 gap-3 sm:grid-cols-3 xl:grid-cols-4\">\n {pages.map((page, index) => {\n const thumb = thumbnails[page.source];\n return (\n <li\n key={page.source}\n draggable={!busy}\n onDragStart={(event) => {\n setDragIndex(index);\n event.dataTransfer.effectAllowed = \"move\";\n }}\n onDragOver={(event) => {\n event.preventDefault();\n setDropIndex(index);\n }}\n onDragLeave={() => setDropIndex((current) => (current === index ? null : current))}\n onDrop={(event) => handleDrop(event, index)}\n onDragEnd={() => {\n setDragIndex(null);\n setDropIndex(null);\n }}\n className={`flex cursor-grab flex-col overflow-hidden rounded-2xl border bg-white/90 transition active:cursor-grabbing dark:bg-slate-900/80 ${\n dropIndex === index && dragIndex !== index\n ? \"border-[#1F8CFF] ring-2 ring-[#1F8CFF]/40\"\n : \"border-slate-200 dark:border-slate-700\"\n } ${dragIndex === index ? \"opacity-40\" : \"\"}`}\n >\n <div className=\"relative flex h-44 items-center justify-center overflow-hidden bg-slate-100 p-2 dark:bg-slate-800\">\n {thumb ? (\n <img\n src={thumb.url}\n alt={`Page ${page.source + 1}`}\n draggable={false}\n className=\"max-h-full max-w-full shadow-sm transition-transform\"\n style={{ transform: `rotate(${page.rotation}deg)`, maxHeight: page.rotation % 180 ? \"8.5rem\" : undefined }}\n />\n ) : (\n <i className=\"fa-solid fa-spinner fa-spin text-slate-400\" aria-label=\"Rendering\" />\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 <span\n data-source-page={page.source + 1}\n className=\"absolute right-2 top-2 rounded-lg bg-white/90 px-1.5 py-0.5 text-[10px] font-medium text-slate-600 shadow-sm dark:bg-slate-900/90 dark:text-slate-300\"\n >\n p.{page.source + 1}\n </span>\n </div>\n <div className=\"flex items-center justify-center p-2\">\n <div className=\"flex gap-1\">\n <button type=\"button\" onClick={() => move(index, index - 1)} disabled={busy || index === 0} className={iconButtonClass} aria-label={`Move page ${page.source + 1} earlier`}>\n <i className=\"fa-solid fa-arrow-left\" />\n </button>\n <button type=\"button\" onClick={() => move(index, index + 1)} disabled={busy || index === pages.length - 1} className={iconButtonClass} aria-label={`Move page ${page.source + 1} later`}>\n <i className=\"fa-solid fa-arrow-right\" />\n </button>\n <button type=\"button\" onClick={() => rotate(index, 90)} disabled={busy} className={iconButtonClass} aria-label={`Rotate page ${page.source + 1}`}>\n <i className=\"fa-solid fa-rotate-right\" />\n </button>\n <button type=\"button\" onClick={() => remove(index)} disabled={busy || pages.length === 1} className={iconButtonClass} aria-label={`Delete page ${page.source + 1}`}>\n <i className=\"fa-solid fa-trash\" />\n </button>\n </div>\n </div>\n </li>\n );\n })}\n </ol>\n </>\n ) : null}\n\n <StatusBanner status={status} />\n\n <div className=\"mt-5 flex flex-wrap gap-3\">\n <button type=\"button\" onClick={handleSave} disabled={busy || !pages.length || !changed} className={primaryButtonClass}>\n <i className={`fa-solid ${isSaving ? \"fa-spinner fa-spin\" : \"fa-floppy-disk\"}`} />\n {isSaving ? \"Saving...\" : \"Save PDF\"}\n </button>\n <BackToPdfTools />\n </div>\n </>\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: \"Original pages\", value: pdf.pageCount || \"-\" },\n { label: \"Pages kept\", value: pages.length || \"-\" },\n { label: \"Pages deleted\", value: pdf.pageCount ? deleted : \"-\" },\n ]}\n />\n <SideTips\n tips={[\n \"Drag a page onto another to move it there, or use the arrows.\",\n \"Rotation is saved as part of the page, so every viewer shows it.\",\n \"Your original file is not changed; you download a new copy.\",\n ]}\n />\n </>\n }\n />\n </PdfToolPage>\n );\n}\n"]}