EmbedPDF

Links

Most PDFs have links: a table of contents that jumps to a chapter, a footnote to a website. Register linkPlugin() and put <LinkLayer> in each page, and they work:

link-layer.vue
<script setup lang="ts">
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { LinkLayer, linkPlugin } from '@embedpdf/vue/link';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { Viewer } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { engine } from './pdf';

const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), linkPlugin()];
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins">
    <Stage>
      <template #page>
        <RenderLayer />
        <LinkLayer />
      </template>
    </Stage>
  </Viewer>
</template>

Loading live preview…

<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { annotationPlugin } from '@embedpdf/vue/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/vue/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.vue';

import '../basic.css';

const engine = localEngine();
// The annotation plugin is only here to make the links below.
const plugins = [
  stagePlugin(),
  renderPlugin(),
  interactionPlugin(),
  annotationPlugin(),
  linkPlugin(),
];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <AddLinks />
      <p class="hint">
        Two links at the top of the first page. Click one, or press Tab to reach it and Enter to
        follow it.
      </p>
      <Stage class="stage">
        <template #page>
          <RenderLayer />
          <LinkLayer />
        </template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>
  • A link to a page goes there, to the exact spot and zoom the link names.
  • A link to a website opens in a new tab, after a safety check (see Good to know).
  • A link with a PDF action, such as a button that resets a form, runs the action when the actions plugin is registered.

The pointer turns into a hand over a link, and a link can be reached with the Tab key and followed with Enter.

By default a link area is invisible, as in other PDF apps. <LinkLayer><template #link> lets you draw it yourself, for example with an underline on hover:

custom-links.vue
<script setup lang="ts">
import { LinkLayer } from '@embedpdf/vue/link';
</script>

<template>
  <LinkLayer>
    <template #link="{ link, native }">
      <span class="pdf-link" :title="link.target.kind === 'uri' ? link.target.uri : undefined">
        <component :is="native" />
      </span>
    </template>
  </LinkLayer>
</template>

Loading live preview…

<script setup lang="ts">
import { ref } from 'vue';
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { annotationPlugin } from '@embedpdf/vue/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/vue/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.vue';

import '../custom.css';

const engine = localEngine();
// The annotation plugin is only here to make the links below.
const plugins = [
  stagePlugin(),
  renderPlugin(),
  interactionPlugin(),
  annotationPlugin(),
  linkPlugin(),
];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

const shown = ref(true);
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <AddLinks />
      <div class="toolbar">
        <div class="segmented" role="group" aria-label="Link areas">
          <button type="button" :aria-pressed="shown" @click="shown = true">
            Show link areas
          </button>
          <button type="button" :aria-pressed="!shown" @click="shown = false">
            As the PDF has them
          </button>
        </div>
      </div>
      <Stage class="stage">
        <template #page>
          <RenderLayer />
          <LinkLayer>
            <template #link="{ link, native }">
              <!-- Wrapping `native` keeps what a click does. -->
              <span v-if="shown" class="pdf-link" :data-kind="link.target.kind">
                <component :is="native" />
              </span>
              <!-- The layer's own, invisible area. -->
              <component :is="native" v-else />
            </template>
          </LinkLayer>
        </template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>

native is the layer’s own clickable area: wrap it to keep what a click does. For a link you don’t want to change, show native alone.

link.listLinks(page.ref); // every link on the page
link.getLinkAt(page.ref, { x: 120, y: 80 }); // the link at a point, or null

Each link has an id, its bounds on the page, and a target: where it goes.

target.kindGoes to
'goto'A page of this document: target.destination
'uri'A website or an email address: target.uri
'named'A viewer action, such as the next page
'javascript'Runs the link’s script, when the actions plugin allows it
'goto-remote', 'launch'Nowhere: see Which links never run

activate() does what a click does:

const { outcome } = link.activate(someLink);

It returns what happened right away, so a website can still open in a new tab: 'revealed' (the view went to a page), 'uri' (a website opened), or 'dispatched' (the actions plugin runs the link’s action). A link it won’t follow, such as one to another file, is 'reported'.

Loading live preview…

<script setup lang="ts">
import { ref } from 'vue';
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { annotationPlugin } from '@embedpdf/vue/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/vue/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.vue';
import LinkList from './LinkList.vue';

import '../list.css';

const engine = localEngine();
// The annotation plugin is only here to make the links below.
const plugins = [
  stagePlugin(),
  renderPlugin(),
  interactionPlugin(),
  annotationPlugin(),
  linkPlugin(),
];

const ebook = async (): Promise<OpenInput> => {
  const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
  return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
};

// The list reads the links once they're made.
const ready = ref(false);
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <AddLinks @added="ready = true" />
      <div class="layout">
        <LinkList :ready="ready" />
        <Stage class="stage">
          <template #page>
            <RenderLayer />
            <LinkLayer />
          </template>
        </Stage>
      </div>
    </DocumentGate>
  </Viewer>
</template>

Methods#

Everything useLink() gives you.

MethodWhat it does
listLinks(page)Every link on a page, once the page’s links are read. Read the links on a page
getLink(page, id)One link by its id, or null
getLinkAt(page, point)The link at a point on the page, the smallest when links overlap, or null
listAllLinks()Every link in the document, reading each page’s links as needed
ensureLoaded(page)Reads a page’s links if they aren’t read yet. <LinkLayer> does this for every page it shows.
isLoaded(page), getStatus(page)Whether a page’s links are read, and in detail: 'idle', 'loading', 'ready', 'error' or 'forbidden'
activate(link)Follows a link, or a link’s target, as a click does, and returns what happened. Follow a link from code
activateAt(page, point)Follows the link at a point on the page. Returns null when there’s no link there.
resolve(target)What following a target would do, without doing it: { kind } is 'destination', 'uri', 'named' or 'reported'
getLabel(link)A short label for a tooltip: the web address, or where the link goes

Every method that returns a promise also takes a signal in its options, to cancel it.

Events#

link-analytics.vue
<script setup lang="ts">
import { LinkLayer, useLinkEvent } from '@embedpdf/vue/link';
import { RenderLayer } from '@embedpdf/vue/render';
import { Stage } from '@embedpdf/vue/stage';
import { analytics } from './analytics';

useLinkEvent(
  (link) => link.onActivated,
  ({ target }) => analytics.track('link_followed', { kind: target.kind }),
);
</script>

<template>
  <Stage>
    <template #page>
      <RenderLayer />
      <LinkLayer />
    </template>
  </Stage>
</template>
EventFires whenCarries
onActivatedSomeone followed a link, by click, key or codetarget, activation
onLoadedA page’s links were read, or read again after a changepage

Good to know#

A website link opens only with http, https, mailto or tel. An address without one opens on https when it starts with // or www.; any other, such as /help or page2.html, is relative to the document and opens nothing, so a link can never open a page of your own app. Links that would start a program or open another file on the user’s computer are never followed, and a link’s JavaScript runs only when the actions plugin has JavaScript turned on.

Links work while the pointer or pan tool is active. With the annotation plugin’s link tool, the layer steps aside and links become annotations you can move, resize and retarget; see Annotations.

Links are annotations: create them with the annotation plugin, or with the engine; see Links.

Was this page helpful?

Your feedback goes directly to the documentation team.