mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Initial addition of encrypted PDF thumbnail
This commit is contained in:
parent
196b1de21c
commit
d3179ad44c
@ -1,5 +1,15 @@
|
|||||||
import { getDocument } from "pdfjs-dist";
|
import { getDocument } from "pdfjs-dist";
|
||||||
|
|
||||||
|
interface ColorScheme {
|
||||||
|
bgTop: string;
|
||||||
|
bgBottom: string;
|
||||||
|
border: string;
|
||||||
|
icon: string;
|
||||||
|
badge: string;
|
||||||
|
textPrimary: string;
|
||||||
|
textSecondary: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate thumbnail scale based on file size
|
* Calculate thumbnail scale based on file size
|
||||||
* Smaller files get higher quality, larger files get lower quality
|
* Smaller files get higher quality, larger files get lower quality
|
||||||
@ -14,6 +24,54 @@ export function calculateScaleFromFileSize(fileSize: number): number {
|
|||||||
return 0.15; // 30MB+: Low quality
|
return 0.15; // 30MB+: Low quality
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate encrypted PDF thumbnail with lock icon
|
||||||
|
*/
|
||||||
|
function generateEncryptedPDFThumbnail(file: File): string {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = 120;
|
||||||
|
canvas.height = 150;
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
|
||||||
|
// Use PDF color scheme but with encrypted styling
|
||||||
|
const colorScheme = getFileTypeColorScheme('PDF');
|
||||||
|
|
||||||
|
// Create gradient background
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
|
||||||
|
gradient.addColorStop(0, colorScheme.bgTop);
|
||||||
|
gradient.addColorStop(1, colorScheme.bgBottom);
|
||||||
|
|
||||||
|
// Rounded rectangle background
|
||||||
|
drawRoundedRect(ctx, 8, 8, canvas.width - 16, canvas.height - 16, 8);
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Border with dashed pattern for encrypted indicator
|
||||||
|
ctx.strokeStyle = colorScheme.border;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.setLineDash([4, 4]);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]); // Reset dash pattern
|
||||||
|
|
||||||
|
// Modern document icon
|
||||||
|
drawModernDocumentIcon(ctx, canvas.width / 2, 35, colorScheme.icon);
|
||||||
|
|
||||||
|
// Large lock icon overlay
|
||||||
|
drawLockIcon(ctx, canvas.width / 2, canvas.height / 2, colorScheme);
|
||||||
|
|
||||||
|
// "ENCRYPTED" badge
|
||||||
|
drawEncryptionBadge(ctx, canvas.width / 2, canvas.height / 2 + 25, colorScheme);
|
||||||
|
|
||||||
|
// File size with subtle styling
|
||||||
|
const sizeText = formatFileSize(file.size);
|
||||||
|
ctx.font = '11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||||||
|
ctx.fillStyle = colorScheme.textSecondary;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(sizeText, canvas.width / 2, canvas.height - 15);
|
||||||
|
|
||||||
|
return canvas.toDataURL();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate modern placeholder thumbnail with file extension
|
* Generate modern placeholder thumbnail with file extension
|
||||||
*/
|
*/
|
||||||
@ -61,8 +119,8 @@ function generatePlaceholderThumbnail(file: File): string {
|
|||||||
/**
|
/**
|
||||||
* Get color scheme based on file extension
|
* Get color scheme based on file extension
|
||||||
*/
|
*/
|
||||||
function getFileTypeColorScheme(extension: string) {
|
function getFileTypeColorScheme(extension: string): ColorScheme {
|
||||||
const schemes: Record<string, any> = {
|
const schemes: Record<string, ColorScheme> = {
|
||||||
// Documents
|
// Documents
|
||||||
'PDF': { bgTop: '#FF6B6B20', bgBottom: '#FF6B6B10', border: '#FF6B6B40', icon: '#FF6B6B', badge: '#FF6B6B', textPrimary: '#FFFFFF', textSecondary: '#666666' },
|
'PDF': { bgTop: '#FF6B6B20', bgBottom: '#FF6B6B10', border: '#FF6B6B40', icon: '#FF6B6B', badge: '#FF6B6B', textPrimary: '#FFFFFF', textSecondary: '#666666' },
|
||||||
'DOC': { bgTop: '#4ECDC420', bgBottom: '#4ECDC410', border: '#4ECDC440', icon: '#4ECDC4', badge: '#4ECDC4', textPrimary: '#FFFFFF', textSecondary: '#666666' },
|
'DOC': { bgTop: '#4ECDC420', bgBottom: '#4ECDC410', border: '#4ECDC440', icon: '#4ECDC4', badge: '#4ECDC4', textPrimary: '#FFFFFF', textSecondary: '#666666' },
|
||||||
@ -130,10 +188,64 @@ function drawModernDocumentIcon(ctx: CanvasRenderingContext2D, centerX: number,
|
|||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw lock icon for encrypted PDFs
|
||||||
|
*/
|
||||||
|
function drawLockIcon(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, colorScheme: ColorScheme) {
|
||||||
|
const size = 16;
|
||||||
|
ctx.fillStyle = colorScheme.icon;
|
||||||
|
ctx.strokeStyle = colorScheme.icon;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
|
||||||
|
// Lock body (rectangle)
|
||||||
|
const bodyWidth = size;
|
||||||
|
const bodyHeight = size * 0.6;
|
||||||
|
const bodyX = centerX - bodyWidth / 2;
|
||||||
|
const bodyY = centerY - bodyHeight / 4;
|
||||||
|
|
||||||
|
drawRoundedRect(ctx, bodyX, bodyY, bodyWidth, bodyHeight, 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Lock shackle (semicircle)
|
||||||
|
const shackleRadius = size * 0.35;
|
||||||
|
const shackleY = centerY - size * 0.4;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, shackleY, shackleRadius, Math.PI, 2 * Math.PI);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Keyhole
|
||||||
|
ctx.fillStyle = colorScheme.textPrimary;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY - 2, 2, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillRect(centerX - 1, centerY - 2, 2, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw encryption badge
|
||||||
|
*/
|
||||||
|
function drawEncryptionBadge(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, colorScheme: ColorScheme) {
|
||||||
|
const extension = "ENCRYPTED";
|
||||||
|
const badgeWidth = extension.length * 6 + 12;
|
||||||
|
const badgeHeight = 18;
|
||||||
|
|
||||||
|
// Badge background
|
||||||
|
drawRoundedRect(ctx, centerX - badgeWidth/2, centerY - badgeHeight/2, badgeWidth, badgeHeight, 9);
|
||||||
|
ctx.fillStyle = colorScheme.badge;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Badge text
|
||||||
|
ctx.font = 'bold 9px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
||||||
|
ctx.fillStyle = colorScheme.textPrimary;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(extension, centerX, centerY + 3);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draw extension badge
|
* Draw extension badge
|
||||||
*/
|
*/
|
||||||
function drawExtensionBadge(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, extension: string, colorScheme: any) {
|
function drawExtensionBadge(ctx: CanvasRenderingContext2D, centerX: number, centerY: number, extension: string, colorScheme: ColorScheme) {
|
||||||
const badgeWidth = Math.max(extension.length * 8 + 16, 40);
|
const badgeWidth = Math.max(extension.length * 8 + 16, 40);
|
||||||
const badgeHeight = 22;
|
const badgeHeight = 22;
|
||||||
|
|
||||||
@ -199,6 +311,13 @@ export async function generateThumbnailForFile(file: File): Promise<string | und
|
|||||||
disableStream: true
|
disableStream: true
|
||||||
}).promise;
|
}).promise;
|
||||||
|
|
||||||
|
// Check if PDF is encrypted
|
||||||
|
if ((pdf as any).isEncrypted) {
|
||||||
|
console.log('PDF is encrypted, generating encrypted thumbnail:', file.name);
|
||||||
|
pdf.destroy();
|
||||||
|
return generateEncryptedPDFThumbnail(file);
|
||||||
|
}
|
||||||
|
|
||||||
const page = await pdf.getPage(1);
|
const page = await pdf.getPage(1);
|
||||||
const viewport = page.getViewport({ scale }); // Dynamic scale based on file size
|
const viewport = page.getViewport({ scale }); // Dynamic scale based on file size
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
@ -220,6 +339,13 @@ export async function generateThumbnailForFile(file: File): Promise<string | und
|
|||||||
return thumbnail;
|
return thumbnail;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
|
// Check if error indicates encrypted PDF
|
||||||
|
const errorMessage = error.message.toLowerCase();
|
||||||
|
if (errorMessage.includes('password') || errorMessage.includes('encrypted')) {
|
||||||
|
console.log('PDF appears to be encrypted based on error, generating encrypted thumbnail:', file.name);
|
||||||
|
return generateEncryptedPDFThumbnail(file);
|
||||||
|
}
|
||||||
|
|
||||||
if (error.name === 'InvalidPDFException') {
|
if (error.name === 'InvalidPDFException') {
|
||||||
console.warn(`PDF structure issue for ${file.name} - using fallback thumbnail`);
|
console.warn(`PDF structure issue for ${file.name} - using fallback thumbnail`);
|
||||||
// Return a placeholder or try with full file instead of chunk
|
// Return a placeholder or try with full file instead of chunk
|
||||||
@ -232,6 +358,13 @@ export async function generateThumbnailForFile(file: File): Promise<string | und
|
|||||||
verbosity: 0 // Reduce PDF.js warnings
|
verbosity: 0 // Reduce PDF.js warnings
|
||||||
}).promise;
|
}).promise;
|
||||||
|
|
||||||
|
// Check if PDF is encrypted in fallback too
|
||||||
|
if ((pdf as any).isEncrypted) {
|
||||||
|
console.log('PDF is encrypted in fallback, generating encrypted thumbnail:', file.name);
|
||||||
|
pdf.destroy();
|
||||||
|
return generateEncryptedPDFThumbnail(file);
|
||||||
|
}
|
||||||
|
|
||||||
const page = await pdf.getPage(1);
|
const page = await pdf.getPage(1);
|
||||||
const viewport = page.getViewport({ scale });
|
const viewport = page.getViewport({ scale });
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user