init commit

This commit is contained in:
iqudoo
2026-06-12 19:34:27 +08:00
commit 89b59ecc1b
38 changed files with 17719 additions and 0 deletions

166
src/pdfExport.ts Normal file
View File

@@ -0,0 +1,166 @@
import { jsPDF } from 'jspdf';
import type { PaperConfig, LabelTemplate, CutLineConfig } from './types';
import type { PrintableLabel } from './utils';
export interface PdfExportProgress {
done: number;
total: number;
page: number;
totalPages: number;
phase: 'render' | 'save';
}
export interface AsyncExportLabelsPdfOptions {
paper: PaperConfig;
template: LabelTemplate;
printablePages: (PrintableLabel | null)[][];
gridCols: number;
cutLine: CutLineConfig;
filename?: string;
imageFormat: 'PNG' | 'JPEG';
renderLabel: (label: PrintableLabel) => Promise<string>;
onProgress?: (progress: PdfExportProgress) => void;
}
const yieldToMain = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
const PAGE_RENDER_CONCURRENCY = 3;
function parseColorToRgb(color: string): [number, number, number] {
const c = color.trim();
if (c.startsWith('#')) {
const hex = c.slice(1);
if (hex.length === 3) {
return [
parseInt(hex[0] + hex[0], 16),
parseInt(hex[1] + hex[1], 16),
parseInt(hex[2] + hex[2], 16),
];
}
if (hex.length >= 6) {
return [
parseInt(hex.slice(0, 2), 16),
parseInt(hex.slice(2, 4), 16),
parseInt(hex.slice(4, 6), 16),
];
}
}
return [204, 204, 204];
}
function drawCutLineRect(
pdf: jsPDF,
x: number,
y: number,
w: number,
h: number,
cutLine: CutLineConfig
) {
const [r, g, b] = parseColorToRgb(cutLine.color);
pdf.setDrawColor(r, g, b);
pdf.setLineWidth(Math.max(0.02, cutLine.width));
if (cutLine.style === 'dashed') {
const dash = Math.max(0.5, cutLine.width * 4);
pdf.setLineDashPattern([dash, dash], 0);
} else {
pdf.setLineDashPattern([], 0);
}
pdf.rect(x, y, w, h, 'S');
pdf.setLineDashPattern([], 0);
}
/** 按页流式渲染并写入 PDF避免大批量时内存暴涨与长时间阻塞 */
export async function exportLabelsPdfAsync(options: AsyncExportLabelsPdfOptions): Promise<void> {
const {
paper,
template,
printablePages,
gridCols,
cutLine,
filename = 'labels.pdf',
imageFormat,
renderLabel,
onProgress,
} = options;
const paperW = paper.orientation === 'portrait' ? paper.width : paper.height;
const paperH = paper.orientation === 'portrait' ? paper.height : paper.width;
const orientation = paperW > paperH ? 'landscape' : 'portrait';
const labelW = template.width;
const labelH = template.height;
const totalPages = printablePages.length;
const totalLabels = printablePages.reduce(
(sum, page) => sum + page.filter((item) => item !== null).length,
0
);
const pdf = new jsPDF({
orientation,
unit: 'mm',
format: [paperW, paperH],
compress: true,
});
let done = 0;
let progressTick = 0;
const report = (page: number, phase: PdfExportProgress['phase']) => {
onProgress?.({ done, total: totalLabels, page, totalPages, phase });
};
for (let pIdx = 0; pIdx < printablePages.length; pIdx++) {
if (pIdx > 0) {
pdf.addPage([paperW, paperH], orientation);
}
const pageRows = printablePages[pIdx];
report(pIdx + 1, 'render');
const pageJobs: { cellIdx: number; label: PrintableLabel }[] = [];
for (let cellIdx = 0; cellIdx < pageRows.length; cellIdx++) {
const item = pageRows[cellIdx];
if (!item) continue;
pageJobs.push({ cellIdx, label: item });
}
for (let i = 0; i < pageJobs.length; i += PAGE_RENDER_CONCURRENCY) {
const batch = pageJobs.slice(i, i + PAGE_RENDER_CONCURRENCY);
const rendered = await Promise.all(
batch.map(async (job) => ({
...job,
dataUrl: await renderLabel(job.label),
}))
);
for (const { cellIdx, dataUrl } of rendered) {
const col = cellIdx % gridCols;
const row = Math.floor(cellIdx / gridCols);
const x = paper.marginLeft + col * (labelW + paper.columnGap);
const y = paper.marginTop + row * (labelH + paper.rowGap);
pdf.addImage(dataUrl, imageFormat, x, y, labelW, labelH, undefined, 'FAST');
if (cutLine.enabled) {
drawCutLineRect(pdf, x, y, labelW, labelH, cutLine);
}
done++;
}
progressTick++;
if (progressTick >= 2) {
progressTick = 0;
report(pIdx + 1, 'render');
await yieldToMain();
}
}
report(pIdx + 1, 'render');
await yieldToMain();
}
report(totalPages, 'save');
await yieldToMain();
pdf.save(filename);
}