НейроАгент

Как добавить интерактивные компоненты PDF в Angular

Узнайте, как добавлять интерактивные кнопки и поля ввода поверх PDF в Angular. Полное руководство с примерами кода для панелей подписей и полей ввода дат с использованием лучших библиотек просмотра PDF.

Как добавить интерактивные компоненты, такие как кнопки и поля ввода, поверх PDF в Angular?

Я пытаюсь создать пользовательский интерфейс, где нажатие на кнопку “Подписать” открывает модальное окно с отображением PDF. Мне нужно добавить:

  1. Кнопку в поле подписи PDF, которая при нажатии отображает панель для подписи пользователей
  2. Поле ввода в поле даты для ввода даты

Как можно реализовать эти интерактивные функции PDF в Angular? Есть ли какие-либо рекомендуемые open-source библиотеки или альтернативные подходы для достижения этой функциональности?

НейроАгент

Добавление интерактивных компонентов поверх PDF в Angular

Чтобы добавить интерактивные компоненты, такие как кнопки и поля ввода, поверх PDF в Angular, можно использовать специализированные библиотеки просмотра PDF, которые поддерживают наложение форм и интерактивные элементы. Наиболее эффективный подход включает использование библиотек, таких как ngx-extended-pdf-viewer или Syncfusion PDF Viewer, которые обеспечивают встроенную поддержку интерактивных форм, или реализацию пользовательского решения наложения с помощью PDF.js в сочетании с компонентами Angular, позиционированными абсолютно поверх содержимого PDF.

Содержание

Обзор подходов

При реализации интерактивных компонентов поверх PDF в Angular следует рассмотреть несколько подходов:

Решения на основе библиотек

Большинство разработчиков выбирают специализированные библиотеки просмотра PDF, которые предоставляют встроенную поддержку интерактивных форм. Эти библиотеки обрабатывают сложную логику рендеринга и взаимодействия, позволяя при этом настраивать пользовательский интерфейс.

Пользовательские решения наложения

Для более специфических требований можно реализовать пользовательскую систему наложения, в которой компоненты Angular (кнопки, поля ввода) позиционируются абсолютно поверх содержимого PDF с использованием координат и обработки событий.

Гибридные подходы

Многие проекты объединяют просмотр PDF на основе библиотек с пользовательскими интерактивными компонентами для конкретных функций, таких как цифровые планшеты для подписи или выборщики даты, которые не полностью поддерживаются стандартными элементами форм PDF.

Выбор зависит от требований вашего проекта, бюджета и сложности необходимых интерактивных функций.

ngx-extended-pdf-viewer

Эта библиотека предоставляет комплексную поддержку интерактивных форм с двусторонним связыванием данных, что делает ее идеальной для вашего случая использования. Она поддерживает:

  • Стандартные и XFA формы
  • Текстовые поля, флажки, переключатели и выпадающие списки
  • Расширенные возможности аннотирования
  • Настраиваемую панель инструментов и пользовательский интерфейс

Согласно документации npm-пакета, она предлагает “Комплексную поддержку форм с двусторонним связыванием данных” и “Функции аннотирования и редактирования PDF”.

Syncfusion PDF Viewer

Syncfusion предоставляет полный интерактивный просмотрщик PDF с обширной поддержкой форм:

  • Полностью интерактивные формы с текстовыми полями, флажками, переключателями и выпадающими списками
  • Редактирование, предварительный просмотр и сохранение форм в реальном времени
  • Поддержка рукописных подписей
  • Настраиваемая панель инструментов и пользовательский интерфейс

Как указано в их описании продукта, она позволяет вам “Полностью интерактивные формы с текстовыми полями, флажками, переключателями и выпадающими списками, или проектирование новых форм внутри просмотрщика.”

PDF.js Express

Это коммерческое решение обертывает открытый движок рендеринга PDF.js и предоставляет:

  • Возможности аннотирования
  • Электронные подписи
  • Заполнение форм
  • Расширенные параметры настройки

В документации PDF.js Express упоминается, что она “предоставляет разработчикам быстрый способ добавления аннотаций, электронных подписей и заполнения форм в их просмотрщик PDF Angular”.

ng2-pdf-viewer

Более простой альтернатива для базового просмотра PDF с некоторыми интерактивными функциями:

  • Извлечение и выбор текста PDF
  • Базовая поддержка полей формы
  • Обработка событий для пользовательских взаимодействий

Репозиторий GitHub предоставляет примеры реализации просмотра PDF с компонентами Angular.

Реализация функциональности подписи

Использование встроенной поддержки подписи

