stephanrauh / ngx-extended-pdf-viewer

A full-blown PDF viewer for Angular 16, 17, and beyond
https://pdfviewer.net
Apache License 2.0
473 stars 181 forks source link

Expose Stamp Editor Functionality #2148

Closed gouravkajal closed 6 months ago

gouravkajal commented 7 months ago

Is your feature request related to a problem? Please describe. I want to upload an image to the pdf at a specific location and at the current visible page as well. I'm creating a feature where I'll make some text in pdf clickable. Once user clicks on the text, a dialog will appear where user either can draw something or upload an image. Once user selects the image then that image should be added to the PDF at the exact location where the clickable text is. On the other hand, if user clicks on the button which is out of the PDF, then they will upload the image in the same manner but now this image should be added to the current visible page.

Describe the solution you'd like I think stamp editor is working in the same way. It's like we can't control what image and location the image will be added to. If you can expose this functionality so that we simply pass the image, x, y and page number and image adds to pdf accordingly, that will be of great help.

Describe alternatives you've considered I have tried addEditorAnnotation but seems like it doesn't accepts the image.

stephanrauh commented 7 months ago

AFAIK addEditorAnnotation accepts the image if you pass it as an URL (e.g. a data URL containing a base64 encoded image). But I haven't tried that myself yet.

gouravkajal commented 7 months ago

Hi @stephanrauh, Thank you for replying.

By looking at the addEditorAnnotation definition, I don't think it accepts the image, or may be I'm missing something.

addEditorAnnotation(serializedAnnotation: string | EditorAnnotation): void;

type EditorAnnotation = InkEditorAnnotation | FreeTextEditorAnnotation;
type InkEditorAnnotation = {
    annotationType: 15;
    color: Array<number>;
    thickness: number;
    opacity: number;
    paths: Array<BezierPath>;
    pageIndex: number;
    rect: Array<number>;
    rotation: 0 | 90 | 180 | 270;
};
type FreeTextEditorAnnotation = {
    annotationType: 3;
    color: Array<number>;
    fontSize: number;
    value: string;
    pageIndex: number;
    rect: Array<number>;
    rotation: 0 | 90 | 180 | 270;
};

And when I execute getSerializedAnnotations after adding an image using in-built stamp editor, this is what I get.

[
  {
    "annotationType": 13,
    "bitmapId": "image_3ab9dab8-1f50-4de6-8d65-67b16e234d1c_0",
    "pageIndex": 0,
    "rect": [
      74.41,
      478.54521711635743,
      520.87,
      610.2998545531196
    ],
    "rotation": 0,
    "isSvg": false,
    "structTreeParentId": null
  }
]

Could you please help me to understand this?

stephanrauh commented 7 months ago

It seems I have to update that type definition. There are also auto-generated type definitions: https://github.com/stephanrauh/ngx-extended-pdf-viewer/blob/main/projects/ngx-extended-pdf-viewer/types/src/display/editor. Unfortunately, these type definitions only cover the editors, not the annotations, to they're not very helpful.

If I understand the source correctly, you can use bitmapUrl instead of bitmapId. This way, you can pass a data URL.

stephanrauh commented 7 months ago

To dos left:

gouravkajal commented 7 months ago

@stephanrauh I came to know about bitmapUrl property and managed to add image with following code.

const x = 496;
const y = 386; 
const stampAnnotation: any = {
  annotationType: 13,
  pageIndex: 2,
  bitmapUrl: 'data:image/png;base64,iVB......',
  rect: [
    x,
    y,
    x + 128,
    y + 32
  ],
  rotation: 0,
}
this._pdfService.addEditorAnnotation(stampAnnotation);

Now the problem is, I don't know how to get x, y coordinates of a text from PDF. Whatever I do, I don't get them right. Secondly, I'm not able to drag and resize the added image. Seems like disbaled class is the culprit which is being added with annotationEditorLayer by default. This class I can remove with JS for now. But could you please help me with the coordinates? If you can share some code to get the exact x, y coordinates of a text from PDF, that would be of great help.

stephanrauh commented 7 months ago

I can only guess... but I think PDFViewerApplication.pdfViewer._pages[0].pdfPage.view yields the dimensions of the page. Another educated guess: it's an array consisting of four numbers: [left, bottom, right, top].

Note that the y axis starts at the bottom. Now that I think of it, that's pretty much standard, but for some reason, I always expected it to be the other way round.

stephanrauh commented 7 months ago

Just double-checking: Is it enough to remove the disabled class? That the image is protected from being moved is no surprise (at least not to me). You're using an API that bypasses the UI workflow. And you might add several images at once. The user can only drag or resize one image at a time, so the PDF viewer defaults to locking them all until the user unlocks them by clicking the image.

gouravkajal commented 7 months ago

