wojtekmaj / react-pdf

Display PDFs in your React app as easily as if they were images.
https://projects.wojtekmaj.pl/react-pdf
MIT License
8.96k stars 861 forks source link

Method to get destination from outline #1829

Open keyneston opened 2 weeks ago

keyneston commented 2 weeks ago

Before you start - checklist

Description

I'm working on a PDF viewing tool. One of the features I want to provide is a custom outline.

From pdf.getOutline() I can successfully create a list of both top level and nested items. The thing I can't do is then link those to the specific pages.

Is there, or could there be a method on an OutlineItem that allows looking up the destination to implement custom onClick events.

Proposed solution

outline.map((item, index) => (
  item.getPageIndex()
)

Or a longer example:

  const [pageIndex, setPageIndex] = useState(0);
  useEffect(() => {
    item.getPageIndex().then((res) => setPageIndex(res))
  })
  return (
    <ListItem key={index} disablePadding>
      <ListItemButton key={index} onClick={(event) => {
        virtuosoRef.current.scrollToIndex(pageIndex);
      }}>
        <ListItemText key={index} primary={data.title} />
      </ListItemButton>
    </ListItem>
  )

Alternatives

Alternatively some way to completely customize the <Outline />. The current <Outline /> is an all or nothing affair from what I've seen.

Additional information

No response

wojtekmaj commented 2 weeks ago

OutlineItem component exposes onItemClick which provides pre-looked-up properties: { dest, pageIndex, pageNumber } as args. Look at the source code of OutlineItem if you'd like to recreate the tree built by Outline component, along with getting page number/index for each item.

keyneston commented 2 weeks ago

If anyone else encounters this the following code made it work. 90+% of it was copied from wojtekmaj's work.

import type { RefProxy } from 'pdfjs-dist/types/src/display/api.js';

class Ref {
  num: number;
  gen: number;

  constructor({ num, gen }: { num: number; gen: number }) {
    this.num = num;
    this.gen = gen;
  }

  toString() {
    let str = `${this.num}R`;
    if (this.gen !== 0) {
      str += this.gen;
    }
    return str;
  }
}

const getDestination = (pdf: any, item: any) => {
  if (typeof item.dest === 'string') {
    return pdf.getDestination(item.dest);
  }

  return item.dest;
};

const getPageIndex = async (pdf: any, item: any) => {
  const destination = await getDestination(pdf, item);

  if (!destination) {
    throw new Error('Destination not found.');
  }

  const [ref] = destination as [RefProxy];

  return pdf.getPageIndex(new Ref(ref));
};

// Callback
       <ListItemButton key={`${index}`} onClick={(_event) => {
        getPageIndex(pdf, data).then((pageIndex) => {
          virtuosoRef.current.scrollToIndex(pageIndex);

This works, but it would be nice to have these functions on the object rather than needing to copy paste them into the code.