How to customize link toolbar on right clicking text in pdftron reactjs

Product: “@pdftron/webviewer”: “^7.3.3”,

Product Version: “@pdftron/webviewer”: “^7.3.3”,

Please give a brief summary of your issue: I want to create a custom modal while selecting text and create it a link. Please share how to customize link toolbar and create a custom modal on it.
(Think of this as an email subject)

Please describe your issue and provide steps to reproduce it:
(The more descriptive your answer, the faster we are able to help you)

Please provide a link to a minimal sample where the issue is reproducible:

Hey @mohit,

Selecting text opens the TextPopup by default, which has a button to open the link modal.
image

If you want to add your own custom modal you have a couple of options to achieve this. The first is to add custom buttons to the TextPopup using our APIs.

You could add a custom button to this popup to open your modal, using our addCustomModal API. You could also wire your modal to open on right click by listening to the mouseRightDown event on the document viewer, but you will have to figure out what text should be selected/linked.

The other option is for you to fork our UI and customize in any way you see fit. To read more about this option follow this guide.

Best Regards,
Armando Bollain
Software Developer
PDFTron Systems, Inc.

Hi Armando,

Thanks for sharing, it worked for me but when I am trying to use this in my code where link needs to be customized. It is saying [Two instances of WebViewer were created on the same HTML element ]

import React, { useEffect, useRef, useState, createElement } from “react”;
import WebViewer from “@pdftron/webviewer”;
import “./App.css”;
import { connect, useDispatch } from “react-redux”;
import { URLHide } from “…/…/…/…/camunda_redux/redux/action”;
import { setSnackbar } from “app/camunda_redux/redux/ducks/snackbar”;

const SplitViewPdfViewer = (props) => {
const dispatch = useDispatch();
const viewer = React.createRef();
const { flag, extension, anottId, fileUrl } = props;
const [instance, setInstance] = useState(null);

const callMessageOut = (msg) => {
dispatch(setSnackbar(true, “error”, msg));
};

const handleWaterMark = (docViewer) => {
docViewer.setWatermark({
// Draw diagonal watermark in middle of the document
diagonal: {
fontSize: 35, // or even smaller size
fontFamily: “sans-serif”,
color: “#5a5ad6”,
opacity: 40, // from 0 to 100
text: ${sessionStorage.getItem("pklDirectrate")},
},

  // Draw header watermark
  header: {
    fontSize: 10,
    fontFamily: "sans-serif",
    color: "red",
    opacity: 70,
  },
});

};

useEffect(() => {
try {
if (instance !== null) {
const { docViewer } = instance;

    if (!fileUrl) {
      return;
    } else {
      handleWaterMark(docViewer);

      const URL =
        fileUrl === ""
          ? `${process.env.PUBLIC_URL + "/assets/sample.pdf"}`
          : fileUrl;
      props.URLHide(URL).then(async (response) => {
        let data = await response.blob();

        let file = new File([data], "test.docx");
        instance.loadDocument(file, {
          extension: extension ? extension : "docx",
        });
      });
    }
  } else {
    WebViewer(
      {
        path: `${process.env.PUBLIC_URL + "/webviewer/lib"}`,
        initialDoc: `${process.env.PUBLIC_URL + "/assets/sample.pdf"}`,
        fullAPI: true,
        enableRedaction: true,
        backendType: "ems",
        disableLogs: true,
      },
      viewer.current
    ).then((instance) => {
      // const { documentViewer } = instance.Core;
      // you can now call WebViewer APIs here...
      instance.textPopup.update([
        {
          type: 'actionButton',
          img: 'https://www.pdftron.com/favicon-32x32.png',
          onClick: () => {
            setOpenDialog(true);
          }
        },
        {
          type: 'actionButton',
          img: 'path/to/image',
          onClick: instance.print,
        },
      ]);
    })
      .then((instance) => {
        setInstance(instance);

        const { docViewer } = instance;
        handleWaterMark(docViewer);
      })
      .catch((e) => {});
  }
} catch (e) {
  callMessageOut(e.message)
}

}, [instance, fileUrl]);

const renderPdf = () => {
// const prevPdf = <div className=“webviewer” style={{ height: “100vh” }} ref={ViewerDiv}>
const newPdf = createElement(“div”, {
id: “pdfv”,
className: “webviewer”,
style: { height: “100vh” },
ref: viewer,
});
return newPdf;
};

return

{renderPdf()}
;
};

function mapStateToProps(state) {
return {
props: state.props,
theme: state.theme,
};
}

export default connect(mapStateToProps, { URLHide })(
React.memo(SplitViewPdfViewer)
);

Hi Mohit,

We usually see this issue when an effect is triggering one to many times and re-rendering the viewer. Is the error causing an issue or is your viewer still rendering correctly?

I would double check in your code that you are not trying to instantiate the viewer twice. If you still have issues we would need a runnable project on our end to see the error happening (a github repo also works).

Best Regards,
Armando Bollain
Software Developer
PDFTron Systems, Inc.

hi armando

