test(e2e): add initial testing harness

This commit is contained in:
Nico Jansen 2022-12-16 09:05:42 +01:00 committed by Antonin Stefanutti
parent 8d102c9115
commit c7f3c97a1c
14 changed files with 204630 additions and 3 deletions

26
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: CI
on:
push: ~
pull_request: ~
schedule:
- cron: '0 12 * * *'
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

4
.gitignore vendored
View File

@ -11,3 +11,7 @@
!package.json
!npm-shrinkwrap.json
!README.adoc
!test/
test/output/
!playwright.config.ts
!.github/

922
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,9 @@
"decktape": "decktape.js"
},
"scripts": {
"start": "node decktape.js"
"start": "node decktape.js",
"test-prepare-pdfs": "node test/run-decktape.js",
"test": "playwright test"
},
"repository": {
"type": "git",
@ -30,5 +32,10 @@
},
"engines": {
"node": ">=12.20"
},
"devDependencies": {
"koa": "2.14.1",
"koa-static": "5.0.0",
"@playwright/test": "1.28.1"
}
}

55
playwright.config.ts Normal file
View File

@ -0,0 +1,55 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
const port = 3010;
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './test',
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
baseURL: `http://localhost:${port}`
},
/* Configure projects for major browsers */
projects: [
{
name: 'firefox',
use: devices['Desktop Firefox'],
}
],
/* Run your local dev server before starting the tests */
webServer: {
command: `node test/run-server.js ${port}`,
port,
},
};
export default config;

21
test/e2e.spec.ts Normal file
View File

@ -0,0 +1,21 @@
import { test, expect } from "@playwright/test";
import fs from "fs";
test.describe("e2e", () => {
const inputDirectories = fs.readdirSync(new URL("./input", import.meta.url));
inputDirectories.forEach((input) => {
test(`should have no visual regression for ${input}`, async ({ page }) => {
await page.goto(`/show-pdf.html?file=${input}`);
await page.waitForFunction(() => typeof deck === "object");
const theCanvas = page.locator("#the-canvas");
const numberOfPages = await page.evaluate(async () => deck.numPages);
for (let i = 0; i < numberOfPages; i++) {
expect(await theCanvas.screenshot()).toMatchSnapshot({
name: `${input}-${i}.png`,
});
await page.evaluate(() => deck.nextPage());
}
});
});
});

5
test/globals.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
interface GlobalDeck {
numPages: number;
nextPage(): Promise<void>;
}
declare const deck: GlobalDeck;

View File

