feat: implement segment-based PDF download functionality with a dedicated UI for multi-page export
Build and Push Multi-Platform Images / build-and-push (push) Successful in 34s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 34s
This commit is contained in:
@@ -53,6 +53,36 @@ export async function cleanupTemp(filePath: string | null): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut ein PDF aus einer Teilmenge von Originalseiten auf (rotiert, keine gelöschten Seiten).
|
||||
* segmentPages enthält 1-basierte Originalseitenzahlen; gelöschte Seiten müssen vom Aufrufer
|
||||
* bereits herausgefiltert worden sein.
|
||||
*/
|
||||
export async function buildSegmentBuffer(
|
||||
doc: InboxDocument,
|
||||
pdfPath: string,
|
||||
segmentPages: number[],
|
||||
): Promise<Buffer> {
|
||||
const bytes = await fs.readFile(pdfPath);
|
||||
const srcPdf = await PDFDocument.load(bytes, { ignoreEncryption: true });
|
||||
const outPdf = await PDFDocument.create();
|
||||
|
||||
const rotations = doc.Rotations ?? {};
|
||||
const indices = segmentPages.map((p) => p - 1);
|
||||
const copied = await outPdf.copyPages(srcPdf, indices);
|
||||
|
||||
copied.forEach((page, i) => {
|
||||
const rot = rotations[String(segmentPages[i])];
|
||||
if (rot !== undefined) {
|
||||
const normalized = ((Math.round(rot / 90) * 90) % 360 + 360) % 360;
|
||||
if (normalized !== 0) page.setRotation(degrees(normalized));
|
||||
}
|
||||
outPdf.addPage(page);
|
||||
});
|
||||
|
||||
return Buffer.from(await outPdf.save());
|
||||
}
|
||||
|
||||
export async function extractSectionToTemp(
|
||||
pdfPath: string,
|
||||
pageIndices: number[],
|
||||
|
||||
@@ -176,14 +176,15 @@ export class InboxController {
|
||||
await this.inboxService.updateSource(id, body.source, preferredUsername);
|
||||
}
|
||||
|
||||
@Get(':id/download')
|
||||
async download(
|
||||
@Post(':id/download-segment')
|
||||
async downloadSegment(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { pages: number[] },
|
||||
@Request() req: any,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
): Promise<StreamableFile> {
|
||||
const preferredUsername: string | null = req.user?.preferredUsername ?? null;
|
||||
const { buffer, filename } = await this.inboxService.getEditedPdfBuffer(id, preferredUsername);
|
||||
const { buffer, filename } = await this.inboxService.getSegmentPdfBuffer(id, preferredUsername, body.pages ?? []);
|
||||
const { Readable } = await import('stream');
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type InboxSource,
|
||||
} from '../database/entities/inbox-document.entity';
|
||||
import { MailService } from '../postprocessing/mail.service';
|
||||
import { applyEditsToTemp, cleanupTemp } from '../inbox-postprocessor/edit-applier';
|
||||
import { applyEditsToTemp, cleanupTemp, buildSegmentBuffer } from '../inbox-postprocessor/edit-applier';
|
||||
|
||||
export interface InboxFile {
|
||||
id: string;
|
||||
@@ -253,19 +253,16 @@ export class InboxService {
|
||||
return this.barcodeScanner.scanRegion(doc, pdfPath, page, x, y, w, h);
|
||||
}
|
||||
|
||||
async getEditedPdfBuffer(
|
||||
async getSegmentPdfBuffer(
|
||||
id: string,
|
||||
preferredUsername: string | null,
|
||||
pages: number[],
|
||||
): Promise<{ buffer: Buffer; filename: string }> {
|
||||
const { doc, pdfPath } = await this.resolveDocument(id, preferredUsername);
|
||||
let tmpPath: string | null = null;
|
||||
try {
|
||||
tmpPath = await applyEditsToTemp(doc, pdfPath);
|
||||
const buffer = await fs.readFile(tmpPath);
|
||||
return { buffer, filename: doc.OriginalName };
|
||||
} finally {
|
||||
await cleanupTemp(tmpPath);
|
||||
}
|
||||
const deleted = new Set(doc.DeletedPages ?? []);
|
||||
const safePages = pages.filter((p) => p >= 1 && p <= doc.PageCount && !deleted.has(p));
|
||||
const buffer = await buildSegmentBuffer(doc, pdfPath, safePages);
|
||||
return { buffer, filename: doc.OriginalName };
|
||||
}
|
||||
|
||||
async sendAsEmail(
|
||||
|
||||
Reference in New Issue
Block a user