@stephanrauh Removing the disabled class is kind of working. I'm able to drag and resize the added image once I remove this class. But I realised one more issue. Once I add an image to the page, then that page becomes un-clickable. I'm not able to click out of the added image container so the image is always selected. I don't know why this is happening. Seems like if we add the image using addEditorAnnotation, not all of the stamp-editor functions are executed.

Secondly, not sure how I can access PDFViewerApplication in my code. Is this exported or I need to do anything else?

stephanrauh commented 7 months ago

PDFViewerApplication is an object stored in the global window (or globalThis) object. Usually I access it using this line:

const PDFViewerApplication: IPDFViewerApplication = (window as any).PDFViewerApplication;

I can only guess why your PDF file isn't clickable, but chances are it's because of the annotation editor mode. Try resetting it like so:

PDFViewerApplication.pdfViewer.annotationEditorMode = 0;
o-sirenko commented 7 months ago

Hello @stephanrauh, I'm trying to implement adding image to the PDF annotation layer, and I faced with the same issue as @gouravkajal mentioned, I can't unselect the loaded image and I can't use this code PDFViewerApplication.pdfViewer.annotationEditorMode = 0; pdfViewer doesn't have the annotationEditorMode property, if I use pdfDefaultOptions.annotationEditorMode = 0 it doesn't help as well.

I noticed if I try to create the stamp using toolbar, my image can be selected/unselected when I click outside of the image only if the stamp icon in toolbar active, when it inactive, I can't select the image if I e.g want to delete it.

Screenshot 2024-02-23 at 22 59 57

Tell me please, do you have any ideas how to unselect/select the image and control 'remove' button visibility? (When click to the image it should be selected and "remove" button should appear, when unselected, remove button should be disappeared)

Screenshot 2024-02-23 at 22 07 35.

Tell me please is there a way to track when "remove" button was clicked on the image? Maybe there is some event that can be triggered.

stephanrauh commented 7 months ago

Oh, the property does exist, it's just slightly different than I thought:

PDFViewerApplication.pdfViewer.annotationEditorMode = {mode:0}; // no editor
PDFViewerApplication.pdfViewer.annotationEditorMode = {mode:3}; // text editor
PDFViewerApplication.pdfViewer.annotationEditorMode = {mode:9}; // highlight editor (sneak preview)
PDFViewerApplication.pdfViewer.annotationEditorMode = {mode:13}; // stamp editor
PDFViewerApplication.pdfViewer.annotationEditorMode = {mode:15}; // ink editor

But maybe it's more future-proof to emit an event:

const PDFViewerApplication: IPDFViewerApplication = (window as any).PDFViewerApplication;
PDFViewerApplication.eventBus.dispatch('switchannotationeditormode', {mode: 0});

The current version has a bug: it doesn't update the state of the buttons. The next version I ship will fix that.

o-sirenko commented 7 months ago

@stephanrauh could you please clarify, if PDFViewerApplication.pdfViewer.annotationEditorMode exists in the "stable" or "bleeding edge" lib version? I'm using the stable version.

Regarding the "disabled" class that is added by default to the annotationEditorLayer, this class visually adds the CSS rule "pointer-event: none", but when we use stampEditor on the toolbar and toggle the state from "disabled" to "stampEditing", some additional logic happens under the hood and also the image with class "stampEditor" gets or lose the additional class "selectedEditor"

Screenshot 2024-02-24 at 20 36 46

When I use this code after I create the new annotation using addEditorAnnotation(), nothing happens, I mean I can't unselect the image. const PDFViewerApplication: IPDFViewerApplication = (window as any).PDFViewerApplication; PDFViewerApplication.eventBus.dispatch('switchannotationeditormode', {mode: 0});

Also if I have a few images, the last image that was added has the selected state and displays "remove" button, when I remove the last image, I can't select the other images while "annotationEditorLayer" has class "disabled"

Tell me please is it possible to activate edit mode for StampEditor by clicking on the image and deactivate it when I click outside of the image? The other question is, is it possible to turn off resizing and draggable features and just allow to select the image and remove it when StampEditor is activated?

stephanrauh commented 7 months ago

PDFViewerApplication.pdfViewer.annotationEditorMode exists in both versions.

stephanrauh commented 6 months ago

Currently, I'm implementing an API allowing you to add images. You can see a sneak preview at https://pdfviewer.net/extended-pdf-viewer/annotation-layer-api. Note that the API is subject to change. I'm still experimenting with the best approach. This, in turn, means you can throw in your 2 pence. If you've got a suggestion how to do it, fire away!

Here's the sneak preview: https://pdfviewer.net/extended-pdf-viewer/annotation-layer-api

o-sirenko commented 6 months ago

@stephanrauh Thank you so much for implementing this very useful feature. Could you please clarify whether there are plans to add the ability to remove the image from the annotation layer when the toolbar is hidden?

Regarding the documentation, it seems that pencil and text drawings could already be a part of Blob. Is this part of the export feature description relevant?

Screenshot 2024-02-26 at 11 47 45
stephanrauh commented 6 months ago

the ability to remove the image from the annotation layer when the toolbar is hidden?

