KEXEL commited on
Commit
e1fa222
·
verified ·
1 Parent(s): edd791a
Files changed (1) hide show
  1. pdfeditor.html +746 -0
pdfeditor.html ADDED
@@ -0,0 +1,746 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Advanced PDF Editor</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
11
+ <style>
12
+ .pdf-container {
13
+ position: relative;
14
+ overflow: auto;
15
+ border: 1px solid #e5e7eb;
16
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
17
+ }
18
+ .canvas-container {
19
+ margin: 0 auto;
20
+ text-align: center;
21
+ position: relative;
22
+ }
23
+ .annotation {
24
+ position: absolute;
25
+ border: 2px dashed #3b82f6;
26
+ background-color: rgba(59, 130, 246, 0.2);
27
+ cursor: move;
28
+ }
29
+ .annotation.selected {
30
+ border: 2px solid #ef4444;
31
+ z-index: 100;
32
+ }
33
+ .annotation-text {
34
+ width: 100%;
35
+ height: 100%;
36
+ padding: 5px;
37
+ resize: none;
38
+ background: transparent;
39
+ border: none;
40
+ outline: none;
41
+ font-family: Arial, sans-serif;
42
+ }
43
+ .tooltip {
44
+ position: absolute;
45
+ background: #1f2937;
46
+ color: white;
47
+ padding: 4px 8px;
48
+ border-radius: 4px;
49
+ font-size: 12px;
50
+ opacity: 0;
51
+ transition: opacity 0.2s;
52
+ pointer-events: none;
53
+ z-index: 1000;
54
+ }
55
+ .tooltip.active {
56
+ opacity: 0.9;
57
+ }
58
+ @keyframes pulse {
59
+ 0%, 100% { opacity: 1; }
60
+ 50% { opacity: 0.5; }
61
+ }
62
+ .pulse {
63
+ animation: pulse 1.5s infinite;
64
+ }
65
+ </style>
66
+ </head>
67
+ <body class="bg-gray-50 min-h-screen">
68
+ <div class="container mx-auto px-4 py-8">
69
+ <header class="mb-8">
70
+ <h1 class="text-3xl font-bold text-gray-800 flex items-center">
71
+ <i class="fas fa-file-pdf text-red-500 mr-3"></i>
72
+ Advanced PDF Editor
73
+ </h1>
74
+ <p class="text-gray-600 mt-2">Upload, annotate, and modify your PDF documents with ease</p>
75
+ </header>
76
+
77
+ <div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
78
+ <!-- Tools Panel -->
79
+ <div class="bg-white rounded-lg shadow p-4 lg:col-span-1">
80
+ <h2 class="text-lg font-semibold text-gray-700 mb-4 flex items-center">
81
+ <i class="fas fa-tools mr-2"></i> Tools
82
+ </h2>
83
+
84
+ <div class="space-y-4">
85
+ <div>
86
+ <label class="block text-sm font-medium text-gray-700 mb-1">Upload PDF</label>
87
+ <div class="relative">
88
+ <input type="file" id="pdf-upload" accept=".pdf" class="hidden">
89
+ <label for="pdf-upload" class="w-full bg-blue-50 hover:bg-blue-100 text-blue-700 py-2 px-4 rounded border border-blue-200 flex items-center justify-center cursor-pointer transition">
90
+ <i class="fas fa-cloud-upload-alt mr-2"></i>
91
+ Choose File
92
+ </label>
93
+ <div id="file-name" class="text-xs text-gray-500 mt-1 truncate"></div>
94
+ </div>
95
+ </div>
96
+
97
+ <div class="pt-2 border-t border-gray-200">
98
+ <label class="block text-sm font-medium text-gray-700 mb-2">Annotation Tools</label>
99
+ <div class="grid grid-cols-3 gap-2">
100
+ <button id="text-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn active" data-tool="text">
101
+ <i class="fas fa-font mb-1"></i>
102
+ <span class="text-xs">Text</span>
103
+ </button>
104
+ <button id="highlight-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="highlight">
105
+ <i class="fas fa-highlighter mb-1"></i>
106
+ <span class="text-xs">Highlight</span>
107
+ </button>
108
+ <button id="rectangle-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="rectangle">
109
+ <i class="fas fa-square mb-1"></i>
110
+ <span class="text-xs">Rectangle</span>
111
+ </button>
112
+ <button id="freehand-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="freehand">
113
+ <i class="fas fa-pen mb-1"></i>
114
+ <span class="text-xs">Freehand</span>
115
+ </button>
116
+ <button id="stamp-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="stamp">
117
+ <i class="fas fa-stamp mb-1"></i>
118
+ <span class="text-xs">Stamp</span>
119
+ </button>
120
+ <button id="eraser-tool" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded flex flex-col items-center justify-center tool-btn" data-tool="eraser">
121
+ <i class="fas fa-eraser mb-1"></i>
122
+ <span class="text-xs">Eraser</span>
123
+ </button>
124
+ </div>
125
+ </div>
126
+
127
+ <div class="pt-2 border-t border-gray-200">
128
+ <label class="block text-sm font-medium text-gray-700 mb-2">Properties</label>
129
+ <div class="space-y-3">
130
+ <div>
131
+ <label class="block text-xs text-gray-600 mb-1">Color</label>
132
+ <input type="color" id="color-picker" value="#3b82f6" class="w-full h-8 cursor-pointer">
133
+ </div>
134
+ <div>
135
+ <label class="block text-xs text-gray-600 mb-1">Opacity</label>
136
+ <input type="range" id="opacity-slider" min="10" max="100" value="50" class="w-full">
137
+ </div>
138
+ <div>
139
+ <label class="block text-xs text-gray-600 mb-1">Font Size</label>
140
+ <select id="font-size" class="w-full border border-gray-200 rounded p-1 text-sm">
141
+ <option value="12">12px</option>
142
+ <option value="14">14px</option>
143
+ <option value="16" selected>16px</option>
144
+ <option value="18">18px</option>
145
+ <option value="20">20px</option>
146
+ <option value="24">24px</option>
147
+ </select>
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ <div class="pt-2 border-t border-gray-200">
153
+ <label class="block text-sm font-medium text-gray-700 mb-2">Pages</label>
154
+ <div id="page-thumbnails" class="space-y-2 max-h-40 overflow-y-auto">
155
+ <div class="text-center text-gray-500 py-4">
156
+ <i class="fas fa-file-upload text-2xl mb-2"></i>
157
+ <p class="text-sm">Upload a PDF to view pages</p>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Main Editor Area -->
165
+ <div class="bg-white rounded-lg shadow p-4 lg:col-span-3">
166
+ <div class="flex justify-between items-center mb-4">
167
+ <h2 class="text-lg font-semibold text-gray-700 flex items-center">
168
+ <i class="fas fa-edit mr-2"></i> Editor
169
+ </h2>
170
+ <div class="flex space-x-2">
171
+ <button id="zoom-in" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded">
172
+ <i class="fas fa-search-plus"></i>
173
+ </button>
174
+ <button id="zoom-out" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded">
175
+ <i class="fas fa-search-minus"></i>
176
+ </button>
177
+ <button id="download-pdf" class="bg-blue-50 hover:bg-blue-100 text-blue-700 py-2 px-4 rounded border border-blue-200 flex items-center">
178
+ <i class="fas fa-file-download mr-2"></i>
179
+ Download
180
+ </button>
181
+ </div>
182
+ </div>
183
+
184
+ <div id="pdf-container" class="pdf-container h-[70vh] bg-gray-100 rounded-lg flex items-center justify-center">
185
+ <div id="pdf-placeholder" class="text-center p-8">
186
+ <i class="fas fa-file-pdf text-5xl text-gray-300 mb-4"></i>
187
+ <h3 class="text-xl font-medium text-gray-500 mb-2">No PDF Loaded</h3>
188
+ <p class="text-gray-400 mb-4">Upload a PDF file to start editing</p>
189
+ <label for="pdf-upload" class="bg-blue-500 hover:bg-blue-600 text-white py-2 px-6 rounded cursor-pointer inline-block">
190
+ <i class="fas fa-cloud-upload-alt mr-2"></i>
191
+ Select PDF File
192
+ </label>
193
+ </div>
194
+ <div id="pdf-viewer" class="hidden w-full h-full relative overflow-auto">
195
+ <div id="canvas-container" class="canvas-container"></div>
196
+ </div>
197
+ </div>
198
+
199
+ <div class="mt-4 flex justify-between items-center">
200
+ <div class="flex items-center space-x-4">
201
+ <button id="prev-page" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded disabled:opacity-50" disabled>
202
+ <i class="fas fa-chevron-left"></i>
203
+ </button>
204
+ <span id="page-info" class="text-sm text-gray-600">Page: 0/0</span>
205
+ <button id="next-page" class="bg-gray-100 hover:bg-gray-200 text-gray-800 p-2 rounded disabled:opacity-50" disabled>
206
+ <i class="fas fa-chevron-right"></i>
207
+ </button>
208
+ </div>
209
+ <div class="text-sm text-gray-500">
210
+ <span id="zoom-level">100%</span>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+
216
+ <div class="mt-8 bg-white rounded-lg shadow p-4">
217
+ <h2 class="text-lg font-semibold text-gray-700 mb-4 flex items-center">
218
+ <i class="fas fa-history mr-2"></i> Recent Actions
219
+ </h2>
220
+ <div id="action-history" class="text-sm text-gray-600">
221
+ <div class="text-center text-gray-400 py-4">
222
+ <i class="fas fa-info-circle mr-1"></i>
223
+ No actions recorded yet
224
+ </div>
225
+ </div>
226
+ </div>
227
+ </div>
228
+
229
+ <div id="tooltip" class="tooltip"></div>
230
+
231
+ <script>
232
+ // Initialize PDF.js
233
+ pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.worker.min.js';
234
+
235
+ // Variables
236
+ let pdfDoc = null;
237
+ let pageNum = 1;
238
+ let pageRendering = false;
239
+ let pageNumPending = null;
240
+ let scale = 1.0;
241
+ let canvas = null;
242
+ let ctx = null;
243
+ let currentTool = 'text';
244
+ let annotations = [];
245
+ let selectedAnnotation = null;
246
+ let isDragging = false;
247
+ let startX, startY;
248
+ let currentPage = 1;
249
+ let pdfName = '';
250
+ let actionHistory = [];
251
+
252
+ // DOM Elements
253
+ const pdfUpload = document.getElementById('pdf-upload');
254
+ const pdfViewer = document.getElementById('pdf-viewer');
255
+ const pdfPlaceholder = document.getElementById('pdf-placeholder');
256
+ const canvasContainer = document.getElementById('canvas-container');
257
+ const pageInfo = document.getElementById('page-info');
258
+ const prevPage = document.getElementById('prev-page');
259
+ const nextPage = document.getElementById('next-page');
260
+ const zoomIn = document.getElementById('zoom-in');
261
+ const zoomOut = document.getElementById('zoom-out');
262
+ const zoomLevel = document.getElementById('zoom-level');
263
+ const downloadPdf = document.getElementById('download-pdf');
264
+ const toolButtons = document.querySelectorAll('.tool-btn');
265
+ const colorPicker = document.getElementById('color-picker');
266
+ const opacitySlider = document.getElementById('opacity-slider');
267
+ const fontSizeSelect = document.getElementById('font-size');
268
+ const pageThumbnails = document.getElementById('page-thumbnails');
269
+ const actionHistoryContainer = document.getElementById('action-history');
270
+ const fileNameDisplay = document.getElementById('file-name');
271
+ const tooltip = document.getElementById('tooltip');
272
+
273
+ // Event Listeners
274
+ pdfUpload.addEventListener('change', handleFileSelect);
275
+ prevPage.addEventListener('click', onPrevPage);
276
+ nextPage.addEventListener('click', onNextPage);
277
+ zoomIn.addEventListener('click', () => changeZoom(0.2));
278
+ zoomOut.addEventListener('click', () => changeZoom(-0.2));
279
+ downloadPdf.addEventListener('click', downloadModifiedPdf);
280
+
281
+ // Tool buttons
282
+ toolButtons.forEach(button => {
283
+ button.addEventListener('click', () => {
284
+ toolButtons.forEach(btn => btn.classList.remove('active', 'bg-blue-100', 'border-blue-300'));
285
+ button.classList.add('active', 'bg-blue-100', 'border-blue-300');
286
+ currentTool = button.dataset.tool;
287
+ addActionToHistory(`Switched to ${button.dataset.tool} tool`);
288
+ });
289
+ });
290
+
291
+ // Initialize first tool as active
292
+ document.getElementById('text-tool').classList.add('active', 'bg-blue-100', 'border-blue-300');
293
+
294
+ // Functions
295
+ function handleFileSelect(event) {
296
+ const file = event.target.files[0];
297
+ if (file && file.type === 'application/pdf') {
298
+ pdfName = file.name;
299
+ fileNameDisplay.textContent = file.name;
300
+
301
+ const fileReader = new FileReader();
302
+ fileReader.onload = function() {
303
+ const typedArray = new Uint8Array(this.result);
304
+ loadPdf(typedArray);
305
+ };
306
+ fileReader.readAsArrayBuffer(file);
307
+
308
+ addActionToHistory(`Uploaded PDF: ${file.name}`);
309
+ } else {
310
+ alert('Please select a valid PDF file.');
311
+ }
312
+ }
313
+
314
+ function loadPdf(data) {
315
+ pdfjsLib.getDocument(data).promise.then(function(pdfDoc_) {
316
+ pdfDoc = pdfDoc_;
317
+ pageInfo.textContent = `Page: 1/${pdfDoc.numPages}`;
318
+
319
+ // Enable/disable buttons
320
+ prevPage.disabled = true;
321
+ nextPage.disabled = pdfDoc.numPages <= 1;
322
+
323
+ // Show PDF viewer and hide placeholder
324
+ pdfViewer.classList.remove('hidden');
325
+ pdfPlaceholder.classList.add('hidden');
326
+
327
+ // Render first page
328
+ renderPage(1);
329
+
330
+ // Generate thumbnails
331
+ generateThumbnails();
332
+ }).catch(function(error) {
333
+ console.error('Error loading PDF:', error);
334
+ alert('Error loading PDF. Please try another file.');
335
+ });
336
+ }
337
+
338
+ function renderPage(num) {
339
+ pageRendering = true;
340
+ currentPage = num;
341
+
342
+ // Update page display
343
+ pageInfo.textContent = `Page: ${num}/${pdfDoc.numPages}`;
344
+
345
+ // Update button states
346
+ prevPage.disabled = num <= 1;
347
+ nextPage.disabled = num >= pdfDoc.numPages;
348
+
349
+ // Clear previous canvas
350
+ canvasContainer.innerHTML = '';
351
+
352
+ // Create new canvas
353
+ canvas = document.createElement('canvas');
354
+ canvas.className = 'pdf-page';
355
+ canvasContainer.appendChild(canvas);
356
+ ctx = canvas.getContext('2d');
357
+
358
+ // Get page
359
+ pdfDoc.getPage(num).then(function(page) {
360
+ const viewport = page.getViewport({ scale: scale });
361
+ canvas.height = viewport.height;
362
+ canvas.width = viewport.width;
363
+
364
+ // Render PDF page into canvas context
365
+ const renderContext = {
366
+ canvasContext: ctx,
367
+ viewport: viewport
368
+ };
369
+
370
+ const renderTask = page.render(renderContext);
371
+
372
+ renderTask.promise.then(function() {
373
+ pageRendering = false;
374
+ if (pageNumPending !== null) {
375
+ renderPage(pageNumPending);
376
+ pageNumPending = null;
377
+ }
378
+
379
+ // Render annotations for this page
380
+ renderAnnotations();
381
+ });
382
+ });
383
+
384
+ // Highlight current page in thumbnails
385
+ highlightCurrentThumbnail(num);
386
+ }
387
+
388
+ function onPrevPage() {
389
+ if (pageNum <= 1) {
390
+ return;
391
+ }
392
+ pageNum--;
393
+ queueRenderPage(pageNum);
394
+ addActionToHistory(`Navigated to page ${pageNum}`);
395
+ }
396
+
397
+ function onNextPage() {
398
+ if (pageNum >= pdfDoc.numPages) {
399
+ return;
400
+ }
401
+ pageNum++;
402
+ queueRenderPage(pageNum);
403
+ addActionToHistory(`Navigated to page ${pageNum}`);
404
+ }
405
+
406
+ function queueRenderPage(num) {
407
+ if (pageRendering) {
408
+ pageNumPending = num;
409
+ } else {
410
+ renderPage(num);
411
+ }
412
+ }
413
+
414
+ function changeZoom(delta) {
415
+ const newScale = scale + delta;
416
+ if (newScale >= 0.2 && newScale <= 3.0) {
417
+ scale = newScale;
418
+ zoomLevel.textContent = `${Math.round(scale * 100)}%`;
419
+ queueRenderPage(pageNum);
420
+ addActionToHistory(`Zoom changed to ${Math.round(scale * 100)}%`);
421
+ }
422
+ }
423
+
424
+ function generateThumbnails() {
425
+ pageThumbnails.innerHTML = '';
426
+
427
+ for (let i = 1; i <= pdfDoc.numPages; i++) {
428
+ const thumbContainer = document.createElement('div');
429
+ thumbContainer.className = 'relative cursor-pointer group';
430
+ thumbContainer.dataset.page = i;
431
+
432
+ const thumbCanvas = document.createElement('canvas');
433
+ thumbCanvas.className = 'border border-gray-200 w-full';
434
+
435
+ const pageNum = document.createElement('div');
436
+ pageNum.className = 'absolute bottom-1 right-1 bg-black bg-opacity-50 text-white text-xs px-1 rounded';
437
+ pageNum.textContent = i;
438
+
439
+ thumbContainer.appendChild(thumbCanvas);
440
+ thumbContainer.appendChild(pageNum);
441
+
442
+ // Render thumbnail
443
+ pdfDoc.getPage(i).then(function(page) {
444
+ const viewport = page.getViewport(0.2);
445
+ thumbCanvas.height = viewport.height;
446
+ thumbCanvas.width = viewport.width;
447
+
448
+ page.render({
449
+ canvasContext: thumbCanvas.getContext('2d'),
450
+ viewport: viewport
451
+ });
452
+ });
453
+
454
+ // Click event to navigate to page
455
+ thumbContainer.addEventListener('click', function() {
456
+ pageNum = parseInt(this.dataset.page);
457
+ queueRenderPage(pageNum);
458
+ addActionToHistory(`Navigated to page ${pageNum} via thumbnail`);
459
+ });
460
+
461
+ // Hover effect
462
+ thumbContainer.addEventListener('mouseenter', function() {
463
+ this.classList.add('ring-2', 'ring-blue-400');
464
+ });
465
+
466
+ thumbContainer.addEventListener('mouseleave', function() {
467
+ if (parseInt(this.dataset.page) !== currentPage) {
468
+ this.classList.remove('ring-2', 'ring-blue-400');
469
+ }
470
+ });
471
+
472
+ pageThumbnails.appendChild(thumbContainer);
473
+ }
474
+ }
475
+
476
+ function highlightCurrentThumbnail(pageNum) {
477
+ const thumbs = pageThumbnails.querySelectorAll('[data-page]');
478
+ thumbs.forEach(thumb => {
479
+ if (parseInt(thumb.dataset.page) === pageNum) {
480
+ thumb.classList.add('ring-2', 'ring-blue-500');
481
+ } else {
482
+ thumb.classList.remove('ring-2', 'ring-blue-500');
483
+ }
484
+ });
485
+ }
486
+
487
+ // Annotation functions
488
+ function renderAnnotations() {
489
+ // Clear existing annotations
490
+ const existingAnnotations = canvasContainer.querySelectorAll('.annotation');
491
+ existingAnnotations.forEach(ann => ann.remove());
492
+
493
+ // Filter annotations for current page
494
+ const pageAnnotations = annotations.filter(ann => ann.page === currentPage);
495
+
496
+ // Render each annotation
497
+ pageAnnotations.forEach(annotation => {
498
+ const annElement = document.createElement('div');
499
+ annElement.className = 'annotation';
500
+ annElement.dataset.id = annotation.id;
501
+
502
+ // Position and size
503
+ annElement.style.left = `${annotation.x}px`;
504
+ annElement.style.top = `${annotation.y}px`;
505
+ annElement.style.width = `${annotation.width}px`;
506
+ annElement.style.height = `${annotation.height}px`;
507
+
508
+ // Style based on type
509
+ if (annotation.type === 'text') {
510
+ const textarea = document.createElement('textarea');
511
+ textarea.className = 'annotation-text';
512
+ textarea.value = annotation.content || '';
513
+ textarea.style.color = annotation.color;
514
+ textarea.style.fontSize = `${annotation.fontSize}px`;
515
+ textarea.style.opacity = annotation.opacity / 100;
516
+
517
+ // Update annotation content when text changes
518
+ textarea.addEventListener('input', function() {
519
+ const ann = annotations.find(a => a.id === annotation.id);
520
+ if (ann) ann.content = this.value;
521
+ });
522
+
523
+ annElement.appendChild(textarea);
524
+ } else if (annotation.type === 'highlight') {
525
+ annElement.style.backgroundColor = annotation.color;
526
+ annElement.style.opacity = annotation.opacity / 100;
527
+ } else if (annotation.type === 'rectangle') {
528
+ annElement.style.border = `2px solid ${annotation.color}`;
529
+ annElement.style.backgroundColor = 'transparent';
530
+ }
531
+
532
+ // Selection
533
+ if (selectedAnnotation && selectedAnnotation.id === annotation.id) {
534
+ annElement.classList.add('selected');
535
+ }
536
+
537
+ // Event listeners for interaction
538
+ annElement.addEventListener('mousedown', startAnnotationDrag);
539
+ annElement.addEventListener('mouseup', stopAnnotationDrag);
540
+ annElement.addEventListener('dblclick', deleteAnnotation);
541
+
542
+ canvasContainer.appendChild(annElement);
543
+ });
544
+ }
545
+
546
+ function createAnnotation(e) {
547
+ if (!canvas || currentTool === 'eraser') return;
548
+
549
+ const rect = canvas.getBoundingClientRect();
550
+ const x = e.clientX - rect.left;
551
+ const y = e.clientY - rect.top;
552
+
553
+ const newAnnotation = {
554
+ id: Date.now().toString(),
555
+ type: currentTool,
556
+ page: currentPage,
557
+ x: x,
558
+ y: y,
559
+ width: currentTool === 'text' ? 200 : 100,
560
+ height: currentTool === 'text' ? 50 : 30,
561
+ color: colorPicker.value,
562
+ opacity: opacitySlider.value,
563
+ fontSize: fontSizeSelect.value,
564
+ content: ''
565
+ };
566
+
567
+ annotations.push(newAnnotation);
568
+ selectedAnnotation = newAnnotation;
569
+
570
+ renderAnnotations();
571
+
572
+ // Focus textarea if it's a text annotation
573
+ if (currentTool === 'text') {
574
+ setTimeout(() => {
575
+ const textarea = canvasContainer.querySelector('.annotation.selected textarea');
576
+ if (textarea) textarea.focus();
577
+ }, 10);
578
+ }
579
+
580
+ addActionToHistory(`Added ${currentTool} annotation`);
581
+ }
582
+
583
+ function startAnnotationDrag(e) {
584
+ if (e.target.tagName === 'TEXTAREA') return;
585
+
586
+ e.preventDefault();
587
+ e.stopPropagation();
588
+
589
+ const annotationId = e.currentTarget.dataset.id;
590
+ selectedAnnotation = annotations.find(ann => ann.id === annotationId);
591
+
592
+ // Update all annotations to show selected state
593
+ renderAnnotations();
594
+
595
+ // Start drag
596
+ isDragging = true;
597
+ startX = e.clientX;
598
+ startY = e.clientY;
599
+
600
+ document.addEventListener('mousemove', dragAnnotation);
601
+ document.addEventListener('mouseup', stopAnnotationDrag);
602
+ }
603
+
604
+ function dragAnnotation(e) {
605
+ if (!isDragging || !selectedAnnotation) return;
606
+
607
+ const rect = canvas.getBoundingClientRect();
608
+ const dx = e.clientX - startX;
609
+ const dy = e.clientY - startY;
610
+
611
+ selectedAnnotation.x += dx;
612
+ selectedAnnotation.y += dy;
613
+
614
+ startX = e.clientX;
615
+ startY = e.clientY;
616
+
617
+ renderAnnotations();
618
+ }
619
+
620
+ function stopAnnotationDrag() {
621
+ isDragging = false;
622
+ document.removeEventListener('mousemove', dragAnnotation);
623
+ document.removeEventListener('mouseup', stopAnnotationDrag);
624
+
625
+ if (selectedAnnotation) {
626
+ addActionToHistory(`Moved ${selectedAnnotation.type} annotation`);
627
+ }
628
+ }
629
+
630
+ function deleteAnnotation(e) {
631
+ e.stopPropagation();
632
+
633
+ const annotationId = e.currentTarget.dataset.id;
634
+ const annotationIndex = annotations.findIndex(ann => ann.id === annotationId);
635
+
636
+ if (annotationIndex !== -1) {
637
+ const deletedAnnotation = annotations[annotationIndex];
638
+ annotations.splice(annotationIndex, 1);
639
+ selectedAnnotation = null;
640
+
641
+ renderAnnotations();
642
+ addActionToHistory(`Deleted ${deletedAnnotation.type} annotation`);
643
+ }
644
+ }
645
+
646
+ // Canvas click handler for creating new annotations
647
+ canvasContainer.addEventListener('mousedown', function(e) {
648
+ if (e.target === canvasContainer || e.target === canvas) {
649
+ createAnnotation(e);
650
+ }
651
+ });
652
+
653
+ // Tooltip handling
654
+ document.querySelectorAll('[data-tooltip]').forEach(el => {
655
+ el.addEventListener('mouseenter', function() {
656
+ const tooltipText = this.dataset.tooltip;
657
+ tooltip.textContent = tooltipText;
658
+ tooltip.classList.add('active');
659
+
660
+ const rect = this.getBoundingClientRect();
661
+ tooltip.style.left = `${rect.left + rect.width / 2 - tooltip.offsetWidth / 2}px`;
662
+ tooltip.style.top = `${rect.top - tooltip.offsetHeight - 5}px`;
663
+ });
664
+
665
+ el.addEventListener('mouseleave', function() {
666
+ tooltip.classList.remove('active');
667
+ });
668
+ });
669
+
670
+ // Download modified PDF
671
+ function downloadModifiedPdf() {
672
+ if (!pdfDoc) {
673
+ alert('No PDF loaded');
674
+ return;
675
+ }
676
+
677
+ addActionToHistory('Initiated PDF download');
678
+
679
+ // In a real implementation, you would use jsPDF or similar to create a new PDF
680
+ // with the annotations. This is a simplified version that just alerts.
681
+
682
+ alert('In a full implementation, this would download the modified PDF with all annotations.');
683
+
684
+ // For demonstration, we'll create a simple download of the original file
685
+ if (pdfUpload.files.length > 0) {
686
+ const file = pdfUpload.files[0];
687
+ const url = URL.createObjectURL(file);
688
+
689
+ const a = document.createElement('a');
690
+ a.href = url;
691
+ a.download = `modified_${file.name}`;
692
+ document.body.appendChild(a);
693
+ a.click();
694
+
695
+ setTimeout(() => {
696
+ document.body.removeChild(a);
697
+ URL.revokeObjectURL(url);
698
+ }, 0);
699
+ }
700
+ }
701
+
702
+ // Action history
703
+ function addActionToHistory(action) {
704
+ const timestamp = new Date().toLocaleTimeString();
705
+ actionHistory.unshift({ action, timestamp });
706
+
707
+ if (actionHistory.length > 10) {
708
+ actionHistory.pop();
709
+ }
710
+
711
+ updateActionHistoryDisplay();
712
+ }
713
+
714
+ function updateActionHistoryDisplay() {
715
+ if (actionHistory.length === 0) {
716
+ actionHistoryContainer.innerHTML = `
717
+ <div class="text-center text-gray-400 py-4">
718
+ <i class="fas fa-info-circle mr-1"></i>
719
+ No actions recorded yet
720
+ </div>
721
+ `;
722
+ return;
723
+ }
724
+
725
+ actionHistoryContainer.innerHTML = '';
726
+
727
+ actionHistory.forEach(item => {
728
+ const actionElement = document.createElement('div');
729
+ actionElement.className = 'flex justify-between items-center py-2 border-b border-gray-100';
730
+
731
+ const actionText = document.createElement('span');
732
+ actionText.textContent = item.action;
733
+ actionText.className = 'text-gray-700';
734
+
735
+ const actionTime = document.createElement('span');
736
+ actionTime.textContent = item.timestamp;
737
+ actionTime.className = 'text-gray-500 text-xs';
738
+
739
+ actionElement.appendChild(actionText);
740
+ actionElement.appendChild(actionTime);
741
+ actionHistoryContainer.appendChild(actionElement);
742
+ });
743
+ }
744
+ </script>
745
+ </body>
746
+ </html>