@ -0,0 +1,666 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Demo | reveal.js</title>
<meta name="description" content="" />
<link rel="stylesheet" href="main.css" />
<meta itemprop="name" content=" | reveal.js" />
<meta itemprop="description" content="" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content=" | reveal.js" />
<meta name="twitter:description" content="" />
<meta name="twitter:site" content="@hakimel" />
<meta name="twitter:creator" content="@hakimel" />
<meta
name="twitter:image:src"
content="https://revealjs.com/images/meta/reveal-twitter-card-1024x512.png"
/>
<meta property="og:title" content=" | reveal.js" />
<meta property="og:description" content="" />
<meta
property="og:image"
name="image"
content="https://revealjs.com/images/meta/reveal-og-card-1200-630.png"
/>
<meta property="og:url" content="https://revealjs.com/demo/" />
<meta property="og:site_name" content="reveal.js" />
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="website" />
<link rel="icon" type="image/png" href="/images/favicon.svg" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
</head>
<body class="h-screen full-page-demo" data-page="demo">
<div class="reveal">
<div class="slides">
<section>
<img
src="https://static.slid.es/reveal/logo-v1/reveal-white-text.svg"
alt="reveal.js"
style="
height: 180px;
margin: 0 auto 4rem auto;
background: transparent;
"
class="demo-logo"
/>
<h3>The HTML Presentation Framework</h3>
<p>
<small
>Created by
<a href="https://twitter.com/hakimel">Hakim El Hattab</a> and
<a href="https://github.com/hakimel/reveal.js/graphs/contributors"
>contributors</a
></small
>
</p>
<div
style="
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 1.5rem;
padding-top: 1.5rem;
"
>
<small>Sponsored by</small>
<a href="https://codeless.co" rel="dofollow"
><img
src="https://d1835mevib0k1p.cloudfront.net/reveal-js/sponsors/codeless_light.png"
width="227"
height="70"
/></a>
</div>
</section>
<section>
<h2>Hello There</h2>
<p>
reveal.js enables you to create beautiful interactive slide decks
using HTML. This presentation will show you examples of what it can
do.
</p>
</section>
<section>
<section>
<h2>Vertical Slides</h2>
<p>Slides can be nested inside of each other.</p>
<p>Use the <em>Space</em> key to navigate through all slides.</p>
<br /><a href="#" class="navigate-down"
><img
class="r-frame"
style="background: rgba(255, 255, 255, 0.1)"
width="178"
height="238"
data-src="https://static.slid.es/reveal/arrow.png"
alt="Down arrow"
/></a>
</section>
<section>
<h2>Basement Level 1</h2>
<p>
Nested slides are useful for adding additional detail underneath a
high level horizontal slide.
</p>
</section>
<section>
<h2>Basement Level 2</h2>
<p>That's it, time to go back up.</p>
<br /><a href="#/2"
><img
class="r-frame"
style="
background: rgba(255, 255, 255, 0.1);
transform: rotate(180deg);
"
width="178"
height="238"
data-src="https://static.slid.es/reveal/arrow.png"
alt="Up arrow"
/></a>
</section>
</section>
<section>
<h2>Slides</h2>
<p>
Not a coder? Not a problem. There's a fully-featured visual editor
for authoring these, try it out at
<a href="https://slides.com" target="_blank">https://slides.com</a>.
</p>
</section>
<section data-auto-animate>
<h2 data-id="code-title">Pretty Code</h2>
<pre
data-id="code-animation"
><code class="hljs" data-trim data-line-numbers>
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
...
);
}
</code></pre>
<p>
Code syntax highlighting courtesy of
<a href="https://highlightjs.org/usage/">highlight.js</a>.
</p>
</section>
<section data-auto-animate>
<h2 data-id="code-title">Even Prettier Animations</h2>
<pre
data-id="code-animation"
><code class="hljs" data-trim data-line-numbers="|4,8-11|17|22-24">
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
&lt;div&gt;
&lt;p&gt;You clicked {count} times&lt;/p&gt;
&lt;button onClick={() =&gt; setCount(count + 1)}&gt;
Click me
&lt;/button&gt;
&lt;/div&gt;
);
}
function SecondExample() {
const [count, setCount] = useState(0);
return (
&lt;div&gt;
&lt;p&gt;You clicked {count} times&lt;/p&gt;
&lt;button onClick={() =&gt; setCount(count + 1)}&gt;
Click me
&lt;/button&gt;
&lt;/div&gt;
);
}
</code></pre>
</section>
<section>
<h2>Point of View</h2>
<p>Press <strong>ESC</strong> to enter the slide overview.</p>
<p>
Hold down the <strong>alt</strong> key (<strong>ctrl</strong> in
Linux) and click on any element to zoom towards it using
<a href="http://lab.hakim.se/zoom-js">zoom.js</a>. Click again to
zoom back out.
</p>
<p>(NOTE: Use ctrl + click in Linux.)</p>
</section>
<section
data-auto-animate
data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)"
>
<h2>Auto-Animate</h2>
<p>
Automatically animate matching elements across slides with
<a href="https://revealjs.com/auto-animate/">Auto-Animate</a>.
</p>
<div class="r-hstack justify-center">
<div
data-id="box1"
style="
background: #999;
width: 50px;
height: 50px;
margin: 10px;
border-radius: 5px;
"
></div>
<div
data-id="box2"
style="
background: #999;
width: 50px;
height: 50px;
margin: 10px;
border-radius: 5px;
"
></div>
<div
data-id="box3"
style="
background: #999;
width: 50px;
height: 50px;
margin: 10px;
border-radius: 5px;
"
></div>
</div>
</section>
<section
data-auto-animate
data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)"
>
<div class="r-hstack justify-center">
<div
data-id="box1"
data-auto-animate-delay="0"
style="
background: cyan;
width: 150px;
height: 100px;
margin: 10px;
"
></div>
<div
data-id="box2"
data-auto-animate-delay="0.1"
style="
background: magenta;
width: 150px;
height: 100px;
margin: 10px;
"
></div>
<div
data-id="box3"
data-auto-animate-delay="0.2"
style="
background: yellow;
width: 150px;
height: 100px;
margin: 10px;
"
></div>
</div>
<h2 style="margin-top: 20px">Auto-Animate</h2>
</section>
<section
data-auto-animate
data-auto-animate-easing="cubic-bezier(0.770, 0.000, 0.175, 1.000)"
>
<div class="r-stack">
<div
data-id="box1"
style="
background: cyan;
width: 300px;
height: 300px;
border-radius: 200px;
"
></div>
<div
data-id="box2"
style="
background: magenta;
width: 200px;
height: 200px;
border-radius: 200px;
"
></div>
<div
data-id="box3"
style="
background: yellow;
width: 100px;
height: 100px;
border-radius: 200px;
"
></div>
</div>
<h2 style="margin-top: 20px">Auto-Animate</h2>
</section>
<section>
<h2>Touch Optimized</h2>
<p>
Presentations look great on touch devices, like mobile phones and
tablets. Simply swipe through your slides.
</p>
</section>
<section data-markdown>
<script type="text/template">
## Markdown support
Write content using inline or external Markdown.
Instructions and more info available in the [readme](https://revealjs.com/markdown/).
```html []
<section data-markdown>
## Markdown support
Write content using inline or external Markdown.
Instructions and more info available in the [readme](https://revealjs.com/markdown/).
</section>
```
</script>
</section>
<section>
<p>Add the <code>r-fit-text</code> class to auto-size text</p>
<h2 class="r-fit-text">FIT TEXT</h2>
</section>
<section>
<section id="fragments">
<h2>Fragments</h2>
<p>Hit the next arrow...</p>
<p class="fragment">... to step through ...</p>
<p>
<span class="fragment">... a</span>
<span class="fragment">fragmented</span>
<span class="fragment">slide.</span>
</p>
<aside class="notes">
This slide has fragments which are also stepped through in the
notes window.
</aside>
</section>
<section>
<h2>Fragment Styles</h2>
<p>There's different types of fragments, like:</p>
<p class="fragment grow">grow</p>
<p class="fragment shrink">shrink</p>
<p class="fragment fade-out">fade-out</p>
<p>
<span style="display: inline-block" class="fragment fade-right"
>fade-right, </span
><span style="display: inline-block" class="fragment fade-up"
>up, </span
><span style="display: inline-block" class="fragment fade-down"
>down, </span
><span style="display: inline-block" class="fragment fade-left"
>left</span
>
</p>
<p class="fragment fade-in-then-out">fade-in-then-out</p>
<p class="fragment fade-in-then-semi-out">fade-in-then-semi-out</p>
<p>
Highlight <span class="fragment highlight-red">red</span>
<span class="fragment highlight-blue">blue</span>
<span class="fragment highlight-green">green</span>
</p>
</section>
</section>
<section id="transitions">
<h2>Transition Styles</h2>
<p>
You can select from different transitions, like:<br /><a
href="?transition=none#/transitions"
>None</a
>
- <a href="?transition=fade#/transitions">Fade</a> -
<a href="?transition=slide#/transitions">Slide</a> -
<a href="?transition=convex#/transitions">Convex</a> -
<a href="?transition=concave#/transitions">Concave</a> -
<a href="?transition=zoom#/transitions">Zoom</a>
</p>
</section>
<section>
<section data-background="#dddddd">
<h2>Slide Backgrounds</h2>
<p>
Set <code>data-background="#dddddd"</code> on a slide to change
the background color. All CSS color formats are supported.
</p>
<a href="#" class="navigate-down"
><img
class="r-frame"
style="background: rgba(255, 255, 255, 0.1)"
width="178"
height="238"
data-src="https://static.slid.es/reveal/arrow.png"
alt="Down arrow"
/></a>
</section>
<section
data-background="https://static.slid.es/reveal/image-placeholder.png"
>
<h2>Image Backgrounds</h2>
<pre><code class="hljs html">&lt;section data-background="image.png"&gt;</code></pre>
</section>
<section
data-background="https://static.slid.es/reveal/image-placeholder.png"
data-background-repeat="repeat"
data-background-size="100px"
>
<h2>Tiled Backgrounds</h2>
<pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background="image.png" data-background-repeat="repeat" data-background-size="100px"&gt;</code></pre>
</section>
<section
data-background-video="https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.mp4"
data-background-color="#000000"
>
<div
style="
background-color: rgba(0, 0, 0, 0.9);
color: #fff;
padding: 20px;
"
>
<h2>Video Backgrounds</h2>
<pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background-video="video.mp4,video.webm"&gt;</code></pre>
</div>
</section>
<section data-background="http://i.giphy.com/90F8aUepslB84.gif">
<h2>... and GIFs!</h2>
</section>
</section>
<section
data-transition="slide"
data-background="#4d7e65"
data-background-transition="zoom"
>
<h2>Background Transitions</h2>
<p>
Different background transitions are available via the
backgroundTransition option. This one's called "zoom".
</p>
<pre><code class="hljs javascript">Reveal.configure({ backgroundTransition: 'zoom' })</code></pre>
</section>
<section
data-transition="slide"
data-background="#b5533c"
data-background-transition="zoom"
>
<h2>Background Transitions</h2>
<p>You can override background transitions per-slide.</p>
<pre><code class="hljs html" style="word-wrap: break-word;">&lt;section data-background-transition="zoom"&gt;</code></pre>
</section>
<section
data-background-iframe="https://hakim.se"
data-background-interactive
>
<div
style="
position: absolute;
width: 40%;
right: 0;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5),
0 5px 25px rgba(0, 0, 0, 0.2);
background-color: rgba(0, 0, 0, 0.9);
color: #fff;
padding: 20px;
font-size: 20px;
text-align: left;
"
>
<h2>Iframe Backgrounds</h2>
<p>
Since reveal.js runs on the web, you can easily embed other web
content. Try interacting with the page in the background.
</p>
</div>
</section>
<section>
<h2>Marvelous List</h2>
<ul>
<li>No order here</li>
<li>Or here</li>
<li>Or here</li>
<li>Or here</li>
</ul>
</section>
<section>
<h2>Fantastic Ordered List</h2>
<ol>
<li>One is smaller than...</li>
<li>Two is smaller than...</li>
<li>Three!</li>
</ol>
</section>
<section>
<h2>Tabular Tables</h2>
<table>
<thead>
<tr>
<th>Item</th>
<th>Value</th>
<th>Quantity</th>
</tr>
</thead>
<tbody>
<tr>
<td>Apples</td>
<td>$1</td>
<td>7</td>
</tr>
<tr>
<td>Lemonade</td>
<td>$2</td>
<td>18</td>
</tr>
<tr>
<td>Bread</td>
<td>$3</td>
<td>2</td>
</tr>
</tbody>
</table>
</section>
<section>
<h2>Clever Quotes</h2>
<p>
These guys come in two forms, inline:
<q
cite="http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations"
>The nice thing about standards is that there are so many to
choose from</q
>
and block:
</p>
<blockquote
cite="http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations"
>
&ldquo;For years there has been a theory that millions of monkeys
typing at random on millions of typewriters would reproduce the
entire works of Shakespeare. The Internet has proven this theory to
be untrue.&rdquo;
</blockquote>
</section>
<section>
<h2>Intergalactic Interconnections</h2>
<p>
You can link between slides internally,
<a href="#/2/3">like this</a>.
</p>
</section>
<section>
<h2>Speaker View</h2>
<p>
There's a
<a href="https://revealjs.com/speaker-view/">speaker view</a>. It
includes a timer, preview of the upcoming slide as well as your
speaker notes.
</p>
<p>Press the <em>S</em> key to try it out.</p>
<aside class="notes">
Oh hey, these are some notes. They'll be hidden in your
presentation, but you can see them if you open the speaker notes
window (hit 's' on your keyboard).
</aside>
</section>
<section>
<h2>Export to PDF</h2>
<p>
Presentations can be
<a href="https://revealjs.com/pdf-export/">exported to PDF</a>,
here's an example:
</p>
<iframe
data-src="https://www.slideshare.net/slideshow/embed_code/42840540"
width="445"
height="355"
frameborder="0"
marginwidth="0"
marginheight="0"
scrolling="no"
style="border: 3px solid #666; margin-bottom: 5px; max-width: 100%"
allowfullscreen
></iframe>
</section>
<section>
<h2>Global State</h2>
<p>
Set <code>data-state="something"</code> on a slide and
<code>"something"</code> will be added as a class to the document
element when the slide is open. This lets you apply broader style
changes, like switching the page background.
</p>
</section>
<section data-state="customevent">
<h2>State Events</h2>
<p>
Additionally custom events can be triggered on a per slide basis by
binding to the <code>data-state</code> name.
</p>
<pre><code class="javascript" data-trim contenteditable style="font-size: 18px;">
Reveal.on( 'customevent', function() {
console.log( '"customevent" has fired' );
} );
</code></pre>
</section>
<section>
<h2>Take a Moment</h2>
<p>
Press B or . on your keyboard to pause the presentation. This is
helpful when you're on stage and want to take distracting slides off
the screen.
</p>
</section>
<section>
<h2>Much more</h2>
<ul>
<li>Right-to-left support</li>
<li>
<a href="https://revealjs.com/api/">Extensive JavaScript API</a>
</li>
<li>
<a href="https://revealjs.com/auto-slide/">Auto-progression</a>
</li>
<li>
<a href="https://revealjs.com/backgrounds/#parallax-background"
>Parallax backgrounds</a
>
</li>
<li>
<a href="https://revealjs.com/keyboard/"
>Custom keyboard bindings</a
>
</li>
</ul>
</section>
<section style="text-align: left">
<h1>THE END</h1>
<p>
- <a href="https://slides.com">Try the online editor</a><br />-
<a href="https://github.com/hakimel/reveal.js"
>Source code &amp; documentation</a
>
</p>
</section>
</div>
</div>
<script src="main.js" defer="defer"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