Многие просмотрщики PDF предоставляют встроенную функциональность подписи. Например, просмотрщик PDF от Syncfusion поддерживает рукописные подписи через свою панель подписи:

typescript
import { Component } from '@angular/core';
import { PdfViewerModule } from '@syncfusion/ej2-angular-pdfviewer';
import { ToolbarItem } from '@syncfusion/ej2-pdfviewer';

@Component({
  selector: 'app-signature-example',
  template: `
    <button (click)="activateSignatureTool()">Подписать документ</button>
    <ejs-pdfviewer 
      #pdfviewer
      [serviceUrl]='serviceUrl'
      [documentPath]='documentPath'
      [toolbarItems]='toolbarItems'>
    </ejs-pdfviewer>
  `
})
export class SignatureExampleComponent {
  serviceUrl = 'https://services.syncfusion.com/production/js/production/pdfviewer';
  documentPath = 'https://cdn.syncfusion.com/content/pdf/pdf-succinctly.pdf';
  
  toolbarItems: ToolbarItem[] = ['SignatureTool'];
  
  activateSignatureTool() {
    const viewer = document.getElementById('pdfviewer').ej2_instances[0];
    viewer.annotationModule.setAnnotationMode('Signature');
  }
}

Реализация пользовательского планшета для подписи

Для большего контроля можно реализовать пользовательский планшет для подписи с использованием canvas и наложить его поверх PDF:

typescript
import { Component, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-custom-signature',
  template: `
    <div class="pdf-container" #pdfContainer>
      <pdf-viewer [src]="pdfSrc" [render-text]="true"></pdf-viewer>
      <div class="overlay-controls">
        <button (click)="showSignaturePad()">Подписать здесь</button>
        <div *ngIf="showPad" class="signature-pad">
          <canvas #signatureCanvas width="300" height="150"></canvas>
          <button (click)="clearSignature()">Очистить</button>
          <button (click)="saveSignature()">Сохранить</button>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .pdf-container { position: relative; }
    .overlay-controls { position: absolute; top: 100px; left: 50px; }
    .signature-pad { border: 1px solid #ccc; margin-top: 10px; }
  `]
})
export class CustomSignatureComponent {
  @ViewChild('pdfContainer') pdfContainer: ElementRef;
  @ViewChild('signatureCanvas') signatureCanvas: ElementRef;
  
  pdfSrc = 'path/to/your/document.pdf';
  showPad = false;
  isDrawing = false;
  
  showSignaturePad() {
    this.showPad = true;
    this.setupCanvas();
  }
  
  setupCanvas() {
    const canvas = this.signatureCanvas.nativeElement;
    const ctx = canvas.getContext('2d');
    
    canvas.addEventListener('mousedown', (e) => {
      this.isDrawing = true;
      ctx.beginPath();
      ctx.moveTo(e.offsetX, e.offsetY);
    });
    
    canvas.addEventListener('mousemove', (e) => {
      if (!this.isDrawing) return;
      ctx.lineTo(e.offsetX, e.offsetY);
      ctx.stroke();
    });
    
    canvas.addEventListener('mouseup', () => {
      this.isDrawing = false;
    });
  }
  
  clearSignature() {
    const canvas = this.signatureCanvas.nativeElement;
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }
  
  saveSignature() {
    const canvas = this.signatureCanvas.nativeElement;
    const signatureData = canvas.toDataURL();
    // Здесь обычно отправляется данные в сервис обработки PDF
    console.log('Подпись сохранена:', signatureData);
    this.showPad = false;
  }
}

Реализация полей ввода даты

Использование полей формы PDF

Наиболее прямой подход - использование полей формы PDF, совместимых с просмотрщиками PDF Angular:

typescript
import { Component } from '@angular/core';

@Component({
  selector: 'app-date-form-example',
  template: `
    <pdf-viewer 
      [src]="pdfSrc"
      [render-text]="true"
      (pageRendered)="onPageRendered($event)">
    </pdf-viewer>
    <div *ngIf="showDateInput" class="date-overlay">
      <input 
        type="date" 
        [(ngModel)]="selectedDate"
        (blur)="onDateBlur()">
    </div>
  `
})
export class DateFormExampleComponent {
  pdfSrc = 'path/to/your/form.pdf';
  showDateInput = false;
  selectedDate: string;
  dateFieldPosition: {x: number, y: number};
  
  onPageRendered(event) {
    // Логика обнаружения, когда поля даты отрисованы
    // и соответствующего позиционирования наложенного поля ввода
  }
  
  onDateBlur() {
    // Сохранение даты в поле формы PDF
    this.showDateInput = false;
  }
}

