Rework generation page and settings

This commit is contained in:
Roman Krček
2025-07-29 15:56:42 +02:00
parent 39b15f1314
commit 1fde370890
5 changed files with 711 additions and 636 deletions

1
src/fontkit.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module 'fontkit';

File diff suppressed because it is too large Load Diff

View File

@@ -78,7 +78,7 @@
{#if photo.status === 'loading'} {#if photo.status === 'loading'}
<div class="border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm"> <div class="border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm">
<div class="h-48 bg-gray-100 flex items-center justify-center"> <div class="h-48 bg-gray-200 flex items-center justify-center">
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<div <div
class="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin mb-2" class="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin mb-2"
@@ -94,7 +94,7 @@
{:else if photo.status === 'success' && photo.objectUrl} {:else if photo.status === 'success' && photo.objectUrl}
<div class="border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm relative"> <div class="border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm relative">
<div <div
class="h-48 bg-gray-100 flex items-center justify-center relative overflow-hidden" class="h-48 bg-gray-200 flex items-center justify-center relative overflow-hidden"
bind:this={imageContainer} bind:this={imageContainer}
> >
<img <img
@@ -152,7 +152,7 @@
</div> </div>
{:else if photo.status === 'error'} {:else if photo.status === 'error'}
<div class="border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm"> <div class="border border-gray-200 rounded-lg overflow-hidden bg-white shadow-sm">
<div class="h-48 bg-gray-100 flex items-center justify-center"> <div class="h-48 bg-gray-200 flex items-center justify-center">
<div class="flex flex-col items-center text-center p-4"> <div class="flex flex-col items-center text-center p-4">
<svg <svg
class="w-12 h-12 text-red-400 mb-2" class="w-12 h-12 text-red-400 mb-2"

View File

@@ -1,157 +1,97 @@
// PDF Layout Configuration Module // PDF Layout Configuration Module
// Centralized configuration for PDF generation layouts // Centralized configuration for PDF generation layouts, using millimeters.
export interface PDFDimensions { import {
pageWidth: number; PHOTO_DIMENSIONS,
pageHeight: number; TEXT_FIELD_LAYOUT,
margin: number; PHOTO_FIELD_LAYOUT
} } from './pdfSettings';
// Conversion factor from millimeters to points (1 inch = 72 points, 1 inch = 25.4 mm)
export const MM_TO_PT = 72 / 25.4;
export interface GridLayout { export interface GridLayout {
cols: number; cols: number;
rows: number; rows: number;
cellWidth: number; cellWidth: number; // mm
cellHeight: number; cellHeight: number; // mm
} }
export interface TextPosition { // Calculate how many cards can fit on a page.
x: number; export function calculateGrid(
y: number; pageWidth: number,
size: number; pageHeight: number,
} margin: number,
cardWidth: number,
export interface PhotoPosition { cardHeight: number
x: number;
y: number;
width: number;
height: number;
}
export interface TextFieldLayout {
name: TextPosition;
nationality: TextPosition;
birthday: TextPosition;
}
export interface PhotoFieldLayout {
photo: PhotoPosition;
name: TextPosition;
}
// A4 dimensions in points
export const PDF_DIMENSIONS: PDFDimensions = {
pageWidth: 595.28,
pageHeight: 841.89,
margin: 40
};
// Text PDF Layout (3x7 grid)
export const TEXT_PDF_GRID = {
cols: 3,
rows: 7
};
// Photo PDF Layout (3x5 grid)
export const PHOTO_PDF_GRID = {
cols: 3,
rows: 5
};
// Calculate grid layout
export function calculateGridLayout(
dimensions: PDFDimensions,
grid: { cols: number; rows: number }
): GridLayout { ): GridLayout {
const cellWidth = (dimensions.pageWidth - 2 * dimensions.margin) / grid.cols; const printableWidth = pageWidth - 2 * margin;
const cellHeight = (dimensions.pageHeight - 2 * dimensions.margin) / grid.rows; const printableHeight = pageHeight - 2 * margin;
return { const cols = Math.floor(printableWidth / cardWidth);
cols: grid.cols, const rows = Math.floor(printableHeight / cardHeight);
rows: grid.rows,
cellWidth, return {
cellHeight cols,
}; rows,
cellWidth: cardWidth,
cellHeight: cardHeight
};
} }
// Text PDF Field Positions (relative to cell) // Helper function to get absolute position in points for pdf-lib
export const TEXT_FIELD_LAYOUT: TextFieldLayout = { export function getAbsolutePositionPt(
name: { cellX_mm: number,
x: 5, // 5pt from left edge of cell cellY_mm: number,
y: -15, // 15pt from top of cell (negative because PDF coords are bottom-up) pageHeight_mm: number,
size: 10 relativePos_mm: any
},
nationality: {
x: 5, // 5pt from left edge of cell
y: -29, // 29pt from top of cell (15 + 14 line height)
size: 10
},
birthday: {
x: 5, // 5pt from left edge of cell
y: -43, // 43pt from top of cell (15 + 14 + 14 line height)
size: 10
}
};
// Photo PDF Field Positions (relative to cell)
export const PHOTO_FIELD_LAYOUT: PhotoFieldLayout = {
photo: {
x: 10, // 10pt from left edge of cell
y: 40, // 40pt from bottom of cell
width: -20, // cell width minus 20pt (10pt margin on each side)
height: -60 // cell height minus 60pt (40pt bottom margin + 20pt top margin)
},
name: {
x: 10, // 10pt from left edge of cell
y: 20, // 20pt from bottom of cell
size: 10
}
};
// Helper function to get absolute position within a cell
export function getAbsolutePosition(
cellX: number,
cellY: number,
cellHeight: number,
relativePos: TextPosition
): { x: number; y: number; size: number } { ): { x: number; y: number; size: number } {
return { const absoluteX_mm = cellX_mm + relativePos_mm.x;
x: cellX + relativePos.x, // pdf-lib Y-coordinate is from bottom, so we invert
y: cellY + cellHeight + relativePos.y, // Convert relative Y to absolute const absoluteY_mm = pageHeight_mm - (cellY_mm + relativePos_mm.y);
size: relativePos.size
}; return {
x: absoluteX_mm * MM_TO_PT,
y: absoluteY_mm * MM_TO_PT,
size: relativePos_mm.size // size is already in points
};
} }
// Helper function to get absolute photo dimensions // Helper function to get absolute photo dimensions in points for pdf-lib
export function getAbsolutePhotoDimensions( export function getAbsolutePhotoDimensionsPt(
cellX: number, cellX_mm: number,
cellY: number, cellY_mm: number,
cellWidth: number, pageHeight_mm: number,
cellHeight: number, relativePhoto_mm: any
relativePhoto: PhotoPosition
): { x: number; y: number; width: number; height: number } { ): { x: number; y: number; width: number; height: number } {
return { const absoluteX_mm = cellX_mm + relativePhoto_mm.x;
x: cellX + relativePhoto.x, // pdf-lib Y-coordinate is from bottom, so we invert and account for height
y: cellY + relativePhoto.y, const absoluteY_mm = pageHeight_mm - (cellY_mm + relativePhoto_mm.y + relativePhoto_mm.height);
width: relativePhoto.width < 0 ? cellWidth + relativePhoto.width : relativePhoto.width,
height: relativePhoto.height < 0 ? cellHeight + relativePhoto.height : relativePhoto.height return {
}; x: absoluteX_mm * MM_TO_PT,
y: absoluteY_mm * MM_TO_PT,
width: relativePhoto_mm.width * MM_TO_PT,
height: relativePhoto_mm.height * MM_TO_PT
};
} }
// Border configuration // Border configuration
export const BORDER_CONFIG = { export const BORDER_CONFIG = {
color: { r: 0.8, g: 0.8, b: 0.8 }, color: { r: 0.8, g: 0.8, b: 0.8 },
width: 1 width: 1 // in points
}; };
// Text configuration // Text configuration
export const TEXT_CONFIG = { export const TEXT_CONFIG = {
color: { r: 0, g: 0, b: 0 }, color: { r: 0, g: 0, b: 0 },
lineHeight: 14 lineHeight: 14 // in points
}; };
// Placeholder text configuration // Placeholder text configuration
export const PLACEHOLDER_CONFIG = { export const PLACEHOLDER_CONFIG = {
text: 'Photo placeholder', text: 'Photo placeholder',
color: { r: 0.5, g: 0.5, b: 0.5 }, color: { r: 0.5, g: 0.5, b: 0.5 },
size: 8 size: 8 // in points
}; };

96
src/lib/pdfSettings.ts Normal file
View File

@@ -0,0 +1,96 @@
// User-configurable settings for PDF generation
export interface PageSettings {
pageWidth: number; // mm
pageHeight: number; // mm
margin: number; // mm
}
export interface CardDimensions {
width: number; // mm
height: number; // mm
}
// A4 Page dimensions in millimeters
export const PAGE_SETTINGS: PageSettings = {
pageWidth: 210,
pageHeight: 297,
margin: 10
};
// Dimensions for a single card in the text PDF.
// These dimensions will be used to calculate how many cards can fit on a page.
export const TEXT_CARD_DIMENSIONS: CardDimensions = {
width: 63,
height: 40
};
// Dimensions for a single card in the photo PDF.
export const PHOTO_CARD_DIMENSIONS: CardDimensions = {
width: 25,
height: 35
};
// Photo dimensions within the photo card
export const PHOTO_DIMENSIONS = {
width: 20, // mm
height: 35 // mm
};
export interface TextPosition {
x: number; // mm, relative to cell top-left
y: number; // mm, relative to cell top-left
size: number; // font size in points
}
export interface PhotoPosition {
x: number; // mm, relative to cell top-left
y: number; // mm, relative to cell top-left
width: number; // mm
height: number; // mm
}
export interface TextFieldLayout {
name: TextPosition;
nationality: TextPosition;
birthday: TextPosition;
}
export interface PhotoFieldLayout {
photo: PhotoPosition;
name: TextPosition;
}
// Text PDF Field Positions (in mm, relative to cell top-left)
export const TEXT_FIELD_LAYOUT: TextFieldLayout = {
name: {
x: 2,
y: 5,
size: 10 // font size in points
},
nationality: {
x: 2,
y: 10,
size: 10
},
birthday: {
x: 2,
y: 15,
size: 10
}
};
// Photo PDF Field Positions (in mm, relative to cell top-left)
export const PHOTO_FIELD_LAYOUT: PhotoFieldLayout = {
photo: {
x: 2, // 2mm from left of cell
y: 2, // 2mm from top of cell
width: PHOTO_DIMENSIONS.width,
height: PHOTO_DIMENSIONS.height
},
name: {
x: 2, // 2mm from left of cell
y: PHOTO_DIMENSIONS.height + 0, // Below the photo + 5mm gap
size: 5 // font size in points
}
};