46
test/run-decktape.js Normal file
View File

@ -0,0 +1,46 @@
import childProcess from "child_process";
import fs from "fs";
import { fileURLToPath } from "url";
import path from "path";
import { server } from "./server.js";
const decktapeExecutable = fileURLToPath(
new URL("../decktape.js", import.meta.url)
);
const outputDirectory = fileURLToPath(new URL("./output", import.meta.url));
const inputDirectories = fs.readdirSync(new URL("./input", import.meta.url));
const [, mainFileName] = process.argv;
if (mainFileName === fileURLToPath(import.meta.url)) {
try {
const port = server.listen();
await Promise.all(
inputDirectories.map(async (inputDir) => {
await runDecktape(port, inputDir);
})
);
} finally {
await server.close();
}
}
function runDecktape(port, inputDir) {
return new Promise((res, rej) => {
console.log(`Generating ${inputDir}`);
const outputFile = path.resolve(outputDirectory, `${inputDir}.pdf`);
const process = childProcess.exec(
`node ${decktapeExecutable} http://localhost:${port}/input/${inputDir}/ ${outputFile}`,
{ stdio: "inherit" },
(err) => {
if (err) {
rej(err);
} else {
console.log(`${inputDir}.pdf`);
res();
}
}
);
process.stdout.on('data', data => console.log(data.toString()));
process.stderr.on('data', data => console.error(data.toString()));
});
}