Пользовательское наложение поля ввода даты

Для большего контроля над опытом использования поля ввода даты:

typescript
import { Component, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-custom-date-input',
  template: `
    <div class="pdf-viewer-container">
      <pdf-viewer [src]="pdfSrc" [render-text]="true"></pdf-viewer>
      <div *ngIf="showDatePicker" class="date-picker-overlay" 
           [style.left]="datePosition.x + 'px'" 
           [style.top]="datePosition.y + 'px'">
        <input type="date" 
               [(ngModel)]="selectedDate"
               (change)="onDateSelect($event)"
               (blur)="hideDatePicker()">
      </div>
    </div>
  `,
  styles: [`
    .pdf-viewer-container { position: relative; }
    .date-picker-overlay { 
      position: absolute; 
      z-index: 1000; 
      background: white;
      border: 1px solid #ccc;
      padding: 5px;
      border-radius: 4px;
    }
  `]
})
export class CustomDateInputComponent {
  @ViewChild('pdfViewer') pdfViewer: ElementRef;
  
  pdfSrc = 'path/to/your/document.pdf';
  showDatePicker = false;
  selectedDate: string;
  datePosition = { x: 0, y: 0 };
  
  showDatePickerAtPosition(x: number, y: number) {
    this.datePosition = { x, y };
    this.showDatePicker = true;
    this.selectedDate = new Date().toISOString().split('T')[0]; // Сегодняшняя дата
  }
  
  onDateSelect(event: any) {
    // Обработка выбора даты и обновление PDF
    console.log('Выбрана дата:', this.selectedDate);
    // Здесь обычно обновляется поле формы PDF
  }
  
  hideDatePicker() {
    setTimeout(() => this.showDatePicker = false, 200); // Время для завершения blur
  }
}

Реализация пользовательского наложения

Для максимальной гибкости можно реализовать пользовательскую систему наложения:

typescript
import { Component, ViewChild, ElementRef } from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

@Component({
  selector: 'app-custom-overlay',
  template: `
    <div class="pdf-container" #pdfContainer>
      <pdf-viewer [src]="pdfSrc" [render-text]="true"></pdf-viewer>
      <div class="interactive-overlay">
        <!-- Наложение кнопки подписи -->
        <div class="signature-field" 
             [style.left]="signatureField.x + 'px'" 
             [style.top]="signatureField.y + 'px'"
             [style.width]="signatureField.width + 'px'" 
             [style.height]="signatureField.height + 'px'">
          <button (click)="openSignaturePad()">Подписать</button>
        </div>
        
        <!-- Наложение поля ввода даты -->
        <div class="date-field" 
             [style.left]="dateField.x + 'px'" 
             [style.top]="dateField.y + 'px'"
             [style.width]="dateField.width + 'px'" 
             [style.height]="dateField.height + 'px'">
          <input *ngIf="showDateInput" 
                 type="date" 
                 [(ngModel)]="selectedDate"
                 (blur)="saveDateInput()">
        </div>
      </div>
    </div>
  `,
  styles: [`
    .pdf-container { position: relative; width: 100%; height: 800px; }
    .interactive-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
    .signature-field, .date-field { 
      position: absolute; 
      border: 1px dashed #007bff; 
      background: rgba(0, 123, 255, 0.1);
      pointer-events: all;
    }
    .signature-field button { 
      width: 100%; height: 100%; 
      border: none; background: transparent; 
      cursor: pointer; font-size: 12px;
    }
    .signature-field button:hover { background: rgba(0, 123, 255, 0.2); }
  `]
})
export class CustomOverlayComponent {
  @ViewChild('pdfContainer') pdfContainer: ElementRef;
  
  pdfSrc = 'path/to/your/document.pdf';
  showDateInput = false;
  selectedDate: string;
  
  // Определение позиций полей (эти значения динамически устанавливаются на основе анализа PDF)
  signatureField = { x: 100, y: 200, width: 80, height: 30 };
  dateField = { x: 200, y: 250, width: 100, height: 30 };
  
  ngOnInit() {
    // Настройка прослушивателей событий для взаимодействий с PDF
    this.setupFieldInteractions();
  }
  
  setupFieldInteractions() {
    // Логика обнаружения, когда пользователи кликают по областям PDF
    // и определение, кликают ли они по полям подписи/даты
  }
  
  openSignaturePad() {
    // Открытие модального окна или наложения планшета для подписи
    console.log('Открытие планшета для подписи');
    // Реализация покажет canvas для подписи
  }
  
  saveDateInput() {
    console.log('Дата сохранена:', this.selectedDate);
    // Логика сохранения даты в документ PDF
    this.showDateInput = false;
  }
}

