react-pdf-highlighter-extended
TypeScript icon, indicating that this package has built-in type declarations

8.0.0 • Public • Published

react-pdf-highlighter-extended

Node.js CI npm version

react-pdf-highlighter-extended is a React library that provides a highly customisable annotation experience for PDF documents on the web, with text and rectangular highlights both supported. It leverages PDF.js as its viewer. The highlight data format is also independent of the viewport, making it suitable for saving on a server.

This originally started as a fork of react-pdf-highlighter but so much has been refactored and redesigned that it would be a burden to pull it to the original repo. Some of these changes include: addition of HighlightContext, PdfHighlighterContext, and MonitoredHighlightContainer; zoom support; exposed styling on all components; and numerous bugfixes. Efforts will be made to try to ensure feature parity with the original repo, but there are no guarantees that syntax and usage will be the same.

Table of Contents

Documentation

If you just want to use this library, you can find comprehensive docs for all aspects by visiting the official documentation page.

If you wish to contribute, most internal components are documented in code, though not to the same depth.

Example

For a live demo check https://danielarnould.github.io/react-pdf-highlighter-extended/example-app/.

To run the example app locally:

git clone https://github.com/DanielArnould/react-pdf-highlighter-extended.git
npm install
npm run dev

Installation

npm install react-pdf-highlighter-extended --save

Usage

Here are some simple usage examples of this library to help you get started with your application. Please note that these examples and explanations are not exhaustive and many additional props are not shown. To see more extensive usage, have a look at the example app or refer to the documentation.

PdfLoader

The PdfLoader creates a container to load PDF documents with PDF.js.

Example loading document with string

const url = "https://arxiv.org/pdf/1708.08021.pdf";

<PdfLoader document={url}>
  {(pdfDocument) => (/* PdfHighlighter component goes here */)}
</PdfLoader>;

Example loading document with DocumentInitParameters

You can also pass any extra parameters to the PdfLoader document that are accepted by PDF.js. For example, you could use this to specify HTTP headers for retrieving your document.

const pdfDocument: Partial<DocumentInitParameters> = {
  url: "https://arxiv.org/pdf/1708.08021.pdf",
  httpHeaders: {
      "Authorization": "Bearer JWT_TOKEN_HERE"
  },
  password: "PDF_PASSWORD_HERE"
}

<PdfLoader document={pdfDocument}>
  {(pdfDocument) => (/* PdfHighlighter component goes here */)}
</PdfLoader>

PdfHighlighter

The PdfHighlighter provides a PDF.js viewer along with various helpful event listeners and niceties for creating a fully-fledged and robust highlighter. It does NOT render any highlights on its own. Instead, it expects a user-defined Highlight Container as its child, which will be rendered and given context for each individual highlights. Please also note for styling that the PDF.js viewer renders its pages with absolute positioning.

Example

const myPdfHighlighter = () => {
  const [highlights, setHighlights] = useState<Array<Highlight>>([]);

  /** Refs for PdfHighlighter utilities
   * These contain numerous helpful functions, such as scrollToHighlight,
   * getCurrentSelection, setTip, and many more
   */
  const highlighterUtilsRef = useRef<PdfHighlighterUtils>();

return (
  <PdfLoader document={url}>
    {(pdfDocument) => (
      <PdfHighlighter
        enableAreaSelection={(event) => event.altKey}
        pdfDocument={pdfDocument}
        utilsRef={(_pdfHighlighterUtils) => {
          highlighterUtilsRef.current = _pdfHighlighterUtils;
        }}
        selectionTip={<ExpandableTip />} // Component will render as a tip upon any selection
        highlights={highlights}
      >
        {/* User-defined HighlightContainer component goes here */}
      </PdfHighlighter>
    )}
  </PdfLoader>
);
}

User-defined HighlightContainer

You must create your own Highlight Container which will be rendered as needed for each highlight inside the PdfHighlighter. This container will receive the context it needs through the useHighlightContainerContext hook. Additionally, you can access numerous useful utility functions through the usePdfHighlighterContext hook. This library also provides two ready-to-use componenets, TextHighlight and AreaHighlight, which you can place inside your container to easily render some standard highlight styles.

Example

interface MyHighlightContainerProps {
  editHighlight: (idToUpdate: string, edit: Partial<Highlight>) => void; // This could update highlights in the parent
}

