- 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.
		
			
				
	
	
		
			135 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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;
 | |
|   }
 | |
| } |