Примеры кода и реализация

Полный пример с использованием ngx-extended-pdf-viewer

Вот комплексная реализация с использованием ngx-extended-pdf-viewer:

typescript
import { Component } from '@angular/core';

@Component({
  selector: 'app-pdf-forms-example',
  template: `
    <div class="pdf-forms-container">
      <ngx-extended-pdf-viewer
        [src]="pdfSrc"
        [useBrowserLocale]="true"
        [height]="'80vh'"
        [textLayer]="true"
        [handTool]="true"
        [stampTool]="true"
        [signatureTool]="true"
        [formElements]="true"
        (pageRendered)="onPageRendered($event)"
        (fieldClick)="onFieldClick($event)">
        
        <!-- Пользовательская панель инструментов с кнопкой подписи -->
        <ngx-pdf-toolbar>
          <button class="custom-sign-btn" (click)="openSignaturePad()">
            Подписать документ
          </button>
        </ngx-pdf-toolbar>
      </ngx-extended-pdf-viewer>
      
      <!-- Модальное окно планшета для подписи -->
      <div *ngIf="showSignatureModal" class="signature-modal">
        <div class="modal-content">
          <h3>Подписать документ</h3>
          <canvas #signatureCanvas width="400" height="200"></canvas>
          <div class="modal-buttons">
            <button (click)="clearSignature()">Очистить</button>
            <button (click)="saveSignature()">Сохранить подпись</button>
            <button (click)="closeSignatureModal()">Отмена</button>
          </div>
        </div>
      </div>
      
      <!-- Наложение выборщика даты -->
      <div *ngIf="showDatePicker" class="date-picker-overlay"
           [style.left]="datePosition.x + 'px'" 
           [style.top]="datePosition.y + 'px'">
        <input type="date" 
               [(ngModel)]="selectedDate"
               (change)="onDateSelect($event)"
               (blur)="hideDatePicker()">
      </div>
    </div>
  `,
  styles: [`
    .pdf-forms-container { position: relative; }
    .custom-sign-btn { 
      background: #007bff; 
      color: white; 
      border: none; 
      padding: 5px 10px; 
      border-radius: 3px;
      cursor: pointer;
    }
    .signature-modal { 
      position: fixed; 
      top: 0; left: 0; 
      width: 100%; height: 100%; 
      background: rgba(0,0,0,0.5); 
      display: flex; 
      justify-content: center; 
      align-items: center;
      z-index: 1000;
    }
    .modal-content { 
      background: white; 
      padding: 20px; 
      border-radius: 8px;
      text-align: center;
    }
    .modal-buttons { 
      margin-top: 15px; 
    }
    .modal-buttons button { 
      margin: 0 5px; 
      padding: 5px 10px;
    }
    .date-picker-overlay { 
      position: absolute; 
      z-index: 100; 
      background: white;
      border: 1px solid #ccc;
      padding: 5px;
      border-radius: 4px;
    }
  `]
})
export class PdfFormsExampleComponent {
  pdfSrc = 'path/to/your/interactive-form.pdf';
  showSignatureModal = false;
  showDatePicker = false;
  selectedDate: string;
  datePosition = { x: 0, y: 0 };
  
  @ViewChild('signatureCanvas') signatureCanvas: any;
  
  onPageRendered(event: any) {
    // Логика обработки, когда страницы PDF отрисованы
    // Здесь можно настроить обнаружение полей
  }
  
  onFieldClick(event: any) {
    // Обработка кликов по полям формы PDF
    if (event.field && event.field.type === 'signature') {
      this.openSignaturePad();
    } else if (event.field && event.field.type === 'text') {
      // Проверка, является ли это полем даты
      if (event.field.name.toLowerCase().includes('date')) {
        this.showDateAtPosition(event.x, event.y);
      }
    }
  }
  
  openSignaturePad() {
    this.showSignatureModal = true;
    this.setupSignatureCanvas();
  }
  
  setupSignatureCanvas() {
    const canvas = this.signatureCanvas.nativeElement;
    const ctx = canvas.getContext('2d');
    let isDrawing = false;
    
    const startDrawing = (e: any) => {
      isDrawing = true;
      ctx.beginPath();
      ctx.moveTo(e.offsetX, e.offsetY);
    };
    
    const draw = (e: any) => {
      if (!isDrawing) return;
      ctx.lineTo(e.offsetX, e.offsetY);
      ctx.stroke();
    };
    
    const stopDrawing = () => {
      isDrawing = false;
    };
    
    canvas.addEventListener('mousedown', startDrawing);
    canvas.addEventListener('mousemove', draw);
    canvas.addEventListener('mouseup', stopDrawing);
  }
  