const MyHighlightContainer = ({
  editHighlight,
}: MyHighlightContainerProps) => {

  const {
    highlight, // The highlight being rendred
    viewportToScaled, // Convert a highlight position to platform agnostic coords (useful for saving edits)
    screenshot, // Screenshot a bounding rectangle
    isScrolledTo, // Whether the highlight has been auto-scrolled to
    highlightBindings, // Whether the highlight has been auto-scrolled to
  } = useHighlightContainerContext();

  const {
    currentTip,
    setTip,
    toggleEditInProgress,
    isEditInProgress
  } = useTipViewerUtils();

  const { toggleEditInProgress } =
    usePdfHighlighterContext();

  const isTextHighlight = !Boolean(
    highlight.content && highlight.content.image
  );

  const component = isTextHighlight ? (
    <TextHighlight
      isScrolledTo={isScrolledTo}
      highlight={highlight}
    />
  ) : (
    <AreaHighlight
      isScrolledTo={isScrolledTo}
      highlight={highlight}
      onChange={(boundingRect) => {
        const edit = {
          position: {
            boundingRect: viewportToScaled(boundingRect),
            rects: [],
          },
          content: {
            image: screenshot(boundingRect),
          },
        };

        editHighlight(highlight.id, edit);
        toggleEditInProgress(false);
      }}
      bounds={highlightBindings.textLayer}
      onEditStart={() => toggleEditInProgress(true)}
    />
  );

  return (component);
};

Example with categories and custom highlights

The power of a user-defined highlight container is that you can customise your highlight rendering as much as you want. For example, here is how you could extend your application to support highlights with categories.

export interface MyCustomHighlight extends Highlight {
    category: string
}

You could then use this in your HighlightContainer to render highlights with different colors depending on their category.

// Same logic as above examples

  const {
    highlight,
    viewportToScaled,
    screenshot,
    isScrolledTo,
    highlightBindings,
  } = useHighlightContainerContext<MyCustomHighlight>();

const category = highlight.category;
let highlightColor = "rgba(199,227,114,1)";

if (category === "red") {
  highlightColor = "rgba(239,90,104,1)";
} else if (category === "blue") {
  highlightColor = "rgba(154,208,220,1)";
}

const component = isTextHighlight ? (
  <TextHighlight highlight={highlight} style={{ background: highlightColor }} />
) : (
  <AreaHighlight
    highlight={highlight}
    style={{
      background: highlightColor,
    }}
  />
);

// Same return as above examples

Example with comments and MonitoredHighlightContainer

Very often you might want to display a popup, tip, or comment if the user hovers over a highlight. To facilitate this, this library offers a MonitoredHighlightContainer, which you can wrap around your rendered highlight to create a mouse listener both over the highlight and any popup content you might display above it. Combining this with custom highlights allows you to associate all sorts of displayable information with your highlights.

export interface MyCustomHighlight extends Highlight {
    comment?: string
}

const MyHighlightPopup = (highlight: ViewportHighlight<MyCustomHighlight>) => {
  return highlight.comment ? (
    <div>{highlight.comment}</div>
  ) : (
    <div>Highlight has no comment</div>
  );
};

const MyHighlightContainer = ({
  editHighlight,
}: MyHighlightContainerProps) => {

  // Same hooks as above example
  // Same logic as above example

  const highlightTip: Tip = {
    position: highlight.position,
    content: <HighlightPopup highlight={highlight} />
  };

  return (
    <MonitoredHighlightContainer
      highlightTip={highlightTip}
      key={highlight.id}
      children={component}
    />
  );
};

Tips

At any point you can use getTip() and setTip() from the usePdfHighlighterContext() hook or the utilsRef property on PdfHighlighter. To set a tip, simply provide the position of a highlight, ghost highlight, or selection and the component you wish to render. This will automatically place your tip in the middle and slightly above your given highlight (or below if necessary). However, since the PdfHighlighter is not aware of the state of your tip, you must tell it to update its position if your tip ever changes size and you want it to remain above/below your given highlight. Fortunately, you can do this quite easily with updateTipPosition() within a PdfHighlighterContext.

Example

const MyExpandableTip = () => {
  const [compact, setCompact] = useState(true);

  const { getCurrentSelection, updateTipPosition } =
    usePdfHighlighterContext();

  useLayoutEffect(() => {
    updateTipPosition!();
  }, [compact]);

  return (
    <div className="Tip">
      {compact ? (
        <button
          onClick={() => {
            setCompact(false);
            if (getCurrentSelection()) {
              getCurrentSelection().makeGhostHighlight();
            }
          }}
        >
          Expand Tip
        </button>
      ) : (
        <div style={{ padding: "50px" }}>Expanded content</div>
      )}
    </div>
  );
};

Contribute

If you have a bug to report, please add it as an issue with clear steps to reproduce it.

If you have a feature request, please add it as an issue or make a pull request. If you do wish to make a pull request, consider checking whether your feature has already been implemented or tested in the original react-pdf-highlighter.

Package Sidebar

Install

npm i react-pdf-highlighter-extended

Weekly Downloads

118

Version

8.0.0

License

MIT

Unpacked Size

276 kB

Total Files

139

Last publish

Collaborators

  • ozek