Wait, what? I guess there are a few words missing... I don't think you want to remove the freshly stamped image from the PDF file again after hiding every button of the toolbar... :)

Would you mind to elaborate?

stephanrauh commented 6 months ago

Regarding the documentation: thanks for pointing this out. I've fixed this. There's no point in describing bugs long since fixed.

stephanrauh commented 6 months ago

Version 19.4.0-alpha.2 offers the API I have in mind. It doesn't implements the unit "px" yet, and it's possible I'm going to split the gigantic NgxExtendedPdfViewerService class into smaller pieces. Apart from that, the API looks pretty much the way I'd like it to do.

o-sirenko commented 6 months ago

the ability to remove the image from the annotation layer when the toolbar is hidden?

Wait, what? I guess there are a few words missing... I don't think you want to remove the freshly stamped image from the PDF file again after hiding every button of the toolbar... :)

Would you mind to elaborate?

In my case I need to hide the whole toolbar and simplify functionality for the user, just input data to the pdf form and sign the document. I use extra lib that allows to create the image from the canvas, this lib allows to create the signature as an image. There can be the case when the user creates the signature as an image, but his signature is wrong and he wants to remove the old signature and create the new one. Or even there can be the case when the user adds two signatures as the images in the document.

I'm wondering if is it possible to remove the images if the user doesn't have the toolbar

stephanrauh commented 6 months ago

Now I've got it! You can use the API I'm describing here: https://pdfviewer.net/extended-pdf-viewer/export-annotations

public removeDrawingEditors(): void {
  const filter = (serial: any) => serial.annotationType === 15;
  this.pdfViewerService.removeEditorAnnotations(filter);
}

The code snippet removes every images. I suppose it'll do the trick for you. If you need more control - for instance, because you've added multiple images - you can export the annotations, delete every annotation, and add them again:

this.rawAnnotations = this.pdfViewerService.getSerializedAnnotations();
this.pdfViewerService.removeEditorAnnotations();
for (const annotation: rawAnnotation) {
   if (...) {
    this.pdfViewerService.addEditorAnnotation(annotation);
  }
o-sirenko commented 6 months ago

Now I've got it! You can use the API I'm describing here: https://pdfviewer.net/extended-pdf-viewer/export-annotations

public removeDrawingEditors(): void {
  const filter = (serial: any) => serial.annotationType === 15;
  this.pdfViewerService.removeEditorAnnotations(filter);
}

The code snippet removes every images. I suppose it'll do the trick for you. If you need more control - for instance, because you've added multiple images - you can export the annotations, delete every annotation, and add them again:

this.rawAnnotations = this.pdfViewerService.getSerializedAnnotations();
this.pdfViewerService.removeEditorAnnotations();
for (const annotation: rawAnnotation) {
   if (...) {
    this.pdfViewerService.addEditorAnnotation(annotation);
  }

Thank you so much for helping

stephanrauh commented 6 months ago

I've finished both implementation and documentation. Your feature is going to ship later this evening (version 19.4.0).

ziaKhan1995 commented 5 months ago

@stephanrauh Thank you for your great efforts to develop this library. And I would be happy if you help me a little bit. I want to set the custom default size for every stamp image that we insert.

In addition to that When I download the PDF it does not show the stamped image, text written, or something drawn but all these are available When I Print the PDF. I need the new images or text stamped in PDF as a blob or any other URL to upload to the server.

Currently, I am using the latest version which is "ngx-extended-pdf-viewer": "^19.7.1",

stephanrauh commented 5 months ago

If you're using NgxExtendedPdfViewerService.addImageToAnnotationLayer() to add the image, you can set the size by setting the parameters left, right, bottom, and top:

export interface PdfImageParameters {
  urlOrDataUrl: string;
  page?: number;
  left?: number | string;
  bottom?: number | string;
  right?: number | string;
  top?: number | string;
  rotation?: 0 | 90 | 180 | 270;
}
stephanrauh commented 5 months ago

@ziaKhan1995 I've tried to reproduce your second issue, but it doesn't happen on my machine. I've opened https://pdfviewer.net/extended-pdf-viewer/blob, added a stamp, and both print and download show the image. Can you send me a reproducer? And when you do, would you mind to open a new ticket? It sounds like a bug, so it needs proper tracking.

ziaKhan1995 commented 5 months ago

@ziaKhan1995 I've tried to reproduce your second issue, but it doesn't happen on my machine. I've opened https://pdfviewer.net/extended-pdf-viewer/blob, added a stamp, and both print and download show the image. Can you send me a reproducer? And when you do, would you mind to open a new ticket? It sounds like a bug, so it needs proper tracking.

@stephanrauh I understand your concern regarding the download issue on your and someone else machine but here is my video proof ngx extended pdf editor export issue

ziaKhan1995 commented 5 months ago

Link the ticket regarding this issue

https://github.com/stephanrauh/ngx-extended-pdf-viewer/issues/2312