  clearSignature() {
    const canvas = this.signatureCanvas.nativeElement;
    const ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }
  
  saveSignature() {
    const canvas = this.signatureCanvas.nativeElement;
    const signatureData = canvas.toDataURL();
    // Здесь сохраняется подпись в PDF
    console.log('Подпись сохранена:', signatureData);
    this.closeSignatureModal();
  }
  
  closeSignatureModal() {
    this.showSignatureModal = false;
  }
  
  showDateAtPosition(x: number, y: number) {
    this.datePosition = { x, y };
    this.showDatePicker = true;
    this.selectedDate = new Date().toISOString().split('T')[0];
  }
  
  onDateSelect(event: any) {
    console.log('Выбрана дата:', this.selectedDate);
    // Логика обновления поля формы PDF
  }
  
  hideDatePicker() {
    setTimeout(() => this.showDatePicker = false, 200);
  }
}

Лучшие практики и соображения по производительности

Оптимизация производительности

  1. Ленивая загрузка: Загружайте модули просмотрщика PDF только при необходимости для уменьшения начального размера пакета.

  2. Оптимизация canvas: Для планшетов подписи реализуйте правильную очистку canvas и управление памятью.

  3. Дебаунсинг событий: Используйте дебаунсинг для событий взаимодействия с PDF для предотвращения проблем с производительностью.

  4. Виртуальная прокрутка: Для больших PDF реализуйте виртуальную прокрутку для эффективной обработки множества страниц.

Соображения доступности

  1. Навигация с клавиатуры: Убедитесь, что все интерактивные компоненты доступны с клавиатуры.

  2. Поддержка скринридеров: Предоставьте правильные ARIA-метки и описания для элементов формы.

  3. Управление фокусом: Правильно управляйте фокусом при показе/скрытии модальных окон и наложений.

Соображения безопасности

  1. Проверка ввода: Проверяйте все пользовательские вводы, особенно для полей даты и данных подписи.

  2. Обработка файлов: Безопасно обрабатывайте загрузку и скачивание PDF.

  3. Очистка данных: Очищайте данные подписи и другие пользовательские вводы перед обработкой.

Советы по реализации

  1. Начинайте с простого: Начните с базовых полей формы и постепенно добавляйте более сложные функции.

  2. Используйте сервисы: Создайте специализированные сервисы для манипулирования PDF и обработки форм.

  3. Тестирование: Тщательно тестируйте взаимодействия с PDF в разных браузерах и на устройствах.

  4. Обработка ошибок: Реализуйте надежную обработку ошибок для сбоев загрузки PDF и отправки форм.

Источники

  1. How to add interactive components like button/input on top of PDF in Angular? - Stack Overflow

  2. Angular + PDF + Forms = PDF Dynamic Reactive Forms | Medium

  3. Angular PDF Viewer | View, Annotate, Fill, Sign PDFs | Syncfusion

  4. Building a Signature Pad in Angular: A Step-by-Step Guide | Medium

  5. ngx-extended-pdf-viewer - npm

  6. Help Syncfusion - Hand written signature in Angular PDF viewer Control

  7. PDF.js Express: Angular PDF Viewer Documentation

  8. GitHub - VadimDez/ng2-pdf-viewer

  9. How to build an Angular PDF viewer with PDF.js | Nutrient

  10. Build an Angular PDF viewer with ng2-pdf-viewer | Nutrient

Заключение

Добавление интерактивных компонентов, таких как кнопки и поля ввода, поверх PDF в Angular достижимо через несколько проверенных подходов. Для ваших конкретных потребностей реализации функциональности подписи и полей ввода даты я рекомендую:

  1. Начните с ngx-extended-pdf-viewer для комплексной поддержки форм и более простой реализации инструментов подписи.

  2. Реализуйте пользовательские наложения для точного контроля над позиционированием компонентов и пользовательским опытом.

  3. Используйте canvas-планшеты для подписи для естественного опыта подписи, который можно легко интегрировать с вашим рабочим процессом PDF.

  4. Используйте реактивные формы Angular для надежной обработки форм и проверки.

  5. Рассмотрите PDF.js Express для расширенных возможностей аннотирования, если ваш проект требует коммерческой лицензии.

Ключом к успеху является тщательное планирование логики обнаружения полей и обеспечение правильной обработки событий между вашим просмотрщиком PDF и компонентами Angular. Начните с базовых реализаций и постепенно добавляйте более сложные функции по мере знакомства с базовыми технологиями.