From 528647146c9892378db7195d4a6f08dd4b4bd19b Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 6 Feb 2023 18:29:27 +0200 Subject: [PATCH] RED-6014: show japanese characters in watermark text --- .../preferences/preferences.component.ts | 5 +- .../watermark-screen.component.html | 3 +- .../watermark-screen.component.ts | 131 ++++++++---------- .../watermarks-listing-screen.component.ts | 12 +- .../entity-services/watermark.service.ts | 39 +++--- apps/red-ui/src/app/utils/functions.ts | 6 +- apps/red-ui/src/app/utils/page-stamper.ts | 24 ++-- 7 files changed, 104 insertions(+), 116 deletions(-) diff --git a/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts b/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts index 252299c5e..7f374b298 100644 --- a/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts +++ b/apps/red-ui/src/app/modules/account/screens/preferences/preferences.component.ts @@ -1,8 +1,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service'; -import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { BaseFormComponent, IqserPermissionsService } from '@iqser/common-ui'; +import { AsControl, BaseFormComponent, IqserPermissionsService } from '@iqser/common-ui'; import { ROLES } from '@users/roles'; interface PreferencesForm { @@ -16,7 +16,6 @@ interface PreferencesForm { [k: string]: any; } -type AsControl = { [K in keyof T]: FormControl }; type Screen = 'preferences' | 'warnings-preferences'; const Screens = { diff --git a/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html index 8bc3c616d..8fcc82c0b 100644 --- a/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/watermark/watermark-screen/watermark-screen.component.html @@ -1,5 +1,6 @@
-
+
+
= { orientation: WatermarkOrientations.HORIZONTAL, } as const; +interface WatermarkForm { + name: string; + text: string; + hexColor: string; + opacity: number; + fontSize: number; + fontType: string; + orientation: WatermarkOrientation; +} + @Component({ selector: 'redaction-watermark-screen', templateUrl: './watermark-screen.component.html', @@ -49,13 +60,11 @@ export class WatermarkScreenComponent { readonly #watermarkId = Number(getParam(WATERMARK_ID)); private _instance: WebViewerInstance; private _watermark: Partial = {}; - @ViewChild('viewer', { static: true }) - private _viewer: ElementRef; constructor( private readonly _http: HttpClient, private readonly _toaster: Toaster, - private readonly _formBuilder: UntypedFormBuilder, + private readonly _formBuilder: FormBuilder, readonly permissionsService: IqserPermissionsService, private readonly _loadingService: LoadingService, private readonly _licenseService: LicenseService, @@ -82,7 +91,7 @@ export class WatermarkScreenComponent { } get valid(): boolean { - if (!this.form.get('name')?.value || !this.form.get('text')?.value) { + if (!this.form.controls.name?.value || !this.form.controls.text?.value) { return false; } return this.form.valid; @@ -102,22 +111,13 @@ export class WatermarkScreenComponent { }; this._loadingService.start(); try { - await firstValueFrom( - this._watermarkService.saveWatermark(watermark).pipe( - tap(() => { - this._toaster.success( - watermark.id ? _('watermark-screen.action.change-success') : _('watermark-screen.action.created-success'), - ); - }), - tap(async updatedWatermark => { - if (!watermark.id) { - await this._router.navigate([ - `/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${updatedWatermark.id}`, - ]); - } - }), - ), + const updatedWatermark = await this._watermarkService.saveWatermark(watermark); + this._toaster.success( + watermark.id ? _('watermark-screen.action.change-success') : _('watermark-screen.action.created-success'), ); + if (!watermark.id) { + await this._router.navigate([`/main/admin/dossier-templates/${this.#dossierTemplateId}/watermarks/${updatedWatermark.id}`]); + } } catch (error) { this._toaster.error(_('watermark-screen.action.error')); } @@ -136,46 +136,44 @@ export class WatermarkScreenComponent { } } - private _initForm(watermark: Partial) { + private async _initForm(watermark: Partial) { this._watermark = { ...watermark, dossierTemplateId: this.#dossierTemplateId }; this.form.patchValue({ ...watermark }); - this._loadViewer(); + await this._loadViewer(); this._changeDetectorRef.markForCheck(); } - private _loadViewer() { - if (!this._instance) { - WebViewer( - { - licenseKey: this._licenseService.activeLicenseKey, - path: this._convertPath('/assets/wv-resources'), - css: this._convertPath('/assets/pdftron/stylesheet.css'), - fullAPI: true, - isReadOnly: true, - backendType: 'ems', - }, - this._viewer.nativeElement as HTMLElement, - ).then(instance => { - this._instance = instance; - - instance.UI.setTheme(this._userPreferenceService.getTheme()); - - instance.Core.documentViewer.on('documentLoaded', async () => { - this._loadingService.stop(); - await this._drawWatermark(); - }); - - this._disableElements(); - - return this._http - .request('get', '/assets/pdftron/blank.pdf', { - responseType: 'blob', - }) - .subscribe(blobData => { - this._instance.UI.loadDocument(blobData, { filename: 'blank.pdf' }); - }); - }); + private async _loadViewer() { + if (this._instance) { + return; } + + this._instance = await WebViewer( + { + licenseKey: this._licenseService.activeLicenseKey, + path: this._convertPath('/assets/wv-resources'), + css: this._convertPath('/assets/pdftron/stylesheet.css'), + fullAPI: true, + isReadOnly: true, + backendType: 'ems', + }, + document.getElementById('viewer'), + ); + + this._instance.UI.setTheme(this._userPreferenceService.getTheme()); + + this._instance.Core.documentViewer.addEventListener('documentLoaded', async () => { + this._loadingService.stop(); + await this._drawWatermark(); + }); + + this._disableElements(); + + const request = this._http.get('/assets/pdftron/blank.pdf', { + responseType: 'blob', + }); + const blobData = await firstValueFrom(request); + this._instance.UI.loadDocument(blobData, { filename: 'blank.pdf' }); } private _disableElements() { @@ -186,22 +184,15 @@ export class WatermarkScreenComponent { const pdfNet = this._instance.Core.PDFNet; const document = await this._instance.Core.documentViewer.getDocument().getPDFDoc(); - const text: string = this.form.get('text').value || ''; - const fontSize: number = this.form.get('fontSize').value; - const fontType: string = this.form.get('fontType').value; - const orientation: WatermarkOrientation = this.form.get('orientation').value; - const opacity: number = this.form.get('opacity').value; - const color: string = this.form.get('hexColor').value; - await stampPDFPage( document, pdfNet, - text, - fontSize, - fontType, - orientation, - opacity, - color, + this.form.controls.text.value || '', + this.form.controls.fontSize.value, + this.form.controls.fontType.value, + this.form.controls.orientation.value, + this.form.controls.opacity.value, + this.form.controls.hexColor.value, [1], this._licenseService.activeLicenseKey, ); @@ -210,8 +201,8 @@ export class WatermarkScreenComponent { this._changeDetectorRef.detectChanges(); } - private _getForm(): UntypedFormGroup { - const form = this._formBuilder.group({ + private _getForm() { + const form: FormGroup> = this._formBuilder.group({ name: [null], text: [null], hexColor: [null], diff --git a/apps/red-ui/src/app/modules/admin/screens/watermark/watermarks-listing/watermarks-listing-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/watermark/watermarks-listing/watermarks-listing-screen.component.ts index ce670246c..61007cd28 100644 --- a/apps/red-ui/src/app/modules/admin/screens/watermark/watermarks-listing/watermarks-listing-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/watermark/watermarks-listing/watermarks-listing-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Component } from '@angular/core'; import { CircleButtonTypes, ConfirmationDialogInput, @@ -14,7 +14,6 @@ import { } from '@iqser/common-ui'; import { DOSSIER_TEMPLATE_ID, User, Watermark } from '@red/domain'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { firstValueFrom } from 'rxjs'; import { WatermarkService } from '@services/entity-services/watermark.service'; import { AdminDialogService } from '../../../services/admin-dialog.service'; import { WatermarksMapService } from '@services/entity-services/watermarks-map.service'; @@ -23,7 +22,6 @@ import { ROLES } from '@users/roles'; @Component({ templateUrl: './watermarks-listing-screen.component.html', styleUrls: ['./watermarks-listing-screen.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, providers: listingProvidersFactory(WatermarksListingScreenComponent), }) export class WatermarksListingScreenComponent extends ListingComponent { @@ -53,8 +51,8 @@ export class WatermarksListingScreenComponent extends ListingComponent { - const isUsed = await firstValueFrom(this._watermarkService.isWatermarkUsed(watermark.id)); + async openConfirmDeleteWatermarkDialog($event: MouseEvent, watermark: Watermark) { + const isUsed = await this._watermarkService.isWatermarkUsed(watermark.id); const data = new ConfirmationDialogInput({ question: isUsed ? _('watermarks-listing.watermark-is-used') : null, @@ -66,7 +64,7 @@ export class WatermarksListingScreenComponent extends ListingComponent { this._loadingService.start(); - const updatedWatermark = await firstValueFrom(this._watermarkService.saveWatermark({ ...watermark, enabled: !watermark.enabled })); + const updatedWatermark = await this._watermarkService.saveWatermark({ ...watermark, enabled: !watermark.enabled }); this.entitiesService.replace(updatedWatermark); this._loadingService.stop(); } @@ -77,7 +75,7 @@ export class WatermarksListingScreenComponent extends ListingComponent { this._loadingService.start(); - await firstValueFrom(this._watermarkService.deleteWatermark(this.#dossierTemplateId, watermark.id)); + await this._watermarkService.deleteWatermark(this.#dossierTemplateId, watermark.id); this.entitiesService.setEntities(this._watermarksMapService.get(this.#dossierTemplateId)); this._toaster.success(_('watermarks-listing.action.delete-success')); this._loadingService.stop(); diff --git a/apps/red-ui/src/app/services/entity-services/watermark.service.ts b/apps/red-ui/src/app/services/entity-services/watermark.service.ts index eeb8a062c..848fed6fe 100644 --- a/apps/red-ui/src/app/services/entity-services/watermark.service.ts +++ b/apps/red-ui/src/app/services/entity-services/watermark.service.ts @@ -1,8 +1,8 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { GenericService, mapEach, QueryParam, RequiredParam, Validate } from '@iqser/common-ui'; import { IWatermark, Watermark } from '@red/domain'; -import { forkJoin, Observable } from 'rxjs'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { firstValueFrom, forkJoin, Observable } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; import { WatermarksMapService } from '@services/entity-services/watermarks-map.service'; interface IsUsedResponse { @@ -14,27 +14,19 @@ interface IsUsedResponse { }) export class WatermarkService extends GenericService { protected readonly _defaultModelPath = 'watermark'; + readonly #watermarksMapService = inject(WatermarksMapService); - constructor(private readonly _watermarksMapService: WatermarksMapService) { - super(); + @Validate() + async deleteWatermark(@RequiredParam() dossierTemplateId: string, @RequiredParam() watermarkId: number) { + await firstValueFrom(super.delete(null, `${this._defaultModelPath}/${watermarkId}`)); + return firstValueFrom(this.loadForDossierTemplate(dossierTemplateId)); } @Validate() - deleteWatermark(@RequiredParam() dossierTemplateId: string, @RequiredParam() watermarkId: number): Observable { - return super - .delete(null, `${this._defaultModelPath}/${watermarkId}`) - .pipe(switchMap(() => this.loadForDossierTemplate(dossierTemplateId))); - } - - @Validate() - saveWatermark(@RequiredParam() body: IWatermark): Observable { - return this._post(body, `${this._defaultModelPath}`).pipe( - switchMap(watermark => - this.loadForDossierTemplate(watermark.dossierTemplateId).pipe( - map(() => this._watermarksMapService.get(watermark.dossierTemplateId, watermark.id)), - ), - ), - ); + async saveWatermark(@RequiredParam() body: IWatermark) { + const watermark = await firstValueFrom(this._post(body, `${this._defaultModelPath}`)); + await firstValueFrom(this.loadForDossierTemplate(watermark.dossierTemplateId)); + return this.#watermarksMapService.get(watermark.dossierTemplateId, watermark.id); } @Validate() @@ -42,7 +34,7 @@ export class WatermarkService extends GenericService { const queryParams: QueryParam[] = [{ key: 'dossierTemplateId', value: dossierTemplateId }]; return this.getAll(this._defaultModelPath, queryParams).pipe( mapEach(entity => new Watermark(entity)), - tap(entities => this._watermarksMapService.set(dossierTemplateId, entities.sort(this.sortByStatusFn))), + tap(entities => this.#watermarksMapService.set(dossierTemplateId, entities.sort(this.sortByStatusFn))), ); } @@ -52,9 +44,10 @@ export class WatermarkService extends GenericService { } @Validate() - isWatermarkUsed(@RequiredParam() watermarkId: number): Observable { + async isWatermarkUsed(@RequiredParam() watermarkId: number) { const queryParams: QueryParam[] = [{ key: 'watermarkId', value: watermarkId }]; - return this.getAll(`${this._defaultModelPath}/used`, queryParams).pipe(map(result => result.value)); + const result = await firstValueFrom(this.getAll(`${this._defaultModelPath}/used`, queryParams)); + return result.value; } sortByStatusFn = (a: Watermark, b: Watermark) => { diff --git a/apps/red-ui/src/app/utils/functions.ts b/apps/red-ui/src/app/utils/functions.ts index 918227208..ff16d7819 100644 --- a/apps/red-ui/src/app/utils/functions.ts +++ b/apps/red-ui/src/app/utils/functions.ts @@ -1,6 +1,7 @@ import type { List } from '@iqser/common-ui'; import type { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { Dayjs } from 'dayjs'; +import { ITrackable } from '../../../../../libs/common-ui/src/lib/listing/models/trackable'; export function hexToRgb(hex: string) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); @@ -89,7 +90,10 @@ export function compareLists(l1: string[], l2: string[]) { } export const byPage = (page: number) => (annotation: AnnotationWrapper) => annotation.pageNumber === page; -export const byId = (id: string) => (annotation: AnnotationWrapper) => annotation.annotationId === id; + +export function byId(id: string) { + return (item: T) => item.id === id; +} export function getLast(list: List) { return list[list.length - 1]; diff --git a/apps/red-ui/src/app/utils/page-stamper.ts b/apps/red-ui/src/app/utils/page-stamper.ts index 0efc57e5f..7c428c383 100644 --- a/apps/red-ui/src/app/utils/page-stamper.ts +++ b/apps/red-ui/src/app/utils/page-stamper.ts @@ -12,16 +12,16 @@ async function createPageSet(pdfNet: typeof Core.PDFNet, pages: number[]) { return pageSet; } -function convertFont(type: string): number { +function convertFont(pdfNet: typeof Core.PDFNet, type: string): number { switch (type) { case 'times-new-roman': - return 0; + return pdfNet.Font.StandardType1Font.e_times_roman; case 'helvetica': - return 4; + return pdfNet.Font.StandardType1Font.e_helvetica; case 'courier': - return 8; + return pdfNet.Font.StandardType1Font.e_courier; } - return 4; + return pdfNet.Font.StandardType1Font.e_helvetica; } export async function clearStamps(document: PDFDoc, pdfNet: typeof Core.PDFNet, pages: number[], licenseKey: string) { @@ -34,7 +34,7 @@ export async function clearStamps(document: PDFDoc, pdfNet: typeof Core.PDFNet, export async function stampPDFPage( document: PDFDoc, - pdfNet: any, + pdfNet: typeof Core.PDFNet, text: string, fontSize: number, fontType: string, @@ -50,10 +50,10 @@ export async function stampPDFPage( const pageSet = await createPageSet(pdfNet, pages); await pdfNet.Stamper.deleteStamps(document, pageSet); - const rgbColor = hexToRgb(color); + const { b, g, r } = hexToRgb(color); - const stamper = await pdfNet.Stamper.create(3, fontSize, 0); - await stamper.setFontColor(await pdfNet.ColorPt.init(rgbColor.r / 255, rgbColor.g / 255, rgbColor.b / 255)); + const stamper = await pdfNet.Stamper.create(pdfNet.Stamper.SizeType.e_font_size, fontSize, 0); + await stamper.setFontColor(await pdfNet.ColorPt.init(r / 255, g / 255, b / 255)); await stamper.setOpacity(opacity / 100); switch (orientation) { @@ -74,8 +74,10 @@ export async function stampPDFPage( await stamper.setRotation(-45); } - const font = await pdfNet.Font.createAndEmbed(document, convertFont(fontType)); - await stamper.setFont(font); + const initialFont = await pdfNet.Font.create(document, convertFont(pdfNet, fontType)); + // in case there are japanese characters in the text, we add them to the font + const fontWithAllTextChars = await pdfNet.Font.createFromFontDescriptor(document, initialFont, text); + await stamper.setFont(fontWithAllTextChars); await stamper.setTextAlignment(0); await stamper.stampText(document, text, pageSet); }, licenseKey);