17
test/run-server.js Normal file
View File

@ -0,0 +1,17 @@
import { server } from "./server.js";
import { fileURLToPath } from "url";
import path from "path";
const [, mainFileName, port] = process.argv;
if (mainFileName === fileURLToPath(import.meta.url)) {
if (!port) {
const fileName = path.basename(fileURLToPath(import.meta.url));
console.error(`
Error: Missing port number to run on
Usage: node ${fileName} <port>
Example: node ${fileName} 3000`);
} else {
server.listen(parseInt(port));
}
}

28
test/server.js Normal file
View File

@ -0,0 +1,28 @@
import Koa from "koa";
import serve from "koa-static";
import { fileURLToPath } from "url";
import { promisify } from "util";
import path from "path";
const app = new Koa();
const dir = path.dirname(fileURLToPath(import.meta.url));
app.use(serve(dir));
export const server = {
listen(portHint = undefined) {
this.innerServer = app.listen(portHint);
const { port } = this.innerServer.address();
console.log(
`listening from http://localhost:${port}/ (serving static files from ${dir})`
);
return port;
},
async close() {
if (this.innerServer && this.innerServer.close) {
const closeAsync = promisify(this.innerServer.close).bind(this.innerServer);
await closeAsync();
}
},
};

50
test/show-pdf.html Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<canvas id="the-canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/pdfjs-dist@3.1.81/build/pdf.min.js"></script>
<script type="module">
const params = new URLSearchParams(window.location.search);
const pdf = await pdfjsLib.getDocument(`./output/${params.get('file')}.pdf`).promise;
let currentPage = 1;
await renderPage();
window.deck = {
nextPage: async () => {
if (currentPage < pdf.numPages) {
currentPage++;
await renderPage();
}
},
numPages: pdf.numPages,
};
async function renderPage() {
const page = await pdf.getPage(currentPage);
const scale = 1.5;
const viewport = page.getViewport({ scale: scale });
// Support HiDPI-screens.
const outputScale = window.devicePixelRatio || 1;
const canvas = document.getElementById("the-canvas");
const canvasContext = canvas.getContext("2d");
canvas.width = Math.floor(viewport.width * outputScale);
canvas.height = Math.floor(viewport.height * outputScale);
canvas.style.width = Math.floor(viewport.width) + "px";
canvas.style.height = Math.floor(viewport.height) + "px";
const transform =
outputScale !== 1
? [outputScale, 0, 0, outputScale, 0, 0]
: null;
await page.render({ canvasContext, transform, viewport })
.promise;
}
</script>
</body>
</html>