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 lang="ts">
import { interactionPlugin } from '@embedpdf/svelte/interaction';
import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
import { Viewer } from '@embedpdf/svelte/runtime';
import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
import { engine } from './pdf';
const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), linkPlugin()];
</script>
<Viewer {engine} {plugins}>
<Stage>
<RenderLayer />
<LinkLayer />
</Stage>
</Viewer>Loading live preview…
<script lang="ts" module>
import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
import { interactionPlugin } from '@embedpdf/svelte/interaction';
import { annotationPlugin } from '@embedpdf/svelte/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.svelte';
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>
<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
<DocumentGate>
{#snippet fallback()}
<p class="loading">Loading…</p>
{/snippet}
<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">
<RenderLayer />
<LinkLayer />
</Stage>
</DocumentGate>
</Viewer>- 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>{#snippet link()} lets you draw it
yourself, for example with an underline on hover:
<script lang="ts">
import { LinkLayer } from '@embedpdf/svelte/link';
</script>
<LinkLayer>
{#snippet link({ link, native })}
<span class="pdf-link" title={link.target.kind === 'uri' ? link.target.uri : undefined}>
{@render native()}
</span>
{/snippet}
</LinkLayer>Loading live preview…
<script lang="ts" module>
import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
import { interactionPlugin } from '@embedpdf/svelte/interaction';
import { annotationPlugin } from '@embedpdf/svelte/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.svelte';
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()) };
};
</script>
<script lang="ts">
let shown = $state(true);
</script>
<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
<DocumentGate>
{#snippet fallback()}
<p class="loading">Loading…</p>
{/snippet}
<AddLinks />
<div class="toolbar">
<div class="segmented" role="group" aria-label="Link areas">
<button type="button" aria-pressed={shown} onclick={() => (shown = true)}>
Show link areas
</button>
<button type="button" aria-pressed={!shown} onclick={() => (shown = false)}>
As the PDF has them
</button>
</div>
</div>
<Stage class="stage">
<RenderLayer />
<LinkLayer>
{#snippet link({ link, native })}
{#if shown}
<!-- Wrapping `native` keeps what a click does. -->
<span class="pdf-link" data-kind={link.target.kind}>{@render native()}</span>
{:else}
<!-- The layer's own, invisible area. -->
{@render native()}
{/if}
{/snippet}
</LinkLayer>
</Stage>
</DocumentGate>
</Viewer>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 lang="ts" module>
import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
import { interactionPlugin } from '@embedpdf/svelte/interaction';
import { annotationPlugin } from '@embedpdf/svelte/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.svelte';
import LinkList from './LinkList.svelte';
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()) };
};
</script>
<script lang="ts">
// The list reads the links once they're made.
let ready = $state(false);
</script>
<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
<DocumentGate>
{#snippet fallback()}
<p class="loading">Loading…</p>
{/snippet}
<AddLinks onAdded={() => (ready = true)} />
<div class="layout">
<LinkList {ready} />
<Stage class="stage">
<RenderLayer />
<LinkLayer />
</Stage>
</div>
</DocumentGate>
</Viewer>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 lang="ts">
import { LinkLayer, useLinkEvent } from '@embedpdf/svelte/link';
import { RenderLayer } from '@embedpdf/svelte/render';
import { Stage } from '@embedpdf/svelte/stage';
import { analytics } from './analytics';
useLinkEvent(
(link) => link.onActivated,
({ target }) => analytics.track('link_followed', { kind: target.kind }),
);
</script>
<Stage>
<RenderLayer />
<LinkLayer />
</Stage>| 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.