feat: Add PDF viewer and preview components with styling and functionality
- Implemented DeesPdfViewer for full-featured PDF viewing with toolbar and sidebar navigation. - Created DeesPdfPreview for lightweight PDF previews. - Introduced PdfManager for managing PDF document loading and caching. - Added CanvasPool for efficient canvas management. - Developed utility functions for performance monitoring and file size formatting. - Established styles for viewer and preview components to enhance UI/UX. - Included demo examples for showcasing PDF viewer capabilities.
This commit is contained in:
135
ts_web/elements/dees-pdf-shared/CanvasPool.ts
Normal file
135
ts_web/elements/dees-pdf-shared/CanvasPool.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
export interface PooledCanvas {
|
||||
canvas: HTMLCanvasElement;
|
||||
ctx: CanvasRenderingContext2D;
|
||||
inUse: boolean;
|
||||
lastUsed: number;
|
||||
}
|
||||
|
||||
export class CanvasPool {
|
||||
private static pool: PooledCanvas[] = [];
|
||||
private static maxPoolSize = 20;
|
||||
private static readonly MIN_CANVAS_SIZE = 256;
|
||||
private static readonly MAX_CANVAS_SIZE = 4096;
|
||||
|
||||
public static acquire(width: number, height: number): PooledCanvas {
|
||||
// Try to find a suitable canvas from the pool
|
||||
const suitable = this.pool.find(
|
||||
(item) => !item.inUse &&
|
||||
item.canvas.width >= width &&
|
||||
item.canvas.height >= height &&
|
||||
item.canvas.width <= width * 1.5 &&
|
||||
item.canvas.height <= height * 1.5
|
||||
);
|
||||
|
||||
if (suitable) {
|
||||
suitable.inUse = true;
|
||||
suitable.lastUsed = Date.now();
|
||||
|
||||
// Clear and resize if needed
|
||||
suitable.canvas.width = width;
|
||||
suitable.canvas.height = height;
|
||||
suitable.ctx.clearRect(0, 0, width, height);
|
||||
|
||||
return suitable;
|
||||
}
|
||||
|
||||
// Create new canvas if pool not full
|
||||
if (this.pool.length < this.maxPoolSize) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d', {
|
||||
alpha: true,
|
||||
desynchronized: true,
|
||||
}) as CanvasRenderingContext2D;
|
||||
|
||||
canvas.width = Math.min(Math.max(width, this.MIN_CANVAS_SIZE), this.MAX_CANVAS_SIZE);
|
||||
canvas.height = Math.min(Math.max(height, this.MIN_CANVAS_SIZE), this.MAX_CANVAS_SIZE);
|
||||
|
||||
const pooledCanvas: PooledCanvas = {
|
||||
canvas,
|
||||
ctx,
|
||||
inUse: true,
|
||||
lastUsed: Date.now(),
|
||||
};
|
||||
|
||||
this.pool.push(pooledCanvas);
|
||||
return pooledCanvas;
|
||||
}
|
||||
|
||||
// Evict and reuse least recently used canvas
|
||||
const lru = this.pool
|
||||
.filter((item) => !item.inUse)
|
||||
.sort((a, b) => a.lastUsed - b.lastUsed)[0];
|
||||
|
||||
if (lru) {
|
||||
lru.canvas.width = width;
|
||||
lru.canvas.height = height;
|
||||
lru.ctx.clearRect(0, 0, width, height);
|
||||
lru.inUse = true;
|
||||
lru.lastUsed = Date.now();
|
||||
return lru;
|
||||
}
|
||||
|
||||
// Fallback: create temporary canvas (shouldn't normally happen)
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
return {
|
||||
canvas,
|
||||
ctx,
|
||||
inUse: true,
|
||||
lastUsed: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
public static release(pooledCanvas: PooledCanvas) {
|
||||
if (this.pool.includes(pooledCanvas)) {
|
||||
pooledCanvas.inUse = false;
|
||||
// Clear canvas to free memory
|
||||
pooledCanvas.ctx.clearRect(0, 0, pooledCanvas.canvas.width, pooledCanvas.canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
public static releaseAll() {
|
||||
for (const item of this.pool) {
|
||||
item.inUse = false;
|
||||
item.ctx.clearRect(0, 0, item.canvas.width, item.canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
public static destroy() {
|
||||
for (const item of this.pool) {
|
||||
item.canvas.width = 0;
|
||||
item.canvas.height = 0;
|
||||
}
|
||||
this.pool = [];
|
||||
}
|
||||
|
||||
public static getStats() {
|
||||
return {
|
||||
poolSize: this.pool.length,
|
||||
maxPoolSize: this.maxPoolSize,
|
||||
inUse: this.pool.filter((item) => item.inUse).length,
|
||||
available: this.pool.filter((item) => !item.inUse).length,
|
||||
};
|
||||
}
|
||||
|
||||
public static adjustPoolSize(newSize: number) {
|
||||
if (newSize < this.pool.length) {
|
||||
// Remove excess canvases
|
||||
const toRemove = this.pool.length - newSize;
|
||||
const removed = this.pool
|
||||
.filter((item) => !item.inUse)
|
||||
.slice(0, toRemove);
|
||||
|
||||
for (const item of removed) {
|
||||
const index = this.pool.indexOf(item);
|
||||
if (index > -1) {
|
||||
this.pool.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.maxPoolSize = newSize;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user