i have implemened the custom pop up on text select based on the refernces u have sent but now i want to accomplish2 things :

  1. on my custom modal i have a controllled textfield in which i will pass the url to which my selected text will prompt me to , so i want to know how can i convert the selected text to a link
  2. once my selected text is coverted to a link i want a function to be called everytime i click on this link

Hi Mohit,

I think the best way to show how to turn selected text into links is to have a look at how our UI does it. Since it is all open source you can have a closer look. The relevant code is here.

You can see that we add a Link annotation and then create an Action U with the hyper link. You can also see some more basic samples in this guide..

To add an action that triggers a function call, you can use the JavaScript action instead. This action takes in the code that will be run.

Best Regards,
Armando Bollain
Software Developer
PDFTron Systems, Inc.

Hi Armando,

Thanks for the reply, but I am not able to achieve outcome. I am just trying to create link on a selected text. I am able to customize text pop-up and inserted a modal in which text-box where url is typed in which cancel and submit are shown. I want is that when submit button is clicked, text become URL and when URL (text) is clicked, it will be redirected to a specified place.

src.zip (2.4 KB)

Attached is the SRC file, as a minimal sample where the issue is reproducible:

Hi Mohit,

Going over your code there are a few things to tweak.

First, your Link annotations need to be based on the selected quads, not just the entire page. The link annotation itself has to match the width and height of the selected text quads. I couldn’t see clearly how you are getting your quads, but I do see this which is not ideal:

instance.docViewer.qI.AnnotationEdit.pageCoordinates[1]

This appears to be using minified properties to get some coordinates - not sure what these map to but we do not recommend you try to access minified properties directly, as these get scrambled on every release. This would mean any time you upgrade versions your code will break. Instead I suggest leveraging APIs to get existing selected text quads.

This is a brief example on how to add a link to selected text based on our UI (which I shared a link to earlier). When the function addLinkToSelectedText is called, it adds a link to google.com to whatever text is currently selected:

  const { documentViewer, Annotations, annotationManager, Actions } = instance.Core;

  // Helper method to create a link 
  const newLink = (x, y, width, height, linkPageNumber) => {
    const link = new Annotations.Link();
    link.PageNumber = linkPageNumber;
    link.StrokeColor = new Annotations.Color(0, 165, 228);
    link.StrokeStyle = 'underline';
    link.StrokeThickness = 2;
    link.Author = 'Test user'
    link.Subject = 'Link';
    link.X = x;
    link.Y = y;
    link.Width = width;
    link.Height = height;
    return link;
  };

  const addLinkToSelectedText = () => {
    const selectedTextQuads = documentViewer.getSelectedTextQuads();
    const currentPageLinks = [];
    const action = new Actions.URI({ uri: 'www.google.com' });

    // Quads is a map of page number to an array of quads for each line of selected text
    for (const pageNumber in selectedTextQuads) {
      selectedTextQuads[pageNumber].forEach((quad) => {
        const link = newLink(
          Math.min(quad.x1, quad.x3),
          Math.min(quad.y1, quad.y3),
          Math.abs(quad.x1 - quad.x3),
          Math.abs(quad.y1 - quad.y3),
          parseInt(pageNumber)
        );
        link.addAction('U', action);
        currentPageLinks.push(link);
      });
    }
    annotationManager.addAnnotations(currentPageLinks);
    let pageNumbersToDraw = currentPageLinks.map((link) => link.PageNumber);
    pageNumbersToDraw = [...new Set(pageNumbersToDraw)];
    pageNumbersToDraw.forEach((pageNumberToDraw) => {
      annotationManager.drawAnnotations(pageNumberToDraw, null, true);
    });
  };

Here are some handy APIs:

The open source WebViewer UI source code should give you more ideas, but this pared down example above will show you the salient points. Let me know how it goes.

Best Regards,
Armando Bollain
Software Developer
PDFTron Systems, Inc.

thank yo so much for you help, we have implement that part can you help us how we can implement our own event when we click on the link

hi Armando
I and mohit are in team, we have implemented all the custom link functionality we just want to do auto save functionality once we add link and for that we are using code given below but it will return same size of blob file even though we have added link can you just help us what we are doing wrong here

`const { docViewer } = instance;

const doc = docViewer.getDocument();
// const xfdfString = await annotationManager.exportAnnotations();
const data = await doc.getFileData({
  // saves the document with annotations in it
  // xfdfString,
});
const arr = new Uint8Array(data);
const blob = new Blob([arr], {
  type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});

console.log(blob)

`

Hello @harshvardhan,

Glad that helped. The latest issue is due to the fact that you are not including the links when you get your file data. All you need to do is include the xfdf string as a parameter and ensure you also include link annotations.

const doc = docViewer.getDocument();
const xfdfString = await annotationManager.exportAnnotations({ links: true });
const data = await doc.getFileData({
  // saves the document with annotations in it, links are annotations
  xfdfString,
});

See docs for all options:
[exportAnnotations] (PDFTron WebViewer Class: AnnotationManager)

Best Regards,
Armando Bollain
Software Developer
PDFTron Systems, Inc.

thank you so much for your help

1 Like