Features/shadcn #1
Generated
+28
-4
@@ -3,7 +3,11 @@
|
|||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="89f9fc3a-63ad-41cb-8d25-f07df352eb98" name="Changes" comment="">
|
<list default="true" id="89f9fc3a-63ad-41cb-8d25-f07df352eb98" name="Changes" comment="">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/routes/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/routes/+page.svelte" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pnpm-lock.yaml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/lib/components/AttachmentPreview.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/components/AttachmentPreview.svelte" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/lib/components/ProcessedZipArchiveEditor.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/components/ProcessedZipArchiveEditor.svelte" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/lib/zip-processing.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/lib/zip-processing.ts" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -29,6 +33,7 @@
|
|||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.cidr.known.project.marker": "true",
|
"RunOnceActivity.cidr.known.project.marker": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
"RunOnceActivity.readMode.enableVisualFormatting": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"cidr.known.project.marker": "true",
|
"cidr.known.project.marker": "true",
|
||||||
@@ -49,7 +54,8 @@
|
|||||||
<option name="presentableId" value="Default" />
|
<option name="presentableId" value="Default" />
|
||||||
<updated>1781169139290</updated>
|
<updated>1781169139290</updated>
|
||||||
<workItem from="1781169141138" duration="309000" />
|
<workItem from="1781169141138" duration="309000" />
|
||||||
<workItem from="1781251694220" duration="14041000" />
|
<workItem from="1781251694220" duration="22281000" />
|
||||||
|
<workItem from="1781349431536" duration="774000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="adapts styling of rvg.legal">
|
<task id="LOCAL-00001" summary="adapts styling of rvg.legal">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -123,7 +129,23 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1781264568851</updated>
|
<updated>1781264568851</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="10" />
|
<task id="LOCAL-00010" summary="range settings">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1781267754325</created>
|
||||||
|
<option name="number" value="00010" />
|
||||||
|
<option name="presentableId" value="LOCAL-00010" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1781267754325</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00011" summary="name generation improvement">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1781268046478</created>
|
||||||
|
<option name="number" value="00011" />
|
||||||
|
<option name="presentableId" value="LOCAL-00011" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1781268046478</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="12" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -142,7 +164,9 @@
|
|||||||
<MESSAGE value="renders thumbnails" />
|
<MESSAGE value="renders thumbnails" />
|
||||||
<MESSAGE value="ignores mac special folders" />
|
<MESSAGE value="ignores mac special folders" />
|
||||||
<MESSAGE value="handles zips in zip correctly" />
|
<MESSAGE value="handles zips in zip correctly" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="handles zips in zip correctly" />
|
<MESSAGE value="range settings" />
|
||||||
|
<MESSAGE value="name generation improvement" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="name generation improvement" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"@skeletonlabs/skeleton": "^4.15.2",
|
"@skeletonlabs/skeleton": "^4.15.2",
|
||||||
"@skeletonlabs/skeleton-svelte": "^4.15.2",
|
"@skeletonlabs/skeleton-svelte": "^4.15.2",
|
||||||
"fflate": "^0.8.3",
|
"fflate": "^0.8.3",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^6.0.227"
|
"pdfjs-dist": "^6.0.227"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+37
@@ -17,6 +17,9 @@ importers:
|
|||||||
fflate:
|
fflate:
|
||||||
specifier: ^0.8.3
|
specifier: ^0.8.3
|
||||||
version: 0.8.3
|
version: 0.8.3
|
||||||
|
pdf-lib:
|
||||||
|
specifier: ^1.17.1
|
||||||
|
version: 1.17.1
|
||||||
pdfjs-dist:
|
pdfjs-dist:
|
||||||
specifier: ^6.0.227
|
specifier: ^6.0.227
|
||||||
version: 6.0.227
|
version: 6.0.227
|
||||||
@@ -187,6 +190,12 @@ packages:
|
|||||||
'@oxc-project/types@0.133.0':
|
'@oxc-project/types@0.133.0':
|
||||||
resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==}
|
resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==}
|
||||||
|
|
||||||
|
'@pdf-lib/standard-fonts@1.0.0':
|
||||||
|
resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==}
|
||||||
|
|
||||||
|
'@pdf-lib/upng@1.0.1':
|
||||||
|
resolution: {integrity: sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==}
|
||||||
|
|
||||||
'@polka/url@1.0.0-next.29':
|
'@polka/url@1.0.0-next.29':
|
||||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||||
|
|
||||||
@@ -798,6 +807,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==}
|
resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==}
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
|
|
||||||
|
pako@1.0.11:
|
||||||
|
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||||
|
|
||||||
|
pdf-lib@1.17.1:
|
||||||
|
resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==}
|
||||||
|
|
||||||
pdfjs-dist@6.0.227:
|
pdfjs-dist@6.0.227:
|
||||||
resolution: {integrity: sha512-/P6M4SXw+70waMVLUM7rdRtvo+dEzqE1t6W/zQNvBETo2MaRa5rrvCcAYdfWGiUzadTgM0lJmRApUrW0d9zgKg==}
|
resolution: {integrity: sha512-/P6M4SXw+70waMVLUM7rdRtvo+dEzqE1t6W/zQNvBETo2MaRa5rrvCcAYdfWGiUzadTgM0lJmRApUrW0d9zgKg==}
|
||||||
engines: {node: '>=22.13.0 || >=24'}
|
engines: {node: '>=22.13.0 || >=24'}
|
||||||
@@ -938,6 +953,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
tslib@1.14.1:
|
||||||
|
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||||
|
|
||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
@@ -1112,6 +1130,14 @@ snapshots:
|
|||||||
|
|
||||||
'@oxc-project/types@0.133.0': {}
|
'@oxc-project/types@0.133.0': {}
|
||||||
|
|
||||||
|
'@pdf-lib/standard-fonts@1.0.0':
|
||||||
|
dependencies:
|
||||||
|
pako: 1.0.11
|
||||||
|
|
||||||
|
'@pdf-lib/upng@1.0.1':
|
||||||
|
dependencies:
|
||||||
|
pako: 1.0.11
|
||||||
|
|
||||||
'@polka/url@1.0.0-next.29': {}
|
'@polka/url@1.0.0-next.29': {}
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.0.3':
|
'@rolldown/binding-android-arm64@1.0.3':
|
||||||
@@ -1786,6 +1812,15 @@ snapshots:
|
|||||||
|
|
||||||
obug@2.1.2: {}
|
obug@2.1.2: {}
|
||||||
|
|
||||||
|
pako@1.0.11: {}
|
||||||
|
|
||||||
|
pdf-lib@1.17.1:
|
||||||
|
dependencies:
|
||||||
|
'@pdf-lib/standard-fonts': 1.0.0
|
||||||
|
'@pdf-lib/upng': 1.0.1
|
||||||
|
pako: 1.0.11
|
||||||
|
tslib: 1.14.1
|
||||||
|
|
||||||
pdfjs-dist@6.0.227:
|
pdfjs-dist@6.0.227:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@napi-rs/canvas': 1.0.0
|
'@napi-rs/canvas': 1.0.0
|
||||||
@@ -1902,6 +1937,8 @@ snapshots:
|
|||||||
|
|
||||||
totalist@3.0.1: {}
|
totalist@3.0.1: {}
|
||||||
|
|
||||||
|
tslib@1.14.1: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
typescript@6.0.3: {}
|
typescript@6.0.3: {}
|
||||||
|
|||||||
@@ -50,7 +50,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const loadingTask = getDocument({ data: attachment.data });
|
// create a copy, getDocument seems to manipulate the data -> creating pdfs fails
|
||||||
|
const buffer = new Uint8Array(attachment.data);
|
||||||
|
const loadingTask = getDocument({ data: buffer });
|
||||||
const pdfDocument = await loadingTask.promise;
|
const pdfDocument = await loadingTask.promise;
|
||||||
const page = await pdfDocument.getPage(1);
|
const page = await pdfDocument.getPage(1);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AttachmentPreview from "$lib/components/AttachmentPreview.svelte";
|
import AttachmentPreview from "$lib/components/AttachmentPreview.svelte";
|
||||||
import type {ProcessedZipArchive, ZipAttachment} from "$lib/zip-processing";
|
import { mergeProcessedZipArchive, type ProcessedZipArchive, type ZipAttachment } from "$lib/zip-processing";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
archive: ProcessedZipArchive;
|
archive: ProcessedZipArchive;
|
||||||
@@ -19,6 +19,14 @@
|
|||||||
let archiveName = $state("");
|
let archiveName = $state("");
|
||||||
let dragIndex = $state<number | null>(null);
|
let dragIndex = $state<number | null>(null);
|
||||||
let dropIndex = $state<number | null>(null);
|
let dropIndex = $state<number | null>(null);
|
||||||
|
let isMerging = $state(false);
|
||||||
|
let mergeError = $state<string | null>(null);
|
||||||
|
|
||||||
|
const toArrayBuffer = (bytes: Uint8Array) => {
|
||||||
|
const copy = new Uint8Array(bytes.byteLength);
|
||||||
|
copy.set(bytes);
|
||||||
|
return copy.buffer;
|
||||||
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
archiveName = archive.name;
|
archiveName = archive.name;
|
||||||
@@ -33,6 +41,37 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const downloadMergedPdf = async () => {
|
||||||
|
if (attachments.length === 0 || isMerging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMerging = true;
|
||||||
|
mergeError = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const mergedBytes = await mergeProcessedZipArchive({
|
||||||
|
...archive,
|
||||||
|
name: archiveName,
|
||||||
|
attachments: attachments.map((attachment) => ({...attachment}))
|
||||||
|
});
|
||||||
|
|
||||||
|
const pdfBlob = new Blob([toArrayBuffer(mergedBytes)], {type: "application/pdf"});
|
||||||
|
const objectUrl = URL.createObjectURL(pdfBlob);
|
||||||
|
const downloadLink = document.createElement("a");
|
||||||
|
downloadLink.href = objectUrl;
|
||||||
|
downloadLink.download = archiveName.toLowerCase().endsWith(".pdf") ? archiveName : `${archiveName}.pdf`;
|
||||||
|
downloadLink.rel = "noopener";
|
||||||
|
downloadLink.click();
|
||||||
|
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to merge archive ${archive.name}`, error);
|
||||||
|
mergeError = "PDF konnte nicht erstellt werden.";
|
||||||
|
} finally {
|
||||||
|
isMerging = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const moveAttachment = (fromIndex: number, toIndex: number) => {
|
const moveAttachment = (fromIndex: number, toIndex: number) => {
|
||||||
if (
|
if (
|
||||||
fromIndex === toIndex ||
|
fromIndex === toIndex ||
|
||||||
@@ -107,8 +146,23 @@
|
|||||||
{attachments.length} relevante Datei{attachments.length === 1 ? "" : "en"}
|
{attachments.length} relevante Datei{attachments.length === 1 ? "" : "en"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="rounded-container whitespace-nowrap bg-primary-900 px-4 py-2 text-[14px] font-bold leading-6 text-white shadow-sm transition hover:bg-primary-800 disabled:cursor-not-allowed disabled:bg-primary-300"
|
||||||
|
type="button"
|
||||||
|
disabled={attachments.length === 0 || isMerging}
|
||||||
|
onclick={downloadMergedPdf}
|
||||||
|
>
|
||||||
|
{isMerging ? "PDF wird erstellt" : "PDF herunterladen"}
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
{#if mergeError}
|
||||||
|
<div class="rounded-container bg-red-50 px-4 py-3 text-[14px] font-medium text-red-900">
|
||||||
|
{mergeError}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<ul class="flex gap-3 overflow-x-auto pb-1">
|
<ul class="flex gap-3 overflow-x-auto pb-1">
|
||||||
{#each attachments as attachment, index (attachment.path)}
|
{#each attachments as attachment, index (attachment.path)}
|
||||||
<li
|
<li
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { unzip } from 'fflate';
|
import { unzip } from 'fflate';
|
||||||
|
import { PDFDocument } from 'pdf-lib';
|
||||||
|
|
||||||
export type ZipAttachmentKind = 'pdf' | 'image';
|
export type ZipAttachmentKind = 'pdf' | 'image';
|
||||||
|
|
||||||
@@ -287,3 +288,121 @@ export const extractZipArchives = async (file: File): Promise<ProcessedZipArchiv
|
|||||||
|
|
||||||
return extractZipEntries(archiveBytes, file.name);
|
return extractZipEntries(archiveBytes, file.name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadImageElement = (src: string) =>
|
||||||
|
new Promise<HTMLImageElement>((resolve, reject) => {
|
||||||
|
const image = new Image();
|
||||||
|
image.decoding = 'async';
|
||||||
|
image.onload = () => resolve(image);
|
||||||
|
image.onerror = () => reject(new Error(`Unable to decode image asset: ${src}`));
|
||||||
|
image.src = src;
|
||||||
|
});
|
||||||
|
|
||||||
|
const toArrayBuffer = (bytes: Uint8Array) =>
|
||||||
|
(() => {
|
||||||
|
const copy = new Uint8Array(bytes.byteLength);
|
||||||
|
copy.set(bytes);
|
||||||
|
return copy.buffer;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const convertImageBytesToPng = async (imageBytes: Uint8Array) => {
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
throw new Error('Image conversion requires a browser environment.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectUrl = URL.createObjectURL(new Blob([toArrayBuffer(imageBytes)]));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const image = await loadImageElement(objectUrl);
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = image.naturalWidth || image.width;
|
||||||
|
canvas.height = image.naturalHeight || image.height;
|
||||||
|
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('Could not create a canvas context for image conversion.');
|
||||||
|
}
|
||||||
|
|
||||||
|
context.drawImage(image, 0, 0);
|
||||||
|
|
||||||
|
const pngBlob = await new Promise<Blob>((resolve, reject) => {
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
resolve(blob);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new Error('Could not encode image asset as PNG.'));
|
||||||
|
}, 'image/png');
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Uint8Array(await pngBlob.arrayBuffer());
|
||||||
|
} finally {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const embedAttachmentImage = async (pdfDocument: PDFDocument, attachment: ZipAttachment) => {
|
||||||
|
const extension = getExtension(attachment.path);
|
||||||
|
const imageData = attachment.data;
|
||||||
|
|
||||||
|
if (extension === '.jpg' || extension === '.jpeg') {
|
||||||
|
return pdfDocument.embedJpg(imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension === '.png') {
|
||||||
|
return pdfDocument.embedPng(imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pngBytes = await convertImageBytesToPng(imageData);
|
||||||
|
|
||||||
|
return pdfDocument.embedPng(pngBytes);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeAttachmentIntoPdf = async (mergedPdf: PDFDocument, attachment: ZipAttachment) => {
|
||||||
|
const canLoadAsPdf = attachment.kind === 'pdf'
|
||||||
|
|
||||||
|
if (canLoadAsPdf) {
|
||||||
|
const sourcePdf = await PDFDocument.load(attachment.data, { ignoreEncryption: true });
|
||||||
|
const copiedPages = await mergedPdf.copyPages(sourcePdf, sourcePdf.getPageIndices());
|
||||||
|
|
||||||
|
for (const page of copiedPages) {
|
||||||
|
mergedPdf.addPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const embeddedImage = await embedAttachmentImage(mergedPdf, attachment);
|
||||||
|
const page = mergedPdf.addPage([embeddedImage.width, embeddedImage.height]);
|
||||||
|
|
||||||
|
page.drawImage(embeddedImage, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: page.getWidth(),
|
||||||
|
height: page.getHeight()
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (attachment.kind === 'pdf') {
|
||||||
|
throw new Error(`Attachment "${attachment.name}" is not a valid PDF and could not be rendered as an image.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mergeProcessedZipArchive = async (archive: ProcessedZipArchive) => {
|
||||||
|
if (archive.attachments.length === 0) {
|
||||||
|
throw new Error('Cannot create a PDF from an empty archive.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergedPdf = await PDFDocument.create();
|
||||||
|
|
||||||
|
for (const attachment of archive.attachments) {
|
||||||
|
await mergeAttachmentIntoPdf(mergedPdf, attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedPdf.save();
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user