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:
<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.
Change how links look#
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:
<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.
Read the links on a page#
link.listLinks(page.ref); // every link on the page
link.getLinkAt(page.ref, { x: 120, y: 80 }); // the link at a point, or nullEach link has an id, its bounds on the page, and a target: where it goes.
target.kind | Goes 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 |
Follow a link from code#
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.
| Method | What 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#
<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>| Event | Fires when | Carries |
|---|---|---|
onActivated | Someone followed a link, by click, key or code | target, activation |
onLoaded | A page’s links were read, or read again after a change | page |
Good to know#
Which links never run#
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 while editing annotations#
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.
Making links#
Links are annotations: create them with the annotation plugin, or with the engine; see Links.
Your feedback goes directly to the documentation team.