red-ui/apps/red-ui/src/app/modules/pdf-viewer/services/annotation-draw.service.ts

202 lines
9.9 KiB
TypeScript

import { Injectable } from '@angular/core';
import { Core } from '@pdftron/webviewer';
import { hexToRgb } from '@utils/functions';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { UserPreferenceService } from '@users/user-preference.service';
import { RedactionLogService } from '@services/files/redaction-log.service';
import { IRectangle, ISectionGrid, ISectionRectangle, SuperTypes } from '@red/domain';
import { firstValueFrom } from 'rxjs';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { PdfViewer } from './pdf-viewer.service';
import { ActivatedRoute } from '@angular/router';
import { REDAnnotationManager } from './annotation-manager.service';
import { List } from '@iqser/common-ui';
import { REDDocumentViewer } from './document-viewer.service';
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import Annotation = Core.Annotations.Annotation;
import Quad = Core.Math.Quad;
const DEFAULT_TEXT_ANNOTATION_OPACITY = 1;
const DEFAULT_REMOVED_ANNOTATION_OPACITY = 0.2;
@Injectable()
export class AnnotationDrawService {
constructor(
private readonly _dictionariesMapService: DictionariesMapService,
private readonly _redactionLogService: RedactionLogService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _annotationManager: REDAnnotationManager,
private readonly _pdf: PdfViewer,
private readonly _documentViewer: REDDocumentViewer,
private readonly _defaultColorsService: DefaultColorsService,
) {}
async draw(annotations: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
try {
await this._draw(annotations, hideSkipped, dossierTemplateId);
} catch (e) {
console.error(e);
}
}
convertColor(hexColor: string) {
return this._pdf.color(hexToRgb(hexColor));
}
annotationToQuads(annotation: Annotation) {
const x1 = annotation.getRect().x1;
const y1 = annotation.getRect().y1 + annotation.getRect().getHeight();
const x2 = annotation.getRect().x1 + annotation.getRect().getWidth();
const y2 = annotation.getRect().y1 + annotation.getRect().getHeight();
const x3 = annotation.getRect().x1 + annotation.getRect().getWidth();
const y3 = annotation.getRect().y1;
const x4 = annotation.getRect().x1;
const y4 = annotation.getRect().y1;
return this._pdf.quad(x1, y1, x2, y2, x3, y3, x4, y4);
}
private async _draw(annotationWrappers: List<AnnotationWrapper>, hideSkipped: boolean, dossierTemplateId: string) {
const totalPages = await firstValueFrom(this._pdf.totalPages$);
const annotations = annotationWrappers
?.map(annotation => this._computeAnnotation(annotation, hideSkipped, totalPages, dossierTemplateId))
.filterTruthy();
const documentLoaded = await firstValueFrom(this._documentViewer.loaded$);
if (!documentLoaded) {
return;
}
await this._annotationManager.add(annotations);
if (this._userPreferenceService.areDevFeaturesEnabled) {
const { dossierId, fileId } = this._pdf;
const sectionsGrid$ = this._redactionLogService.getSectionGrid(dossierId, fileId);
const sectionsGrid = await firstValueFrom(sectionsGrid$).catch(() => ({ rectanglesPerPage: {} }));
await this._drawSections(sectionsGrid, dossierTemplateId);
}
}
private async _drawSections(sectionGrid: ISectionGrid, dossierTemplateId: string) {
const sections: Core.Annotations.RectangleAnnotation[] = [];
for (const page of Object.keys(sectionGrid.rectanglesPerPage)) {
const sectionRectangles = sectionGrid.rectanglesPerPage[page];
sectionRectangles.forEach(sectionRectangle => {
sections.push(this._computeSection(parseInt(page, 10), sectionRectangle, dossierTemplateId));
});
}
await this._annotationManager.add(sections);
}
private _computeSection(pageNumber: number, sectionRectangle: ISectionRectangle, dossierTemplateId: string) {
const rectangleAnnot = this._pdf.rectangle();
const pageHeight = this._documentViewer.getHeight(pageNumber);
const rectangle: IRectangle = {
topLeft: sectionRectangle.topLeft,
page: pageNumber,
height: sectionRectangle.height,
width: sectionRectangle.width,
};
rectangleAnnot.PageNumber = pageNumber;
rectangleAnnot.X = rectangle.topLeft.x - 1;
rectangleAnnot.Y = pageHeight - (rectangle.topLeft.y + rectangle.height) - 1;
rectangleAnnot.Width = rectangle.width + 2;
rectangleAnnot.Height = rectangle.height + 2;
rectangleAnnot.ReadOnly = true;
rectangleAnnot.StrokeColor = this.convertColor(this._defaultColorsService.getColor(dossierTemplateId, 'analysisColor'));
rectangleAnnot.StrokeThickness = 1;
return rectangleAnnot;
}
private _computeAnnotation(annotationWrapper: AnnotationWrapper, hideSkipped: boolean, totalPages: number, dossierTemplateId: string) {
const pageNumber = this._pdf.isCompare ? annotationWrapper.pageNumber * 2 - 1 : annotationWrapper.pageNumber;
if (pageNumber > totalPages) {
// skip imported annotations from files that have more pages than the current one
return;
}
if (annotationWrapper.superType === SuperTypes.TextHighlight) {
const rectangleAnnot = this._pdf.rectangle();
const pageHeight = this._documentViewer.getHeight(pageNumber);
const rectangle: IRectangle = annotationWrapper.positions[0];
rectangleAnnot.PageNumber = pageNumber;
rectangleAnnot.X = rectangle.topLeft.x;
rectangleAnnot.Y = pageHeight - (rectangle.topLeft.y + rectangle.height);
rectangleAnnot.Width = rectangle.width;
rectangleAnnot.Height = rectangle.height;
rectangleAnnot.ReadOnly = true;
rectangleAnnot.StrokeColor = this.convertColor(annotationWrapper.color);
rectangleAnnot.StrokeThickness = 1;
rectangleAnnot.Id = annotationWrapper.id;
return rectangleAnnot;
}
const annotation = this._pdf.textHighlight();
annotation.Quads = this._rectanglesToQuads(annotationWrapper.positions, pageNumber);
annotation.Opacity = annotationWrapper.isChangeLogRemoved ? DEFAULT_REMOVED_ANNOTATION_OPACITY : DEFAULT_TEXT_ANNOTATION_OPACITY;
annotation.setContents(annotationWrapper.content);
annotation.PageNumber = pageNumber;
annotation.StrokeColor = this.convertColor(annotationWrapper.color);
annotation.Id = annotationWrapper.id;
annotation.ReadOnly = true;
const isOCR = annotationWrapper.isOCR && !annotationWrapper.isSuggestionResize;
if (isOCR && !this._annotationManager.isHidden(annotationWrapper.annotationId)) {
this._annotationManager.addToHidden(annotationWrapper.annotationId);
}
annotation.Hidden =
annotationWrapper.isChangeLogRemoved ||
(hideSkipped && annotationWrapper.isSkipped) ||
this._annotationManager.isHidden(annotationWrapper.annotationId);
annotation.setCustomData('redact-manager', 'true');
annotation.setCustomData('redaction', String(annotationWrapper.previewAnnotation));
annotation.setCustomData('suggestion', String(annotationWrapper.isSuggestion));
annotation.setCustomData('suggestionAdd', String(annotationWrapper.isSuggestionAdd));
annotation.setCustomData('suggestionAddToFalsePositive', String(annotationWrapper.isSuggestionAddToFalsePositive));
annotation.setCustomData('suggestionRemove', String(annotationWrapper.isSuggestionRemove));
annotation.setCustomData('suggestionRecategorizeImage', String(annotationWrapper.isSuggestionRecategorizeImage));
annotation.setCustomData('skipped', String(annotationWrapper.isSkipped));
annotation.setCustomData('changeLog', String(annotationWrapper.isChangeLogEntry));
annotation.setCustomData('changeLogRemoved', String(annotationWrapper.isChangeLogRemoved));
annotation.setCustomData('opacity', String(annotation.Opacity));
const redactionColor =
annotationWrapper.isSuggestion && this._userPreferenceService.getDisplaySuggestionsInPreview()
? this._defaultColorsService.getColor(dossierTemplateId, 'requestAddColor')
: this._defaultColorsService.getColor(dossierTemplateId, 'previewColor');
annotation.setCustomData('redactionColor', String(redactionColor));
annotation.setCustomData('annotationColor', String(annotationWrapper.color));
const appliedRedactionColor = this._defaultColorsService.getColor(dossierTemplateId, 'appliedRedactionColor');
annotation.setCustomData('appliedRedactionColor', String(appliedRedactionColor));
return annotation;
}
private _rectanglesToQuads(positions: IRectangle[], pageNumber: number): Quad[] {
const pageHeight = this._documentViewer.getHeight(pageNumber);
return positions.map(p => this._rectangleToQuad(p, pageHeight));
}
private _rectangleToQuad(rectangle: IRectangle, pageHeight: number): Quad {
const x1 = rectangle.topLeft.x;
const y1 = pageHeight - (rectangle.topLeft.y + rectangle.height);
const x2 = rectangle.topLeft.x + rectangle.width;
const y2 = pageHeight - (rectangle.topLeft.y + rectangle.height);
const x3 = rectangle.topLeft.x + rectangle.width;
const y3 = pageHeight - rectangle.topLeft.y;
const x4 = rectangle.topLeft.x;
const y4 = pageHeight - rectangle.topLeft.y;
return this._pdf.quad(x1, y1, x2, y2, x3, y3, x4, y4);
}
}